diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index d12ae9e..0897082 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -15,6 +15,5 @@
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index ae04c4e..1b83e31 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -64,6 +64,9 @@ dependencies {
implementation 'com.google.android.gms:play-services-maps:19.1.0'
implementation 'com.google.android.gms:play-services-location:21.3.0'
+ //MapBox SDK
+ implementation 'com.mapbox.maps:android:11.11.0'
+
// Glide
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'androidx.preference:preference:1.2.1'
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java
index 3cba800..cf4c670 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java
@@ -15,7 +15,6 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -23,27 +22,26 @@ import androidx.recyclerview.widget.RecyclerView;
import com.aldo.apps.familyhelpers.model.LocationObject;
import com.aldo.apps.familyhelpers.model.User;
import com.aldo.apps.familyhelpers.ui.ActiveShareAdapter;
+import com.aldo.apps.familyhelpers.ui.map.AbstractMap;
+import com.aldo.apps.familyhelpers.ui.map.IMapMovementListener;
+
+import com.aldo.apps.familyhelpers.ui.map.mapbox.MapBoxView;
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
import com.aldo.apps.familyhelpers.workers.LocationHelper;
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
import com.bumptech.glide.Glide;
-import com.google.android.gms.maps.CameraUpdateFactory;
-import com.google.android.gms.maps.GoogleMap;
-import com.google.android.gms.maps.OnMapReadyCallback;
-import com.google.android.gms.maps.SupportMapFragment;
-import com.google.android.gms.maps.model.LatLng;
-import com.google.android.gms.maps.model.MapStyleOptions;
-import com.google.android.gms.maps.model.Marker;
-import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
+import com.mapbox.maps.MapView;
+
+
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
+
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -53,87 +51,80 @@ import io.reactivex.rxjava3.disposables.Disposable;
/**
* Activity showing a Map to display the shared location plus additional information.
*/
-public class ShareLocationActivity extends AppCompatActivity implements OnMapReadyCallback {
+public class ShareLocationActivity extends AppCompatActivity implements IMapMovementListener {
/**
* Tag for debugging purpose.
*/
private static final String TAG = "ShareLocationActivity";
- /**
- * Map containing all markers and the corresponding ID.
- */
- private final Map mMarkerMap = new HashMap<>();
+
/**
* {@link List} of all {@link User}s.
*/
private final List mAllUsers = new ArrayList<>();
- /**
- * The {@link GoogleMap} view.
- */
- private GoogleMap mGmap;
+
/**
* The {@link DatabaseHelper} to read and load data from.
*/
private DatabaseHelper mDbHelper;
+
/**
* The currently logged in {@link FirebaseUser}.
*/
private FirebaseUser mCurrentUser;
+
/**
* The ID of the location updates to listen to.
*/
private String mShareId;
+
/**
* The InfoBox {@link ImageView} showing the user icon.
*/
private ImageView mInfoBoxIcon;
+
/**
* The InfoBox {@link TextView} showing the title.
*/
private TextView mInfoBoxTitle;
+
/**
* The InfoBox {@link TextView} showing the Location information.
*/
private TextView mInfoBoxLocation;
+
/**
* The InfoBox {@link TextView} showing the altitude..
*/
private TextView mInfoBoxAltitude;
+
/**
* The InfoBox {@link TextView} showing the last received speed.
*/
private TextView mInfoBoxSpeed;
+
/**
* The InfoBox {@link TextView} showing the timestamp of the last update.
*/
private TextView mInfoBoxTimeStamp;
+
/**
* The {@link Disposable} holding the subscription to the {@link LocationObject}
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
*/
private Disposable mLocationUpdateSubscription;
+
/**
* The {@link Disposable} holding the subscription to the {@link String}
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
*/
private Disposable mCurrentlyFollowingSubscription;
+
/**
* The {@link Disposable} holding the subscription to {@link User}
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
*/
private Disposable mUserSubscription;
- /**
- * Boolean flag indicating whether the system is currently in night mode or not.
- */
- private boolean mIsNightMode;
- /**
- * Reference to the Google Maps Marker, to update it's position rather than adding a new one.
- */
- private Marker mGmapsMarker;
- /**
- * The ID of who the user is currently following.
- */
- private String mCurrentlyFollowing;
/**
* Displays an error if no active shares.
@@ -150,25 +141,21 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
*/
private FloatingActionButton mRecenterFollowBtn;
- private LocationObject mCurrentlyFollowingLocation;
-
/**
* The {@link ActiveShareAdapter} to populate the activeShare view.
*/
private ActiveShareAdapter mActiveShareAdapter;
- /**
- * Flag indicating whether currently the user wants to be following another user or not.
- */
- private boolean mIsFollowing;
+ private AbstractMap mMap;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_location);
- final SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
- .findFragmentById(R.id.map);
+ mMap = new MapBoxView(findViewById(R.id.mapbox), this);
+ initMapView();
+
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
mInfoBoxIcon = findViewById(R.id.share_location_info_user_icon);
mInfoBoxTitle = findViewById(R.id.share_location_info_title);
@@ -185,7 +172,7 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
mRecenterFollowBtn.setOnClickListener(v -> startFollowingAndCenterCamera());
mActiveShares.setAdapter(mActiveShareAdapter);
- mapFragment.getMapAsync(this);
+
mDbHelper = new DatabaseHelper();
}
@@ -194,8 +181,9 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
super.onResume();
final Configuration configuration = getResources().getConfiguration();
- mIsNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ boolean isNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
+ mMap.applyDayNightMode(isNightMode);
if (mShareId == null) {
mShareId = mCurrentUser.getUid();
@@ -257,32 +245,30 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
* @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
*/
private void handleReceivedLocations(final Map locationObjectMap) {
- if (TextUtils.isEmpty(mCurrentlyFollowing)) {
+ if (TextUtils.isEmpty(mMap.getCurrentlyFollowing())) {
clearInfoBox();
}
+ Log.d(TAG, "handleReceivedLocations() called with: locationObjectMap = [" + locationObjectMap + "]");
// If empty received, remove all markers.
if (locationObjectMap.isEmpty()) {
- for (final Marker marker : mMarkerMap.values()) {
- marker.remove();
- }
- mMarkerMap.clear();
+ Log.d(TAG, "handleReceivedLocations: Noone sharing");
+ mMap.clearAllMarkers();
clearInfoBox();
mNoActiveShares.setVisibility(View.VISIBLE);
mActiveShares.setVisibility(View.GONE);
} else {
+ Log.d(TAG, "handleReceivedLocations: Sharing active");
mNoActiveShares.setVisibility(View.GONE);
mActiveShares.setVisibility(View.VISIBLE);
}
-
- if (mGmapsMarker != null) {
- mGmapsMarker.remove();
- }
//Update the available active shares.
updateActiveShares(locationObjectMap);
// If non-empty received, remove all invalid existing markers
- removeInvalidExistingMarkers(locationObjectMap);
+ if (mMap.removeInvalidExistingMarkers(locationObjectMap)) {
+ clearInfoBox();
+ }
// Update/Insert all existing/new markers
- updateExistingOrCreateValidMarkers(locationObjectMap);
+ updateInfoBox(mMap.updateOrAddCustomMarker(locationObjectMap));
}
/**
@@ -298,72 +284,6 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
mActiveShareAdapter.applyNewData(activeShares);
}
-
- /**
- * Removed all markers that became invalid because the sharing was stopped.
- *
- * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
- */
- private void removeInvalidExistingMarkers(final Map locationObjectMap) {
- final List toBeRemovedKeys = new ArrayList<>();
- for (Map.Entry markerEntry : mMarkerMap.entrySet()) {
- if (!locationObjectMap.containsKey(markerEntry.getKey())) {
- Log.d(TAG, "handleReceivedLocations: Marker not valid anymore, remove");
- markerEntry.getValue().remove();
- toBeRemovedKeys.add(markerEntry.getKey());
- }
- }
- // Remove from the map as well
- for (final String removeKey : toBeRemovedKeys) {
- if (TextUtils.equals(removeKey, mCurrentlyFollowing)) {
- clearInfoBox();
- mCurrentlyFollowing = null;
- mIsFollowing = false;
- mCurrentlyFollowingLocation = null;
- }
- mMarkerMap.remove(removeKey);
- }
- }
-
- /**
- * Helper method to update or create the valid markers on the map. Updates the position of a
- * marker if it is existing before, adds a new marker if nor yet existing.
- *
- * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
- */
- private void updateExistingOrCreateValidMarkers(final Map locationObjectMap) {
- for (final Map.Entry locationSet : locationObjectMap.entrySet()) {
- final LocationObject newReceived = locationSet.getValue();
- final LatLng receivedLocation = new LatLng(newReceived.getLatitude(), newReceived.getLongitude());
- final String sharingId = locationSet.getKey();
- final User locationUser = mDbHelper.getUserForId(sharingId);
- if (mMarkerMap.containsKey(sharingId)) {
- final Marker existing = mMarkerMap.get(sharingId);
- if (existing == null) {
- Log.w(TAG, "updateExistingOrCreateValidMarkers: Marker somehow not existing, skip");
- continue;
- }
- Log.d(TAG, "handleReceivedLocations: Marker still available, update position");
- existing.setPosition(receivedLocation);
-
- } else {
- Log.d(TAG, "handleReceivedLocations: New Marker to be created.");
- final Marker marker = mGmap.addMarker(new MarkerOptions()
- .position(receivedLocation)
- .title(locationUser == null ? "" : locationUser.getDisplayName()));
- mMarkerMap.put(locationSet.getKey(), marker);
- }
- if (TextUtils.equals(mCurrentlyFollowing, sharingId)) {
- final LocationObject locationObject = locationSet.getValue();
- mCurrentlyFollowingLocation = locationObject;
- updateInfoBox(locationObject);
- if (mIsFollowing) {
- startFollowingAndCenterCamera();
- }
- }
- }
- }
-
/**
* Helper method to clear the info box.
*/
@@ -382,8 +302,12 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
* @param locationObject The {@link LocationObject} holding the information.
*/
private void updateInfoBox(final LocationObject locationObject) {
- if (mCurrentlyFollowing != null) {
- final User followed = mDbHelper.getUserForId(mCurrentlyFollowing);
+ if (locationObject == null) {
+ Log.d(TAG, "updateInfoBox: Location is null, skip update...");
+ return;
+ }
+ if (mMap.getCurrentlyFollowing() != null) {
+ final User followed = mDbHelper.getUserForId(mMap.getCurrentlyFollowing());
if (followed != null) {
mInfoBoxTitle.setText(String.format(getString(R.string.share_location_info_title),
followed.getDisplayName()));
@@ -436,19 +360,18 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
}
/**
- * Handlles the callback when the selection of currently sharedd Id changes. Re-Established the
+ * Handles the callback when the selection of currently sharedd Id changes. Re-Established the
* subscription to all locations and updates the info box.
*
* @param shareId The ID of the user to be followed.
*/
private void handleCurrentlyFollowingChanged(final String shareId) {
- Log.d(TAG, "onCreate: Currently selected = [" + shareId + "]");
- mCurrentlyFollowing = shareId;
- mIsFollowing = !TextUtils.isEmpty(mCurrentlyFollowing);
+ Log.d(TAG, "handleCurrentlyFollowingChanged: Currently selected = [" + shareId + "]");
// Re-setup the subscription.
if (mLocationUpdateSubscription != null) {
mLocationUpdateSubscription.dispose();
}
+ mMap.setCurrentlyFollowing(shareId);
mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
.subscribe(this::handleReceivedLocations, this::handleSubscriptionError);
}
@@ -466,43 +389,18 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
* Helper method to start following and recenter the camera on the location followed.
*/
private void startFollowingAndCenterCamera() {
- mIsFollowing = true;
- mGmap.animateCamera(CameraUpdateFactory.newLatLngZoom(
- new LatLng(mCurrentlyFollowingLocation.getLatitude(),
- mCurrentlyFollowingLocation.getLongitude()),
- 15.0f));
+ mMap.startFollowingAndCenterCamera();
mRecenterFollowBtn.setVisibility(View.GONE);
}
- @Override
- public void onMapReady(@NonNull final GoogleMap googleMap) {
- if (googleMap == null) {
- Log.w(TAG, "onMapReady: Map null, cannot continue");
- return;
- }
- mGmap = googleMap;
- if (mIsNightMode) {
- mGmap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style_night));
- } else {
- mGmap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style_day));
- }
- final LatLng defaultHome = new LatLng(DEFAULT_HOME_LATITUDE, DEFAULT_HOME_LONGITUDE);
+ private void initMapView() {
+ mMap.zoomOnDefaultLocation();
+ mMap.displayOwnLocation();
+ mMap.registerMapMovementListener(this);
+ }
- if (mGmapsMarker == null) {
- mGmapsMarker = mGmap.addMarker(new MarkerOptions()
- .position(defaultHome)
- .title("Default - Home"));
- } else {
- mGmapsMarker.setPosition(defaultHome);
- }
- mGmap.setOnCameraMoveStartedListener(reason -> {
- if (GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE == reason
- && !TextUtils.isEmpty(mCurrentlyFollowing)) {
- Log.d(TAG, "onCameraMoveStarted: User moved map, set following to false.");
- mIsFollowing = false;
- mRecenterFollowBtn.setVisibility(View.VISIBLE);
- }
- });
- mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultHome, 8.0f));
+ @Override
+ public void onMapMovementDetected() {
+ mRecenterFollowBtn.setVisibility(View.VISIBLE);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/model/LocationObject.java b/app/src/main/java/com/aldo/apps/familyhelpers/model/LocationObject.java
index e415eb0..f1c6325 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/model/LocationObject.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/model/LocationObject.java
@@ -41,6 +41,11 @@ public class LocationObject {
*/
private long timestamp;
+ /**
+ * Last received bearing.
+ */
+ private float bearing;
+
/**
* Empty C'Tor for database usage.
*/
@@ -57,9 +62,10 @@ public class LocationObject {
* @param altitude Last received altitude.
* @param speed Last received speed.
* @param timestamp Last received timestamp.
+ * @param bearing Last received bearing.
*/
public LocationObject(final String shareId, final double latitude, final double longitude,
- final double altitude, final float speed, final long timestamp) {
+ final double altitude, final float speed, final long timestamp, final float bearing) {
this.shareId = shareId;
this.latitude = latitude;
this.longitude = longitude;
@@ -78,7 +84,7 @@ public class LocationObject {
final FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
return new LocationObject(firebaseUser.getUid(), location.getLatitude(),
location.getLongitude(), location.getAltitude(), location.getSpeed(),
- location.getTime());
+ location.getTime(), location.getBearing());
}
/**
@@ -146,11 +152,20 @@ public class LocationObject {
return timestamp;
}
+ /**
+ * Returns the last received bearing.
+ *
+ * @return The last received bearing.
+ */
+ public float getBearing() {
+ return bearing;
+ }
+
@NonNull
@Override
public String toString() {
return "LocationObject with shareId = [" + shareId + "], lat = [" + latitude
+ "], long = [" + longitude + "], alt = [" + altitude + "], speed = [" + speed
- + "], time = [" + timestamp + "]";
+ + "], time = [" + timestamp + "], bearing = [" + bearing + "]";
}
}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/AbstractMap.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/AbstractMap.java
new file mode 100644
index 0000000..779454f
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/AbstractMap.java
@@ -0,0 +1,152 @@
+package com.aldo.apps.familyhelpers.ui.map;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.aldo.apps.familyhelpers.model.LocationObject;
+import com.aldo.apps.familyhelpers.model.User;
+import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+
+/**
+ *
+ * @param The generic type of MapFragment to be used.
+ */
+public abstract class AbstractMap {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "AbstractMap";
+
+ /**
+ * The {@link DatabaseHelper} to read data from.
+ */
+ protected final DatabaseHelper mDbHelper = new DatabaseHelper();
+
+ /**
+ * The {@link WeakReference} to the calling {@link Context}.
+ */
+ protected final WeakReference mContextRef;
+
+ /**
+ * The ID of who the user is currently following.
+ */
+ protected String mCurrentlyFollowing;
+
+ /**
+ * The {@link LocationObject} of the currently following user.
+ */
+ protected LocationObject mCurrentlyFollowingLocation;
+
+ /**
+ * Listener to be invoked when the map moved.
+ */
+ protected IMapMovementListener mMapMovementListener;
+
+ /**
+ * The MapFragment to be shown.
+ */
+ protected T mMapFragment;
+
+
+ /**
+ * C'Tor.
+ *
+ * @param mapFragment The inflated Map-Fragment.
+ * @param context The calling {@link Context}.
+ */
+ public AbstractMap(final T mapFragment, final Context context) {
+ Log.d(TAG, "AbstractMap() called with: mapFragment = [" + mapFragment + "]");
+ mMapFragment = mapFragment;
+ mContextRef = new WeakReference<>(context);
+ }
+
+ /**
+ * Helper method to display and zoom onto the default location until Map is loaded and location
+ * is received.
+ */
+ public abstract void zoomOnDefaultLocation();
+
+ /**
+ * Helper method to start following and recenter the camera on the location followed.
+ */
+ public abstract void startFollowingAndCenterCamera();
+
+ /**
+ * Helper method to show and display the own location.
+ */
+ public abstract void displayOwnLocation();
+
+ /**
+ * Helper method to clear all markers.
+ */
+ public abstract void clearAllMarkers();
+
+ /**
+ * Helper method to update or create the valid markers on the map. Updates the position of a
+ * marker if it is existing before, adds a new marker if nor yet existing.
+ *
+ * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
+ *
+ * @return The {@link LocationObject} that is currently being followed.
+ */
+ public abstract LocationObject updateOrAddCustomMarker(final Map locationObjectMap);
+
+ /**
+ * Removed all markers that became invalid because the sharing was stopped.
+ *
+ * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
+ *
+ * @return true if currently following was removed too, false otherwise.
+ */
+ public abstract boolean removeInvalidExistingMarkers(final Map locationObjectMap);
+
+
+ /**
+ * Helper method to animate the camera to a specific location.
+ *
+ * @param locationObject The {@link LocationObject} to zoom to.
+ */
+ public abstract void animateCamera(final LocationObject locationObject);
+
+ /**
+ * Helper method to apply day/night mode. As per standard, this is handled via resources directly,
+ * but may be overridden.
+ *
+ * @param isNightMode true if night mode is active, false otherwise.
+ */
+ public void applyDayNightMode(final boolean isNightMode) {
+ // Standard implementation: Do nothing as handled in resources.
+ }
+
+ /**
+ * Sets the ID of who the user is currently following.
+ *
+ * @param currentlyFollowing The ID of who the user is currently following.
+ */
+ public void setCurrentlyFollowing(final String currentlyFollowing) {
+ mCurrentlyFollowing = currentlyFollowing;
+ }
+
+ /**
+ * Returns the ID of who the user is currently following.
+ *
+ * @return The ID of who the user is currently following.
+ */
+ public String getCurrentlyFollowing() {
+ return mCurrentlyFollowing;
+ }
+
+ /**
+ * Registers a {@link IMapMovementListener} to be invoked whenever the user moves the map
+ * to properly show the re-center button.
+ *
+ * @param mapMovementListener The {@link IMapMovementListener} to be invoked.
+ */
+ public void registerMapMovementListener(final IMapMovementListener mapMovementListener) {
+ mMapMovementListener = mapMovementListener;
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/IMapMovementListener.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/IMapMovementListener.java
new file mode 100644
index 0000000..349194f
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/IMapMovementListener.java
@@ -0,0 +1,6 @@
+package com.aldo.apps.familyhelpers.ui.map;
+
+public interface IMapMovementListener {
+
+ void onMapMovementDetected();
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/gmaps/GoogleMapsView.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/gmaps/GoogleMapsView.java
new file mode 100644
index 0000000..800c44d
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/gmaps/GoogleMapsView.java
@@ -0,0 +1,189 @@
+package com.aldo.apps.familyhelpers.ui.map.gmaps;
+
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LATITUDE;
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LONGITUDE;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.model.LocationObject;
+import com.aldo.apps.familyhelpers.model.User;
+import com.aldo.apps.familyhelpers.ui.map.AbstractMap;
+import com.aldo.apps.familyhelpers.ui.map.IMapMovementListener;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.model.CameraPosition;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.MapStyleOptions;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Google Maps specific implementation of the {@link AbstractMap}.
+ */
+public class GoogleMapsView extends AbstractMap {
+
+ /**
+ * Tag for debugging purposes.
+ */
+ private static final String TAG = "GoogleMapsView";
+
+ /**
+ * The zoom level to be applied per default.
+ */
+ private static final float DEFAULT_ZOOM_LEVEL = 15.0f;
+
+ /**
+ * The default tilt level.
+ */
+ private static final float DEFAULT_TILT = 0.0f;
+
+ /**
+ * Map containing all markers and the corresponding ID.
+ */
+ private final Map mMarkerMap = new HashMap<>();
+ /**
+ * Reference to the Google Maps Marker, to update it's position rather than adding a new one.
+ */
+ private Marker mGmapsMarker;
+
+ /**
+ * C'Tor.
+ *
+ * @param gMap The inflated Map-Fragment.
+ * @param context The calling {@link Context}.
+ */
+ public GoogleMapsView(final GoogleMap gMap, final Context context) {
+ super(gMap, context);
+ }
+
+ @Override
+ public void zoomOnDefaultLocation() {
+ final LatLng defaultHome = new LatLng(DEFAULT_HOME_LATITUDE, DEFAULT_HOME_LONGITUDE);
+
+ if (mGmapsMarker == null) {
+ mGmapsMarker = mMapFragment.addMarker(new MarkerOptions()
+ .position(defaultHome)
+ .title("Default - Home"));
+ } else {
+ mGmapsMarker.setPosition(defaultHome);
+ }
+ mMapFragment.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultHome, DEFAULT_ZOOM_LEVEL));
+ }
+
+ @Override
+ public void startFollowingAndCenterCamera() {
+ animateCamera(mCurrentlyFollowingLocation);
+ }
+
+ @Override
+ public void displayOwnLocation() {
+ // Do nothing for GMaps
+ }
+
+ @Override
+ public LocationObject updateOrAddCustomMarker(final Map locationObjectMap) {
+ for (final Map.Entry locationSet : locationObjectMap.entrySet()) {
+ final LocationObject newReceived = locationSet.getValue();
+ final LatLng receivedLocation = new LatLng(newReceived.getLatitude(), newReceived.getLongitude());
+ final String sharingId = locationSet.getKey();
+ final User locationUser = mDbHelper.getUserForId(sharingId);
+ if (mMarkerMap.containsKey(sharingId)) {
+ final Marker existing = mMarkerMap.get(sharingId);
+ if (existing == null) {
+ Log.w(TAG, "updateOrAddCustomMarker: Marker somehow not existing, skip");
+ continue;
+ }
+ Log.d(TAG, "updateOrAddCustomMarker: Marker still available, update position");
+ existing.setPosition(receivedLocation);
+
+ } else {
+ Log.d(TAG, "updateOrAddCustomMarker: New Marker to be created.");
+ final Marker marker = mMapFragment.addMarker(new MarkerOptions()
+ .position(receivedLocation)
+ .title(locationUser == null ? "" : locationUser.getDisplayName()));
+ mMarkerMap.put(locationSet.getKey(), marker);
+ }
+ if (TextUtils.equals(mCurrentlyFollowing, sharingId)) {
+ mCurrentlyFollowingLocation = locationSet.getValue();
+ if (mCurrentlyFollowing != null) {
+ startFollowingAndCenterCamera();
+ }
+ }
+ }
+ return mCurrentlyFollowingLocation;
+ }
+
+ @Override
+ public void clearAllMarkers() {
+ for (final Marker marker : mMarkerMap.values()) {
+ marker.remove();
+ }
+ mMarkerMap.clear();
+ }
+
+ @Override
+ public boolean removeInvalidExistingMarkers(final Map locationObjectMap) {
+ final List toBeRemovedKeys = new ArrayList<>();
+ for (Map.Entry markerEntry : mMarkerMap.entrySet()) {
+ if (!locationObjectMap.containsKey(markerEntry.getKey())) {
+ Log.d(TAG, "removeInvalidExistingMarkers: Marker not valid anymore, remove");
+ markerEntry.getValue().remove();
+ toBeRemovedKeys.add(markerEntry.getKey());
+ }
+ }
+ // Remove from the map as well
+ for (final String removeKey : toBeRemovedKeys) {
+ if (TextUtils.equals(removeKey, mCurrentlyFollowing)) {
+ mCurrentlyFollowing = null;
+ mCurrentlyFollowingLocation = null;
+ }
+ mMarkerMap.remove(removeKey);
+ }
+ return mCurrentlyFollowing == null;
+ }
+
+ @Override
+ public void applyDayNightMode(final boolean isNightMode) {
+ final Context context = mContextRef.get();
+ if (context == null) {
+ Log.w(TAG, "applyDayNightMode: Context is null, cannot continue");
+ return;
+ }
+ if (isNightMode) {
+ mMapFragment.setMapStyle(MapStyleOptions.loadRawResourceStyle(context, R.raw.map_style_night));
+ } else {
+ mMapFragment.setMapStyle(MapStyleOptions.loadRawResourceStyle(context, R.raw.map_style_day));
+ }
+
+ }
+
+ @Override
+ public void animateCamera(final LocationObject locationObject) {
+ mMapFragment.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(
+ new LatLng(locationObject.getLatitude(), locationObject.getLongitude()),
+ DEFAULT_ZOOM_LEVEL,
+ DEFAULT_TILT,
+ locationObject.getBearing())));
+ }
+
+ @Override
+ public void registerMapMovementListener(final IMapMovementListener mapMovementListener) {
+ super.registerMapMovementListener(mapMovementListener);
+ mMapFragment.setOnCameraMoveStartedListener(reason -> {
+ if (GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE == reason
+ && !TextUtils.isEmpty(mCurrentlyFollowing)) {
+ Log.d(TAG, "onCameraMoveStarted: User moved map...");
+ mMapMovementListener.onMapMovementDetected();
+ }
+ });
+
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/mapbox/MapBoxView.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/mapbox/MapBoxView.java
new file mode 100644
index 0000000..767e112
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/map/mapbox/MapBoxView.java
@@ -0,0 +1,276 @@
+package com.aldo.apps.familyhelpers.ui.map.mapbox;
+
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LATITUDE;
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LONGITUDE;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.aldo.apps.familyhelpers.model.LocationObject;
+import com.aldo.apps.familyhelpers.model.User;
+import com.aldo.apps.familyhelpers.ui.map.AbstractMap;
+import com.aldo.apps.familyhelpers.ui.map.IMapMovementListener;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.target.CustomTarget;
+import com.bumptech.glide.request.transition.Transition;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.mapbox.android.gestures.MoveGestureDetector;
+import com.mapbox.geojson.Point;
+import com.mapbox.maps.CameraOptions;
+import com.mapbox.maps.MapView;
+import com.mapbox.maps.plugin.Plugin;
+import com.mapbox.maps.plugin.PuckBearing;
+import com.mapbox.maps.plugin.annotation.AnnotationConfig;
+import com.mapbox.maps.plugin.annotation.AnnotationManager;
+import com.mapbox.maps.plugin.annotation.AnnotationPlugin;
+import com.mapbox.maps.plugin.annotation.AnnotationType;
+import com.mapbox.maps.plugin.annotation.generated.PointAnnotation;
+import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager;
+import com.mapbox.maps.plugin.annotation.generated.PointAnnotationOptions;
+import com.mapbox.maps.plugin.gestures.GesturesPlugin;
+import com.mapbox.maps.plugin.gestures.OnMoveListener;
+import com.mapbox.maps.plugin.locationcomponent.LocationComponentPlugin;
+import com.mapbox.maps.plugin.viewport.ViewportPlugin;
+import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateOptions;
+import com.mapbox.maps.plugin.viewport.state.FollowPuckViewportState;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MapBox specific implementation of the {@link AbstractMap}.
+ */
+public class MapBoxView extends AbstractMap implements OnMoveListener {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "MapBoxView";
+
+ /**
+ * The default zoom level.
+ */
+ private static final double DEFAULT_ZOOM_LEVEL = 15.0;
+
+ /**
+ * {@link Map} holding the mapping of sharing ID to {@link PointAnnotation}.
+ */
+ private final Map mMarkerMap = new HashMap<>();
+
+ /**
+ * The {@link LocationComponentPlugin} to enable specific UI features.
+ */
+ private final LocationComponentPlugin mLocCompPlugin;
+
+ /**
+ * The {@link ViewportPlugin} to follow own location.
+ */
+ private final ViewportPlugin mViewportPlugin;
+
+ /**
+ * The {@link GesturesPlugin} to listen to ptential swipes.
+ */
+ private final GesturesPlugin mGesturesPlugin;
+
+ /**
+ * The {@link AnnotationPlugin} to add/remove markers.
+ */
+ private final AnnotationPlugin mAnnotationPlugin;
+
+ /**
+ * The {@link AnnotationManager} to actually display markers.
+ */
+ private final PointAnnotationManager mAnnotationManager;
+
+ /**
+ * The ID of the currently logged in user.
+ */
+ private final String mCurrentUserId;
+
+ /**
+ * C'Tor.
+ *
+ * @param mapView The inflated Map-Fragment.
+ * @param context The calling {@link Context}.
+ */
+ public MapBoxView(final MapView mapView, final Context context) {
+ super(mapView, context);
+ mLocCompPlugin = mapView.getPlugin(Plugin.MAPBOX_LOCATION_COMPONENT_PLUGIN_ID);
+ mViewportPlugin = mapView.getPlugin(Plugin.MAPBOX_VIEWPORT_PLUGIN_ID);
+ mGesturesPlugin = mapView.getPlugin(Plugin.MAPBOX_GESTURES_PLUGIN_ID);
+ mAnnotationPlugin = mapView.getPlugin(Plugin.MAPBOX_ANNOTATION_PLUGIN_ID);
+ mCurrentUserId = FirebaseAuth.getInstance().getCurrentUser().getUid();
+ if (mAnnotationPlugin != null) {
+ mAnnotationManager = (PointAnnotationManager) mAnnotationPlugin.createAnnotationManager(
+ AnnotationType.PointAnnotation, new AnnotationConfig());
+ } else {
+ Log.d(TAG, "MapBoxView: AnnotationManager is null, no markers will be set.");
+ mAnnotationManager = null;
+ }
+ if (mLocCompPlugin != null) {
+ mLocCompPlugin.setPulsingEnabled(true);
+ }
+ }
+
+ @Override
+ public void zoomOnDefaultLocation() {
+ animateCamera(mCurrentlyFollowingLocation);
+ }
+
+ @Override
+ public void startFollowingAndCenterCamera() {
+ if (mCurrentlyFollowingLocation == null || TextUtils.equals(mCurrentlyFollowingLocation.getShareId(), mCurrentUserId)) {
+ displayOwnLocation();
+ } else {
+ animateCamera(mCurrentlyFollowingLocation);
+ }
+ }
+
+ @Override
+ public void displayOwnLocation() {
+ if (mViewportPlugin != null) {
+ final FollowPuckViewportState state = mViewportPlugin.makeFollowPuckViewportState(new FollowPuckViewportStateOptions.Builder()
+ .zoom(DEFAULT_ZOOM_LEVEL)
+ .build());
+ mViewportPlugin.transitionTo(state, mViewportPlugin.getDefaultTransition(),
+ done -> Log.d(TAG, "Movement finished = [" + done + "]"));
+ }
+ }
+
+ @Override
+ public void clearAllMarkers() {
+ if (mAnnotationManager != null) {
+ mAnnotationManager.deleteAll();
+ mMarkerMap.clear();
+ }
+ }
+
+ @Override
+ public LocationObject updateOrAddCustomMarker(final Map locationObjectMap) {
+ final Context context = mContextRef.get();
+ if (context == null) {
+ Log.w(TAG, "updateOrAddCustomMarker: No valid context, cannot continue.");
+ return null;
+ }
+ for (final Map.Entry locationSet : locationObjectMap.entrySet()) {
+ final LocationObject newReceived = locationSet.getValue();
+ final String sharingId = locationSet.getKey();
+ final User locationUser = mDbHelper.getUserForId(sharingId);
+
+ if (mMarkerMap.containsKey(sharingId)) {
+ final PointAnnotation existing = mMarkerMap.get(sharingId);
+ if (existing == null) {
+ Log.w(TAG, "updateOrAddCustomMarker: Marker somehow not existing, skip");
+ continue;
+ }
+ Log.d(TAG, "updateOrAddCustomMarker: Marker still available, update position");
+ existing.setPoint(Point.fromLngLat(newReceived.getLongitude(), newReceived.getLatitude()));
+ }else if (TextUtils.equals(locationUser.getuId(), mCurrentUserId)) {
+ Log.d(TAG, "updateOrAddCustomMarker: Marker to be added is from own user, no need to add");
+ } else {
+ Log.d(TAG, "updateOrAddCustomMarker: New Marker to be created.");
+ Glide.with(context)
+ .asBitmap()
+ .circleCrop()
+ .load(locationUser.getPhotoUrl())
+ .into(new CustomTarget() {
+ @Override
+ public void onResourceReady(@NonNull final Bitmap resource,
+ @Nullable final Transition super Bitmap> transition) {
+ final PointAnnotationOptions newMarker = new PointAnnotationOptions()
+ .withPoint(Point.fromLngLat(newReceived.getLongitude(), newReceived.getLatitude()))
+ .withIconImage(resource);
+ final PointAnnotation pointAnnotation = mAnnotationManager.create(newMarker);
+ mMarkerMap.put(sharingId, pointAnnotation);
+ }
+
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {
+
+ }
+ });
+ }
+ if (TextUtils.equals(mCurrentlyFollowing, sharingId)) {
+ mCurrentlyFollowingLocation = locationSet.getValue();
+ if (mCurrentlyFollowing != null) {
+ startFollowingAndCenterCamera();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean removeInvalidExistingMarkers(final Map locationObjectMap) {
+ final List toBeRemovedKeys = new ArrayList<>();
+ for (Map.Entry markerEntry : mMarkerMap.entrySet()) {
+ if (!locationObjectMap.containsKey(markerEntry.getKey())) {
+ Log.d(TAG, "removeInvalidExistingMarkers: Marker not valid anymore, remove");
+ mAnnotationManager.delete(markerEntry.getValue());
+ toBeRemovedKeys.add(markerEntry.getKey());
+ }
+ }
+ // Remove from the map as well
+ for (final String removeKey : toBeRemovedKeys) {
+ if (TextUtils.equals(removeKey, mCurrentlyFollowing)) {
+ mCurrentlyFollowing = null;
+ mCurrentlyFollowingLocation = null;
+ }
+ mMarkerMap.remove(removeKey);
+ }
+ return mCurrentlyFollowing == null;
+ }
+
+ @Override
+ public void animateCamera(final LocationObject locationObject) {
+ if (locationObject == null) {
+ Log.w(TAG, "animateCamera: Invalid LocationObject, skip update");
+ return;
+ }
+ mMapFragment.getMapboxMap().setCamera(
+ new CameraOptions.Builder()
+ .center(Point.fromLngLat(locationObject.getLongitude(), locationObject.getLatitude()))
+ .zoom(DEFAULT_ZOOM_LEVEL)
+ .bearing((double) locationObject.getBearing())
+ .build()
+ );
+ }
+
+ @Override
+ public void registerMapMovementListener(IMapMovementListener mapMovementListener) {
+ super.registerMapMovementListener(mapMovementListener);
+ if (mGesturesPlugin != null) {
+ mGesturesPlugin.addOnMoveListener(this);
+ }
+ }
+
+ @Override
+ public boolean onMove(@NonNull final MoveGestureDetector moveGestureDetector) {
+ return false;
+ }
+
+ @Override
+ public void onMoveBegin(@NonNull final MoveGestureDetector moveGestureDetector) {
+ if (mMapMovementListener != null) {
+ mMapMovementListener.onMapMovementDetected();
+ }
+ }
+
+ @Override
+ public void onMoveEnd(@NonNull final MoveGestureDetector moveGestureDetector) {
+ // Do nothing.
+ }
+}
diff --git a/app/src/main/res/drawable/red_marker.png b/app/src/main/res/drawable/red_marker.png
new file mode 100644
index 0000000..be782e1
Binary files /dev/null and b/app/src/main/res/drawable/red_marker.png differ
diff --git a/app/src/main/res/layout/activity_share_location.xml b/app/src/main/res/layout/activity_share_location.xml
index e78419e..20245a9 100644
--- a/app/src/main/res/layout/activity_share_location.xml
+++ b/app/src/main/res/layout/activity_share_location.xml
@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ xmlns:mapbox="http://schemas.android.com/apk/res-auto"
tools:context=".ShareLocationActivity">
+
+
+
+
@color/md_theme_surfaceContainerHigh
- @color/md_theme_surfaceContainerHighest
+ mapbox://styles/mapbox/navigation-night-v1
+ mapbox://styles/mapbox/navigation-day-v1
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 2ed6ba7..e42100d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,5 +9,8 @@ allprojects {
google()
mavenCentral()
maven { url "https://jitpack.io" }
+ maven {
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
+ }
}
}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 12ab20f..782ae6e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -10,6 +10,9 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven {
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
+ }
}
}