/*
* GeoSolutions - MapstoreMobile - GeoSpatial Framework on Android based devices
* Copyright (C) 2014 GeoSolutions (www.geo-solutions.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.geosolutions.geocollect.android.core.mission;
import it.geosolutions.android.map.MapsActivity;
import it.geosolutions.android.map.fragment.MapFragment;
import it.geosolutions.android.map.model.Layer;
import it.geosolutions.android.map.model.MSMMap;
import it.geosolutions.android.map.utils.SpatialDbUtils;
import it.geosolutions.android.map.wfs.geojson.feature.Feature;
import it.geosolutions.geocollect.android.app.BuildConfig;
import it.geosolutions.geocollect.android.core.GeoCollectApplication;
import it.geosolutions.geocollect.android.app.R;
import it.geosolutions.geocollect.android.core.form.FormEditActivity;
import it.geosolutions.geocollect.android.core.form.utils.FormBuilder;
import it.geosolutions.geocollect.android.core.mission.utils.MissionUtils;
import it.geosolutions.geocollect.android.core.mission.utils.PersistenceUtils;
import it.geosolutions.geocollect.model.config.MissionTemplate;
import it.geosolutions.geocollect.model.viewmodel.Field;
import it.geosolutions.geocollect.model.viewmodel.type.XType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import jsqlite.Exception;
import jsqlite.Stmt;
import org.mapsforge.core.model.GeoPoint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.vividsolutions.jts.geom.Point;
/**
* A fragment representing a single Pending Mission detail screen. This fragment
* is either contained in a {@link PendingMissionListActivity} in two-pane mode
* (on tablets) or a {@link PendingMissionDetailActivity} on handsets.
*/
public class PendingMissionDetailFragment extends MapFragment implements LoaderCallbacks<Void> {
/**
* Tag for logging
*/
public static final String TAG = "MissionDetail";
public static final int EDIT_ACTIVITY_CODE = 0;
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM_ID = "item_id";
public static final String ARG_ITEM_FEATURE = "item_FEATURE";
/**
* The <ScrollView> that display fragment content
*/
private ScrollView mScrollView;
/**
* The <LinearLayout> inside the <ScrollView>
* This displays dynamic content as the other forms.
*/
private LinearLayout mFormView;
/**
* <ProgressBar> for loading
*/
private ProgressBar mProgressView;
private boolean mDone;
protected Mission mission;
public static int DEFAULT_COLOR = Color.GRAY;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM_ID)) {
// TODO: get the Feature from db
Log.d(TAG, "onCreate() ARG_ITEM_ID: " + getArguments().getString(ARG_ITEM_ID));
}
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView(): container = " + container
+ "savedInstanceState = " + savedInstanceState);
if (mScrollView == null) {
// normally inflate the view hierarchy
mScrollView = (ScrollView) inflater.inflate(R.layout.preview_page_fragment,
container, false);
mFormView = (LinearLayout) mScrollView.findViewById(R.id.previewcontent);
mProgressView = (ProgressBar) mScrollView
.findViewById(R.id.loading);
} else {
// mScrollView is still attached to the previous view hierarchy
// we need to remove it and re-attach it to the current one
ViewGroup parent = (ViewGroup) mScrollView.getParent();
parent.removeView(mScrollView);
}
//TODO it should be false because orientation change can
// have different layout (for map view )
//setRetainInstance(true);
return mScrollView;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Remove the button to open the full map
MenuItem full_map = menu.findItem(R.id.full_map);
if (full_map != null) {
menu.removeItem(R.id.full_map);
}
inflater.inflate(R.menu.nav_map_editable, menu);
}
/* (non-Javadoc)
* @see com.actionbarsherlock.app.SherlockFragment#onOptionsItemSelected(com.actionbarsherlock.view.MenuItem)
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id==R.id.accept){
//check if this mission was already marked as uploadable and is going to be altered
final String gcid = MissionUtils.getFeatureGCID( mission.getOrigin());
HashMap<String,ArrayList<String>> uploadables = PersistenceUtils.loadUploadables(getActivity());
final String table = mission.getTemplate().schema_sop.localFormStore;
final ArrayList<String> ids = uploadables.get(table);
//if an entry exists, remove it and save
if(ids != null && ids.size() > 0 && ids.contains(gcid)){
ids.remove(gcid);
uploadables.put(table, ids);
PersistenceUtils.saveUploadables(getActivity(), uploadables);
}
Intent i = new Intent(getSherlockActivity(),FormEditActivity.class);
i.putExtra("MISSION", mission);
startActivityForResult(i, EDIT_ACTIVITY_CODE);
return true;
}else if(id == R.id.single_map){
final GeoPoint geoPoint = getOriginGeoPoint();
if(geoPoint != null){
Intent mapIntent = new Intent(getSherlockActivity(),SimpleMapActivity.class);
mapIntent.putExtra(SimpleMapActivity.ARG_PRIORITY_COLOR, getPriorityColor());
mapIntent.putExtra(SimpleMapActivity.ARG_FIRST_POINT_LAT, geoPoint.latitude);
mapIntent.putExtra(SimpleMapActivity.ARG_FIRST_POINT_LON, geoPoint.longitude);
mapIntent.putExtra(SimpleMapActivity.ARG_ZOOM,((byte) 18));
final GeoPoint updatedPoint = getUpdatedGeoPoint();
if(updatedPoint != null){
mapIntent.putExtra(SimpleMapActivity.ARG_SECOND_POINT_LAT, updatedPoint.latitude);
mapIntent.putExtra(SimpleMapActivity.ARG_SECOND_POINT_LON, updatedPoint.longitude);
}
MissionTemplate t = ((GeoCollectApplication) getActivity().getApplication()).getTemplate();
MSMMap m = SpatialDbUtils.mapFromDb();
for (Iterator<Layer> it = m.layers.iterator(); it.hasNext();) {
Layer layer = it.next();
if (!(layer.getTitle().equals(t.schema_seg.localSourceStore)
|| layer.getTitle().equals(t.schema_sop.localFormStore) || layer.getTitle()
.equals(t.schema_seg.localSourceStore + MissionTemplate.NEW_NOTICE_SUFFIX))) {
Log.d(this.getClass().getSimpleName(), layer.getTitle()
+ " not corresponding to current schema " + t.schema_seg.localSourceStore);
it.remove();
}
}
mapIntent.putExtra(MapsActivity.MSM_MAP, m);
startActivity(mapIntent);
}else{
Log.e(TAG, "could not retrieve geopoint");
}
}else if(id == R.id.navigate){
//start an intent to navigate to this position
GeoPoint geoPoint = getUpdatedGeoPoint();
if(geoPoint == null){
geoPoint = getOriginGeoPoint();
}
if(geoPoint == null){
Log.e(TAG, "no coordinate to navigate to available");
return super.onOptionsItemSelected(item);
}
/**
* http://stackoverflow.com/questions/5801684/intent-to-start-a-navigation-activity
*
* The bad news is, there isn't a standard Intent URI for navigation.
*
* possibilities :
*
* Uri.parse("google.navigation:q= lat,lon) //won't start navigation
* Uri.parse("geo:latitude,longitude //will only zoom map on position
* Uri.parse("http://maps.google.com/maps?daddr=lat,lon) worked best for now
*
* problematic for users without google maps App but they should be few ?!
*/
// String uri_string = String.format("google.navigation:q%f",geoPoint.latitude,geoPoint.longitude);
String uri_string = String.format("http://maps.google.com/maps?daddr=%s,%s",Double.toString(geoPoint.latitude),Double.toString(geoPoint.longitude));
Log.d(TAG, "uri "+ uri_string);
Intent navIntent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(uri_string));
startActivity(navIntent);
}
return super.onOptionsItemSelected(item);
}
/**
* Return the color code of the priority field of the mission
* Default is Color.GRAY
* @return
*/
public int getPriorityColor(){
Field colorField = getField(XType.separatorWithIcon);
if(colorField == null){
return DEFAULT_COLOR;
}
HashMap <String,String> colors = mission.getTemplate().priorityValuesColors;
final String key = mission.getValueAsString(getActivity(), colorField);
final String color = colors.get(key);
if(color == null){
return DEFAULT_COLOR;
}
try {
return Color.parseColor(color);
}catch(IllegalArgumentException iae){
if(BuildConfig.DEBUG){
Log.w(TAG, "Could not parse color: "+color);
}
return DEFAULT_COLOR;
}
}
/**
* reads out the missions origin GeoPoint by retrieving the mapView field and using it
* to extract it out of the missions tags
* @return the geopoint of this mission
*/
public GeoPoint getOriginGeoPoint(){
final Field mapField = getField(XType.mapViewPoint);
if(mapField == null){
return null;
}
//extract the point
GeoPoint geoPoint = null;
List<String> tags = MissionUtils.getTags(mapField.value);
if(tags != null && tags.size() ==1){
Point geom = (Point) mission.getValueByTag(getActivity(), tags.get(0));
if(geom !=null){
if(!geom.isEmpty()){
double lat = geom.getY();
double lon = geom.getX();
geoPoint = new GeoPoint(lat, lon);
}
}
}
return geoPoint;
}
/**
* accesses the database to acquire an updated position for this mission
* @return the updated GeoPoint or null if none available
*/
public GeoPoint getUpdatedGeoPoint(){
final Field f = getField(XType.mapViewPoint);
final String tableName = getTablename();
// Default ID
String originIDString = MissionUtils.getMissionGCID(mission);
final String s = "SELECT Y(" + f.fieldId + "), X(" + f.fieldId + ") FROM '" + tableName + "' WHERE "+ Mission.ORIGIN_ID_STRING +" = '" + originIDString +"';";
if(mission == null || mission.db == null){
Log.w(TAG, "Cannot retrieve mission database");
return null;
}
try {
if(jsqlite.Database.complete(s)){
Stmt st = mission.db.prepare(s);
if(st.step()){
final GeoPoint p = new GeoPoint(st.column_double(0), st.column_double(1));
st.close();
return p;
}
}else{
if(BuildConfig.DEBUG){
Log.w(TAG, "Query is not complete:\n"+s);
}
}
} catch (Exception e) {
Log.e(TAG, "Error retrieving updatedPoint",e);
}
return null;
}
/**
* returns a field from the default templates previews form
* @param xType to search for
* @return the field according to the xType
*/
public Field getField(final XType xType){
final MissionTemplate t = MissionUtils.getDefaultTemplate(getSherlockActivity());
for(Field f : t.preview.fields){
if(f.xtype == xType){
return f;
}
}
return null;
}
/**
* gets the tablename of this mission's results
* @return
*/
public String getTablename(){
String tableName = mission.getTemplate().id+"_data";
if(mission.getTemplate().schema_sop != null
&& mission.getTemplate().schema_sop.localFormStore != null
&& !mission.getTemplate().schema_sop.localFormStore.isEmpty()){
tableName = mission.getTemplate().schema_sop.localFormStore;
}
return tableName;
}
/**
* Handle the results
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult()");
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == EDIT_ACTIVITY_CODE){
MissionTemplate t = MissionUtils.getDefaultTemplate(getSherlockActivity());
PersistenceUtils.loadPageData(t.preview, mFormView, mission, getSherlockActivity(),true);
}
}
/**
* Fills the Page layout with widgets based on page template
*/
private void buildForm() {
// if the view hierarchy was already build, skip this
if (mDone)
return;
MissionTemplate t = MissionUtils.getDefaultTemplate(getSherlockActivity());
FormBuilder.buildForm(getActivity(), this.mFormView,t.preview.fields,mission);//TODO page is not enough, some data should be accessible like constants and data
PersistenceUtils.loadPageData(t.preview, mFormView, mission, getSherlockActivity(),false);
// the view hierarchy is now complete
mDone = true;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated(): savedInstanceState = "
+ savedInstanceState);
if(savedInstanceState == null){
toggleLoading(true);
getLoaderManager().restartLoader(0, null, this);
}else{
toggleLoading(true);
Feature origin = (Feature)savedInstanceState.getSerializable(ARG_ITEM_FEATURE);
Mission m = new Mission();
m.setTemplate(MissionUtils.getDefaultTemplate(getSherlockActivity()));
m.setOrigin(origin);
if(getSherlockActivity() instanceof PendingMissionDetailActivity){
Log.d(TAG, "Loader: Connecting to Activity database");
m.db = ((PendingMissionDetailActivity)getSherlockActivity()).spatialiteDatabase;
}else if(getSherlockActivity() instanceof PendingMissionListActivity){
Log.d(TAG, "Loader: Connecting to Activity database");
m.db = ((PendingMissionListActivity)getSherlockActivity()).spatialiteDatabase;
}else {
Log.w(TAG, "Loader: Could not connect to Activity database");
}
mission =m;
toggleLoading(false);
buildForm();
}
}
/**
* Load the mission and template data either from the database or from the intent
*/
public Loader<Void> onCreateLoader(int id, Bundle args) {
Log.d(TAG, "onCreateLoader(): id=" + id);
Loader<Void> loader = new AsyncTaskLoader<Void>(getActivity()) {
@Override
public Void loadInBackground() {
Activity activity = getSherlockActivity();
Feature myFeature = (Feature) getArguments().getSerializable(ARG_ITEM_FEATURE);
Mission m = new Mission();
m.setTemplate(MissionUtils.getDefaultTemplate(activity));
m.setOrigin(myFeature);
if(activity instanceof PendingMissionDetailActivity){
Log.d(TAG, "Loader: Connecting to Activity database");
m.db = ((PendingMissionDetailActivity)activity).spatialiteDatabase;
}else if(activity instanceof PendingMissionListActivity){
Log.d(TAG, "Loader: Connecting to Activity database");
m.db = ((PendingMissionListActivity)activity).spatialiteDatabase;
}else{
Log.w(TAG, "Loader: Could not connect to Activity database");
}
mission =m;
return null;
}
};
//TODO create loader;
loader.forceLoad();
return loader;
}
@Override
public void onSaveInstanceState(Bundle outState) {
if(mission != null){
//save the origin
outState.putSerializable(ARG_ITEM_FEATURE, mission.getOrigin());
}
}
public void onLoadFinished(Loader<Void> id, Void result) {
Log.d(TAG, "onLoadFinished(): id=" + id);
toggleLoading(false);
buildForm();
}
public void onLoaderReset(Loader<Void> loader) {
Log.d(TAG, "onLoaderReset(): id=" + loader.getId());
}
private void toggleLoading(boolean show) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mFormView.setGravity(show ? Gravity.CENTER : Gravity.TOP);
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "onDestroyView()");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
}
@Override
public void onDetach() {
super.onDetach();
Log.d(TAG, "onDetach()");
}
}