From 5e44a8aed1add369d03bd38fa9833d72728c0aaa Mon Sep 17 00:00:00 2001 From: Michael Palmer Date: Sat, 22 Apr 2017 22:32:46 -0400 Subject: [PATCH 1/4] view session --- .../java/com/kitty/geotracker/GPSService.java | 3 +- .../com/kitty/geotracker/MapsActivity.java | 36 +++- .../kitty/geotracker/MeteorController.java | 61 ++++++- .../kitty/geotracker/dialogs/ViewSession.java | 171 ++++++++++++++++++ app/src/main/res/layout/activity_maps.xml | 20 +- app/src/main/res/values/strings.xml | 2 + 6 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java diff --git a/app/src/main/java/com/kitty/geotracker/GPSService.java b/app/src/main/java/com/kitty/geotracker/GPSService.java index d55d34a..8e775ec 100644 --- a/app/src/main/java/com/kitty/geotracker/GPSService.java +++ b/app/src/main/java/com/kitty/geotracker/GPSService.java @@ -58,13 +58,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Nullable @Override public IBinder onBind(Intent intent) { - Log.d(getClass().getSimpleName(), "onBind()"); return null; } @Override public void onLocationChanged(Location location) { - Log.d(getClass().getSimpleName(), "Service posting location to Meteor: " + location.toString()); + Log.d(getClass().getSimpleName(), "Service received location: " + location.toString()); meteorController.postLocation(location); } diff --git a/app/src/main/java/com/kitty/geotracker/MapsActivity.java b/app/src/main/java/com/kitty/geotracker/MapsActivity.java index 45fcc21..4c58bb7 100644 --- a/app/src/main/java/com/kitty/geotracker/MapsActivity.java +++ b/app/src/main/java/com/kitty/geotracker/MapsActivity.java @@ -29,6 +29,7 @@ import com.google.maps.android.heatmaps.HeatmapTileProvider; import com.kitty.geotracker.dialogs.JoinSession; import com.kitty.geotracker.dialogs.StartSession; +import com.kitty.geotracker.dialogs.ViewSession; import java.util.ArrayList; import java.util.HashMap; @@ -44,11 +45,12 @@ public class MapsActivity extends FragmentActivity implements View.OnClickListener, StartSession.StartSessionListener, JoinSession.JoinSessionListener, + ViewSession.ViewSessionListener, MeteorController.MeteorControllerListener { private GoogleMap mMap; private FloatingActionsMenu floatingMenu; - private FloatingActionButton btnJoinSession, btnStartSession, btnLeaveSession, btnEndSession; + private FloatingActionButton btnJoinSession, btnStartSession, btnLeaveSession, btnEndSession, btnViewSession; private MeteorController meteorController; private HashMap mapMarkers = new HashMap<>(); private Intent serviceIntent; @@ -70,6 +72,7 @@ protected void onCreate(Bundle savedInstanceState) { FloatingActionButton btnSettings = (FloatingActionButton) findViewById(R.id.btn_settings); btnJoinSession = (FloatingActionButton) findViewById(R.id.btn_join_session); btnStartSession = (FloatingActionButton) findViewById(R.id.btn_start_session); + btnViewSession = (FloatingActionButton) findViewById(R.id.btn_view_session); btnLeaveSession = (FloatingActionButton) findViewById(R.id.btn_leave_session); btnEndSession = (FloatingActionButton) findViewById(R.id.btn_end_session); btnSettings.setOnClickListener(this); @@ -77,6 +80,7 @@ protected void onCreate(Bundle savedInstanceState) { btnStartSession.setOnClickListener(this); btnLeaveSession.setOnClickListener(this); btnEndSession.setOnClickListener(this); + btnViewSession.setOnClickListener(this); // Get map fragment and register callback SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() @@ -122,6 +126,12 @@ public void onClick(View v) { openJoinSessionDialog(); break; + case R.id.btn_view_session: + // Create the start session dialog + ViewSession viewSession = new ViewSession(); + viewSession.show(getSupportFragmentManager(), viewSession.getClass().getSimpleName()); + break; + case R.id.btn_leave_session: leaveSession(); break; @@ -261,6 +271,7 @@ public void onSessionStarted(String sessionName) { // Hide the start and join session buttons, show the end session button. btnStartSession.setVisibility(View.GONE); btnJoinSession.setVisibility(View.GONE); + btnViewSession.setVisibility(View.GONE); btnEndSession.setVisibility(View.VISIBLE); } @@ -280,6 +291,19 @@ public void onSessionJoined(final String sessionName) { // Hide the start and join session buttons, show the leave session button. btnStartSession.setVisibility(View.GONE); btnJoinSession.setVisibility(View.GONE); + btnViewSession.setVisibility(View.GONE); + btnLeaveSession.setVisibility(View.VISIBLE); + } + + @Override + public void onSessionViewed(String sessionName) { + // View the session + meteorController.viewSession(sessionName); + + // Hide the start and join session buttons, show the leave session button. + btnStartSession.setVisibility(View.GONE); + btnJoinSession.setVisibility(View.GONE); + btnViewSession.setVisibility(View.GONE); btnLeaveSession.setVisibility(View.VISIBLE); } @@ -316,6 +340,7 @@ public void leaveSession() { // Hide the leave session button, show the start and join session buttons btnStartSession.setVisibility(View.VISIBLE); btnJoinSession.setVisibility(View.VISIBLE); + btnViewSession.setVisibility(View.VISIBLE); btnLeaveSession.setVisibility(View.GONE); // Stop location updates @@ -370,6 +395,7 @@ public void onSuccess(String result) { // Hide/show buttons btnStartSession.setVisibility(View.VISIBLE); btnJoinSession.setVisibility(View.VISIBLE); + btnViewSession.setVisibility(View.VISIBLE); btnEndSession.setVisibility(View.GONE); // Show confirmation @@ -411,8 +437,12 @@ private void startLocationUpdates() { * Stop the location update service */ private void stopLocationUpdates() { - Log.d(getClass().getSimpleName(), "Stopping location updates..."); - stopService(serviceIntent); + try { + Log.d(getClass().getSimpleName(), "Stopping location updates..."); + stopService(serviceIntent); + } catch (NullPointerException e) { + // Let it fly + } } /** diff --git a/app/src/main/java/com/kitty/geotracker/MeteorController.java b/app/src/main/java/com/kitty/geotracker/MeteorController.java index c5afe51..dfb4bb1 100644 --- a/app/src/main/java/com/kitty/geotracker/MeteorController.java +++ b/app/src/main/java/com/kitty/geotracker/MeteorController.java @@ -43,6 +43,7 @@ public class MeteorController implements MeteorCallback, SharedPreferences.OnSha public static final int STATE_NO_SESSION = 0; public static final int STATE_CREATED_SESSION = 1; public static final int STATE_JOINED_SESSION = 2; + public static final int STATE_VIEWING_SESSION = 3; // Sessions public static final String COLLECTION_SESSIONS = "Sessions"; @@ -453,6 +454,50 @@ public void onError(String error, String reason, String details) { }); } + /** + * View a finished session + * + * @param sessionName Session to view + */ + public void viewSession(final String sessionName) { + Log.d(TAG, "[View Session] Viewing session \"" + sessionName + "\""); + setState(STATE_VIEWING_SESSION); + session = sessionName; + Document document = meteor + .getDatabase() + .getCollection(COLLECTION_SESSIONS) + .whereEqual(COLLECTION_SESSIONS_COLUMN_TITLE, sessionName) + .whereEqual(COLLECTION_SESSIONS_COLUMN_ACTIVE, false) + .findOne(); + + if (document == null) { + new AlertDialog.Builder(context) + .setMessage("Session cannot be viewed") + .show(); + return; + } + + // Subscribe to the session + meteor.subscribe(sessionName, null, new SubscribeListener() { + @Override + public void onSuccess() { + Log.d(TAG, "[View Session] Session viewing successfully."); + } + + @Override + public void onError(String error, String reason, String details) { + Log.e(TAG, "[View Session] Failed to view session \"" + sessionName + "\""); + Log.e(TAG, "[View Session] Error: " + error); + Log.e(TAG, "[View Session] Reason: " + reason); + Log.e(TAG, "[View Session] Details: " + details); + clearSession(); + new AlertDialog.Builder(context) + .setMessage("Failed to view session") + .show(); + } + }); + } + /** * Clear the current session */ @@ -468,7 +513,17 @@ public void clearSession() { * @return List of sessions */ public Document[] getSessions() { - return database.getCollection(COLLECTION_SESSIONS).whereEqual(COLLECTION_SESSIONS_COLUMN_ACTIVE, true).find(); + return getSessions(true); + } + + /** + * Get all sessions + * + * @param active Whether the session is active + * @return Session list + */ + public Document[] getSessions(boolean active) { + return database.getCollection(COLLECTION_SESSIONS).whereEqual(COLLECTION_SESSIONS_COLUMN_ACTIVE, active).find(); } /** @@ -478,7 +533,7 @@ public Document[] getSessions() { */ public void postLocation(Location location) { // Only post location if the user is a member of a session (and not a session owner) - if (getState() != STATE_JOINED_SESSION || getSession() == null) { + if (getState() != STATE_JOINED_SESSION || getState() != STATE_VIEWING_SESSION || getSession() == null) { return; } @@ -518,7 +573,7 @@ public void onDataAdded(String collectionName, String documentID, String newValu // Only trigger GPS data listener if the user created the session if (collectionName.equals(COLLECTION_GPS_DATA)) { - if (getState() == STATE_CREATED_SESSION && getSession() != null) { + if ((getState() == STATE_CREATED_SESSION || getState() == STATE_VIEWING_SESSION) && getSession() != null) { mListener.onReceivedGPSData(documentID); } } diff --git a/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java b/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java new file mode 100644 index 0000000..45bc87c --- /dev/null +++ b/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java @@ -0,0 +1,171 @@ +package com.kitty.geotracker.dialogs; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.widget.ArrayAdapter; + +import com.kitty.geotracker.MeteorController; +import com.kitty.geotracker.R; + +import java.util.ArrayList; +import java.util.HashMap; + +import im.delight.android.ddp.Meteor; +import im.delight.android.ddp.MeteorCallback; +import im.delight.android.ddp.db.Collection; +import im.delight.android.ddp.db.Database; +import im.delight.android.ddp.db.Document; + + +public class ViewSession extends DialogFragment implements MeteorCallback, DialogInterface.OnClickListener { + + private ViewSessionListener mListener; + private Meteor mMeteor; + private MeteorController meteorController; + private Database database; + private ArrayList items = new ArrayList<>(); + private HashMap documentMap = new HashMap<>(); + private ArrayAdapter adapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, items); + meteorController = MeteorController.getInstance(); + mMeteor = meteorController.getMeteor(); + mMeteor.addCallback(this); + database = mMeteor.getDatabase(); + refreshData(); + } + + @Override + public void onDestroy() { + Log.d(getClass().getSimpleName(), "OnDestroy: Unsubscribe from " + MeteorController.SUBSCRIPTION_SESSION_LIST); + mMeteor.removeCallback(this); + super.onDestroy(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.view_session) + .setAdapter(adapter, this); + return builder.create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + Log.d(getClass().getSimpleName(), "Selected item " + String.valueOf(which)); + mListener.onSessionViewed(items.get(which)); + } + + // Override the Fragment.onAttach() method to instantiate the ViewSessionListener + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + // Verify that the host activity implements the callback interface + try { + // Instantiate the ViewSessionListener so we can send events to the host + mListener = (ViewSessionListener) activity; + } catch (ClassCastException e) { + // The activity doesn't implement the interface, throw exception + throw new ClassCastException(activity.toString() + " must implement ViewSessionListener"); + } + } + + /* The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. */ + public interface ViewSessionListener { + public void onSessionViewed(final String sessionName); + } + + private void refreshData() { + items.clear(); + documentMap.clear(); + for (Document document : meteorController.getSessions(false)) { + String title = document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_TITLE).toString(); + items.add(title); + documentMap.put(document.getId(), title); + } + } + + @Override + public void onConnect(boolean signedInAutomatically) {} + + @Override + public void onDisconnect() {} + + @Override + public void onException(Exception e) {} + + @Override + public void onDataAdded(String collectionName, String documentID, String newValuesJson) { + Log.d(getClass().getSimpleName(), "New data added to " + collectionName + ": " + documentID); + if (collectionName.equals(MeteorController.COLLECTION_SESSIONS)) { + Collection collection = database.getCollection(collectionName); + if (!documentMap.containsKey(documentID)) { + Document document = collection.getDocument(documentID); + boolean active = (boolean) document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_ACTIVE); + + // If this session is not active, don't add it to the list + if (active) { + return; + } + + String title = document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_TITLE).toString(); + items.add(title); + documentMap.put(documentID, title); + adapter.notifyDataSetChanged(); + } + } + } + + @Override + public void onDataRemoved(String collectionName, String documentID) { + Log.d(getClass().getSimpleName(), "Data removed from " + collectionName + ": " + documentID); + if (collectionName.equals(MeteorController.COLLECTION_SESSIONS)) { + String title = documentMap.get(documentID); + if (title != null) { + items.remove(title); + documentMap.remove(documentID); + adapter.notifyDataSetChanged(); + } + } + } + + @Override + public void onDataChanged(String collectionName, String documentID, String updatedValuesJson, + String removedValuesJson) { + Log.d(getClass().getSimpleName(), "Data changed in " + collectionName + ": " + documentID); + if (collectionName.equals(MeteorController.COLLECTION_SESSIONS)) { + + Collection collection = database.getCollection(collectionName); + Document document = collection.getDocument(documentID); + + boolean active = (boolean) document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_ACTIVE); + String title = (String) document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_TITLE); + + if (active && !items.contains(title)) { + // Session becomes inactive + items.remove(title); + documentMap.remove(documentID); + adapter.notifyDataSetChanged(); + } else if (!active && items.contains(title)) { + // Session becomes active + items.add(title); + documentMap.put(documentID, title); + adapter.notifyDataSetChanged(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_maps.xml b/app/src/main/res/layout/activity_maps.xml index 98af5b4..c9a00a5 100644 --- a/app/src/main/res/layout/activity_maps.xml +++ b/app/src/main/res/layout/activity_maps.xml @@ -32,7 +32,7 @@ android:layout_height="wrap_content" app:fab_colorNormal="@color/white" app:fab_size="mini" - app:fab_title="Settings" + app:fab_title="@string/title_activity_settings" app:fab_icon="@drawable/ic_build_black_24dp" app:fab_colorPressed="@color/white_pressed"/> @@ -42,7 +42,7 @@ android:layout_height="wrap_content" app:fab_colorNormal="@color/white" app:fab_size="mini" - app:fab_title="Start Session" + app:fab_title="@string/start_session" app:fab_icon="@drawable/ic_add_black_24dp" app:fab_colorPressed="@color/white_pressed"/> @@ -52,7 +52,17 @@ android:layout_height="wrap_content" app:fab_colorNormal="@color/white" app:fab_size="mini" - app:fab_title="Join Session" + app:fab_title="@string/join_session" + app:fab_icon="@drawable/ic_add_black_24dp" + app:fab_colorPressed="@color/white_pressed"/> + + @@ -63,7 +73,7 @@ android:visibility="gone" app:fab_colorNormal="@color/white" app:fab_size="mini" - app:fab_title="End Session" + app:fab_title="@string/end_session" app:fab_icon="@drawable/ic_cancel_black_24dp" app:fab_colorPressed="@color/white_pressed"/> @@ -74,7 +84,7 @@ android:visibility="gone" app:fab_colorNormal="@color/white" app:fab_size="mini" - app:fab_title="Leave Session" + app:fab_title="@string/leave_session" app:fab_icon="@drawable/ic_cancel_black_24dp" app:fab_colorPressed="@color/white_pressed"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 280b8a5..a9c6581 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Start Session Join Session + View Session Leave Session Session Name @@ -15,5 +16,6 @@ Meteor IP Location permission is required + End Session From 191616203364f077d46566ee1a74a6be1de44ae8 Mon Sep 17 00:00:00 2001 From: Michael Palmer Date: Sun, 23 Apr 2017 17:30:05 -0400 Subject: [PATCH 2/4] fix message --- .../main/java/com/kitty/geotracker/MeteorController.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/kitty/geotracker/MeteorController.java b/app/src/main/java/com/kitty/geotracker/MeteorController.java index 46c9866..1cb255e 100644 --- a/app/src/main/java/com/kitty/geotracker/MeteorController.java +++ b/app/src/main/java/com/kitty/geotracker/MeteorController.java @@ -529,9 +529,7 @@ public void viewSession(final String sessionName) { .findOne(); if (document == null) { - new AlertDialog.Builder(context) - .setMessage("Session cannot be viewed") - .show(); + mListener.onSessionMessage("Session cannot be viewed"); return; } @@ -549,9 +547,7 @@ public void onError(String error, String reason, String details) { Log.e(TAG, "[View Session] Reason: " + reason); Log.e(TAG, "[View Session] Details: " + details); clearSession(); - new AlertDialog.Builder(context) - .setMessage("Failed to view session") - .show(); + mListener.onSessionMessage("Failed to view session"); } }); } From 546e041d28282d662d84b361fa3a6b77551266fd Mon Sep 17 00:00:00 2001 From: Michael Palmer Date: Mon, 24 Apr 2017 08:58:15 -0400 Subject: [PATCH 3/4] finish merge --- app/src/main/java/com/kitty/geotracker/MeteorController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/kitty/geotracker/MeteorController.java b/app/src/main/java/com/kitty/geotracker/MeteorController.java index 48f8fe7..ca9a5da 100644 --- a/app/src/main/java/com/kitty/geotracker/MeteorController.java +++ b/app/src/main/java/com/kitty/geotracker/MeteorController.java @@ -477,7 +477,7 @@ public void viewSession(final String sessionName) { .findOne(); if (document == null) { - mListener.onSessionMessage("Session cannot be viewed"); + mListener.onSessionMessage("Session cannot be viewed", false); return; } @@ -495,7 +495,7 @@ public void onError(String error, String reason, String details) { Log.e(TAG, "[View Session] Reason: " + reason); Log.e(TAG, "[View Session] Details: " + details); clearSession(); - mListener.onSessionMessage("Failed to view session"); + mListener.onSessionMessage("Failed to view session", false); } }); } From e0578695fe1b4a4c8719c71e9b1e1920091ce03e Mon Sep 17 00:00:00 2001 From: Michael Palmer Date: Mon, 24 Apr 2017 09:01:27 -0400 Subject: [PATCH 4/4] fix logs and comments --- .../java/com/kitty/geotracker/dialogs/ViewSession.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java b/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java index 45bc87c..b959bf2 100644 --- a/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java +++ b/app/src/main/java/com/kitty/geotracker/dialogs/ViewSession.java @@ -48,7 +48,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public void onDestroy() { - Log.d(getClass().getSimpleName(), "OnDestroy: Unsubscribe from " + MeteorController.SUBSCRIPTION_SESSION_LIST); mMeteor.removeCallback(this); super.onDestroy(); } @@ -117,7 +116,7 @@ public void onDataAdded(String collectionName, String documentID, String newValu Document document = collection.getDocument(documentID); boolean active = (boolean) document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_ACTIVE); - // If this session is not active, don't add it to the list + // If this session is active, don't add it to the list if (active) { return; } @@ -156,12 +155,12 @@ public void onDataChanged(String collectionName, String documentID, String updat String title = (String) document.getField(MeteorController.COLLECTION_SESSIONS_COLUMN_TITLE); if (active && !items.contains(title)) { - // Session becomes inactive + // Session becomes active items.remove(title); documentMap.remove(documentID); adapter.notifyDataSetChanged(); } else if (!active && items.contains(title)) { - // Session becomes active + // Session becomes inactive items.add(title); documentMap.put(documentID, title); adapter.notifyDataSetChanged();