Compare commits
1 Commits
master
...
gmaps_to_o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e482093832 |
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -15,6 +15,5 @@
|
|||||||
<option name="resolveExternalAnnotations" value="false" />
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
<option name="offlineMode" value="true" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -64,6 +64,9 @@ dependencies {
|
|||||||
implementation 'com.google.android.gms:play-services-maps:19.1.0'
|
implementation 'com.google.android.gms:play-services-maps:19.1.0'
|
||||||
implementation 'com.google.android.gms:play-services-location:21.3.0'
|
implementation 'com.google.android.gms:play-services-location:21.3.0'
|
||||||
|
|
||||||
|
//MapBox SDK
|
||||||
|
implementation 'com.mapbox.maps:android:11.11.0'
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import android.view.View;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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.LocationObject;
|
||||||
import com.aldo.apps.familyhelpers.model.User;
|
import com.aldo.apps.familyhelpers.model.User;
|
||||||
import com.aldo.apps.familyhelpers.ui.ActiveShareAdapter;
|
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.DatabaseHelper;
|
||||||
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
||||||
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
|
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
|
||||||
import com.bumptech.glide.Glide;
|
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.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
|
|
||||||
|
import com.mapbox.maps.MapView;
|
||||||
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
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.
|
* 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.
|
* Tag for debugging purpose.
|
||||||
*/
|
*/
|
||||||
private static final String TAG = "ShareLocationActivity";
|
private static final String TAG = "ShareLocationActivity";
|
||||||
/**
|
|
||||||
* Map containing all markers and the corresponding ID.
|
|
||||||
*/
|
|
||||||
private final Map<String, Marker> mMarkerMap = new HashMap<>();
|
|
||||||
/**
|
/**
|
||||||
* {@link List} of all {@link User}s.
|
* {@link List} of all {@link User}s.
|
||||||
*/
|
*/
|
||||||
private final List<User> mAllUsers = new ArrayList<>();
|
private final List<User> mAllUsers = new ArrayList<>();
|
||||||
/**
|
|
||||||
* The {@link GoogleMap} view.
|
|
||||||
*/
|
|
||||||
private GoogleMap mGmap;
|
|
||||||
/**
|
/**
|
||||||
* The {@link DatabaseHelper} to read and load data from.
|
* The {@link DatabaseHelper} to read and load data from.
|
||||||
*/
|
*/
|
||||||
private DatabaseHelper mDbHelper;
|
private DatabaseHelper mDbHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently logged in {@link FirebaseUser}.
|
* The currently logged in {@link FirebaseUser}.
|
||||||
*/
|
*/
|
||||||
private FirebaseUser mCurrentUser;
|
private FirebaseUser mCurrentUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the location updates to listen to.
|
* The ID of the location updates to listen to.
|
||||||
*/
|
*/
|
||||||
private String mShareId;
|
private String mShareId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link ImageView} showing the user icon.
|
* The InfoBox {@link ImageView} showing the user icon.
|
||||||
*/
|
*/
|
||||||
private ImageView mInfoBoxIcon;
|
private ImageView mInfoBoxIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the title.
|
* The InfoBox {@link TextView} showing the title.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxTitle;
|
private TextView mInfoBoxTitle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the Location information.
|
* The InfoBox {@link TextView} showing the Location information.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxLocation;
|
private TextView mInfoBoxLocation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the altitude..
|
* The InfoBox {@link TextView} showing the altitude..
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxAltitude;
|
private TextView mInfoBoxAltitude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the last received speed.
|
* The InfoBox {@link TextView} showing the last received speed.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxSpeed;
|
private TextView mInfoBoxSpeed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InfoBox {@link TextView} showing the timestamp of the last update.
|
* The InfoBox {@link TextView} showing the timestamp of the last update.
|
||||||
*/
|
*/
|
||||||
private TextView mInfoBoxTimeStamp;
|
private TextView mInfoBoxTimeStamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Disposable} holding the subscription to the {@link LocationObject}
|
* The {@link Disposable} holding the subscription to the {@link LocationObject}
|
||||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
||||||
*/
|
*/
|
||||||
private Disposable mLocationUpdateSubscription;
|
private Disposable mLocationUpdateSubscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Disposable} holding the subscription to the {@link String}
|
* The {@link Disposable} holding the subscription to the {@link String}
|
||||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
|
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
|
||||||
*/
|
*/
|
||||||
private Disposable mCurrentlyFollowingSubscription;
|
private Disposable mCurrentlyFollowingSubscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Disposable} holding the subscription to {@link User}
|
* The {@link Disposable} holding the subscription to {@link User}
|
||||||
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
* {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
|
||||||
*/
|
*/
|
||||||
private Disposable mUserSubscription;
|
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.
|
* Displays an error if no active shares.
|
||||||
@@ -150,25 +141,21 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
*/
|
*/
|
||||||
private FloatingActionButton mRecenterFollowBtn;
|
private FloatingActionButton mRecenterFollowBtn;
|
||||||
|
|
||||||
private LocationObject mCurrentlyFollowingLocation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ActiveShareAdapter} to populate the activeShare view.
|
* The {@link ActiveShareAdapter} to populate the activeShare view.
|
||||||
*/
|
*/
|
||||||
private ActiveShareAdapter mActiveShareAdapter;
|
private ActiveShareAdapter mActiveShareAdapter;
|
||||||
|
|
||||||
/**
|
private AbstractMap<MapView> mMap;
|
||||||
* Flag indicating whether currently the user wants to be following another user or not.
|
|
||||||
*/
|
|
||||||
private boolean mIsFollowing;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_share_location);
|
setContentView(R.layout.activity_share_location);
|
||||||
|
|
||||||
final SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
|
mMap = new MapBoxView(findViewById(R.id.mapbox), this);
|
||||||
.findFragmentById(R.id.map);
|
initMapView();
|
||||||
|
|
||||||
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
mInfoBoxIcon = findViewById(R.id.share_location_info_user_icon);
|
mInfoBoxIcon = findViewById(R.id.share_location_info_user_icon);
|
||||||
mInfoBoxTitle = findViewById(R.id.share_location_info_title);
|
mInfoBoxTitle = findViewById(R.id.share_location_info_title);
|
||||||
@@ -185,7 +172,7 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
|
|
||||||
mRecenterFollowBtn.setOnClickListener(v -> startFollowingAndCenterCamera());
|
mRecenterFollowBtn.setOnClickListener(v -> startFollowingAndCenterCamera());
|
||||||
mActiveShares.setAdapter(mActiveShareAdapter);
|
mActiveShares.setAdapter(mActiveShareAdapter);
|
||||||
mapFragment.getMapAsync(this);
|
|
||||||
mDbHelper = new DatabaseHelper();
|
mDbHelper = new DatabaseHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,8 +181,9 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
final Configuration configuration = getResources().getConfiguration();
|
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;
|
== Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
mMap.applyDayNightMode(isNightMode);
|
||||||
|
|
||||||
if (mShareId == null) {
|
if (mShareId == null) {
|
||||||
mShareId = mCurrentUser.getUid();
|
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.
|
* @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
|
||||||
*/
|
*/
|
||||||
private void handleReceivedLocations(final Map<String, LocationObject> locationObjectMap) {
|
private void handleReceivedLocations(final Map<String, LocationObject> locationObjectMap) {
|
||||||
if (TextUtils.isEmpty(mCurrentlyFollowing)) {
|
if (TextUtils.isEmpty(mMap.getCurrentlyFollowing())) {
|
||||||
clearInfoBox();
|
clearInfoBox();
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "handleReceivedLocations() called with: locationObjectMap = [" + locationObjectMap + "]");
|
||||||
// If empty received, remove all markers.
|
// If empty received, remove all markers.
|
||||||
if (locationObjectMap.isEmpty()) {
|
if (locationObjectMap.isEmpty()) {
|
||||||
for (final Marker marker : mMarkerMap.values()) {
|
Log.d(TAG, "handleReceivedLocations: Noone sharing");
|
||||||
marker.remove();
|
mMap.clearAllMarkers();
|
||||||
}
|
|
||||||
mMarkerMap.clear();
|
|
||||||
clearInfoBox();
|
clearInfoBox();
|
||||||
mNoActiveShares.setVisibility(View.VISIBLE);
|
mNoActiveShares.setVisibility(View.VISIBLE);
|
||||||
mActiveShares.setVisibility(View.GONE);
|
mActiveShares.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
|
Log.d(TAG, "handleReceivedLocations: Sharing active");
|
||||||
mNoActiveShares.setVisibility(View.GONE);
|
mNoActiveShares.setVisibility(View.GONE);
|
||||||
mActiveShares.setVisibility(View.VISIBLE);
|
mActiveShares.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mGmapsMarker != null) {
|
|
||||||
mGmapsMarker.remove();
|
|
||||||
}
|
|
||||||
//Update the available active shares.
|
//Update the available active shares.
|
||||||
updateActiveShares(locationObjectMap);
|
updateActiveShares(locationObjectMap);
|
||||||
// If non-empty received, remove all invalid existing markers
|
// If non-empty received, remove all invalid existing markers
|
||||||
removeInvalidExistingMarkers(locationObjectMap);
|
if (mMap.removeInvalidExistingMarkers(locationObjectMap)) {
|
||||||
|
clearInfoBox();
|
||||||
|
}
|
||||||
// Update/Insert all existing/new markers
|
// 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);
|
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<String, LocationObject> locationObjectMap) {
|
|
||||||
final List<String> toBeRemovedKeys = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, Marker> 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<String, LocationObject> locationObjectMap) {
|
|
||||||
for (final Map.Entry<String, LocationObject> 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.
|
* 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.
|
* @param locationObject The {@link LocationObject} holding the information.
|
||||||
*/
|
*/
|
||||||
private void updateInfoBox(final LocationObject locationObject) {
|
private void updateInfoBox(final LocationObject locationObject) {
|
||||||
if (mCurrentlyFollowing != null) {
|
if (locationObject == null) {
|
||||||
final User followed = mDbHelper.getUserForId(mCurrentlyFollowing);
|
Log.d(TAG, "updateInfoBox: Location is null, skip update...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mMap.getCurrentlyFollowing() != null) {
|
||||||
|
final User followed = mDbHelper.getUserForId(mMap.getCurrentlyFollowing());
|
||||||
if (followed != null) {
|
if (followed != null) {
|
||||||
mInfoBoxTitle.setText(String.format(getString(R.string.share_location_info_title),
|
mInfoBoxTitle.setText(String.format(getString(R.string.share_location_info_title),
|
||||||
followed.getDisplayName()));
|
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.
|
* subscription to all locations and updates the info box.
|
||||||
*
|
*
|
||||||
* @param shareId The ID of the user to be followed.
|
* @param shareId The ID of the user to be followed.
|
||||||
*/
|
*/
|
||||||
private void handleCurrentlyFollowingChanged(final String shareId) {
|
private void handleCurrentlyFollowingChanged(final String shareId) {
|
||||||
Log.d(TAG, "onCreate: Currently selected = [" + shareId + "]");
|
Log.d(TAG, "handleCurrentlyFollowingChanged: Currently selected = [" + shareId + "]");
|
||||||
mCurrentlyFollowing = shareId;
|
|
||||||
mIsFollowing = !TextUtils.isEmpty(mCurrentlyFollowing);
|
|
||||||
// Re-setup the subscription.
|
// Re-setup the subscription.
|
||||||
if (mLocationUpdateSubscription != null) {
|
if (mLocationUpdateSubscription != null) {
|
||||||
mLocationUpdateSubscription.dispose();
|
mLocationUpdateSubscription.dispose();
|
||||||
}
|
}
|
||||||
|
mMap.setCurrentlyFollowing(shareId);
|
||||||
mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
|
mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
|
||||||
.subscribe(this::handleReceivedLocations, this::handleSubscriptionError);
|
.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.
|
* Helper method to start following and recenter the camera on the location followed.
|
||||||
*/
|
*/
|
||||||
private void startFollowingAndCenterCamera() {
|
private void startFollowingAndCenterCamera() {
|
||||||
mIsFollowing = true;
|
mMap.startFollowingAndCenterCamera();
|
||||||
mGmap.animateCamera(CameraUpdateFactory.newLatLngZoom(
|
|
||||||
new LatLng(mCurrentlyFollowingLocation.getLatitude(),
|
|
||||||
mCurrentlyFollowingLocation.getLongitude()),
|
|
||||||
15.0f));
|
|
||||||
mRecenterFollowBtn.setVisibility(View.GONE);
|
mRecenterFollowBtn.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void initMapView() {
|
||||||
public void onMapReady(@NonNull final GoogleMap googleMap) {
|
mMap.zoomOnDefaultLocation();
|
||||||
if (googleMap == null) {
|
mMap.displayOwnLocation();
|
||||||
Log.w(TAG, "onMapReady: Map null, cannot continue");
|
mMap.registerMapMovementListener(this);
|
||||||
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);
|
|
||||||
|
|
||||||
if (mGmapsMarker == null) {
|
@Override
|
||||||
mGmapsMarker = mGmap.addMarker(new MarkerOptions()
|
public void onMapMovementDetected() {
|
||||||
.position(defaultHome)
|
mRecenterFollowBtn.setVisibility(View.VISIBLE);
|
||||||
.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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,11 @@ public class LocationObject {
|
|||||||
*/
|
*/
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last received bearing.
|
||||||
|
*/
|
||||||
|
private float bearing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty C'Tor for database usage.
|
* Empty C'Tor for database usage.
|
||||||
*/
|
*/
|
||||||
@@ -57,9 +62,10 @@ public class LocationObject {
|
|||||||
* @param altitude Last received altitude.
|
* @param altitude Last received altitude.
|
||||||
* @param speed Last received speed.
|
* @param speed Last received speed.
|
||||||
* @param timestamp Last received timestamp.
|
* @param timestamp Last received timestamp.
|
||||||
|
* @param bearing Last received bearing.
|
||||||
*/
|
*/
|
||||||
public LocationObject(final String shareId, final double latitude, final double longitude,
|
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.shareId = shareId;
|
||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
@@ -78,7 +84,7 @@ public class LocationObject {
|
|||||||
final FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
|
final FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
return new LocationObject(firebaseUser.getUid(), location.getLatitude(),
|
return new LocationObject(firebaseUser.getUid(), location.getLatitude(),
|
||||||
location.getLongitude(), location.getAltitude(), location.getSpeed(),
|
location.getLongitude(), location.getAltitude(), location.getSpeed(),
|
||||||
location.getTime());
|
location.getTime(), location.getBearing());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,11 +152,20 @@ public class LocationObject {
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last received bearing.
|
||||||
|
*
|
||||||
|
* @return The last received bearing.
|
||||||
|
*/
|
||||||
|
public float getBearing() {
|
||||||
|
return bearing;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "LocationObject with shareId = [" + shareId + "], lat = [" + latitude
|
return "LocationObject with shareId = [" + shareId + "], lat = [" + latitude
|
||||||
+ "], long = [" + longitude + "], alt = [" + altitude + "], speed = [" + speed
|
+ "], long = [" + longitude + "], alt = [" + altitude + "], speed = [" + speed
|
||||||
+ "], time = [" + timestamp + "]";
|
+ "], time = [" + timestamp + "], bearing = [" + bearing + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 <T> The generic type of MapFragment to be used.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMap<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<Context> 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<String, LocationObject> 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<String, LocationObject> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.aldo.apps.familyhelpers.ui.map;
|
||||||
|
|
||||||
|
public interface IMapMovementListener {
|
||||||
|
|
||||||
|
void onMapMovementDetected();
|
||||||
|
}
|
||||||
@@ -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<GoogleMap> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String, Marker> 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<String, LocationObject> locationObjectMap) {
|
||||||
|
for (final Map.Entry<String, LocationObject> 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<String, LocationObject> locationObjectMap) {
|
||||||
|
final List<String> toBeRemovedKeys = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Marker> 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<MapView> 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<String, PointAnnotation> 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<String, LocationObject> locationObjectMap) {
|
||||||
|
final Context context = mContextRef.get();
|
||||||
|
if (context == null) {
|
||||||
|
Log.w(TAG, "updateOrAddCustomMarker: No valid context, cannot continue.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (final Map.Entry<String, LocationObject> 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<Bitmap>() {
|
||||||
|
@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<String, LocationObject> locationObjectMap) {
|
||||||
|
final List<String> toBeRemovedKeys = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, PointAnnotation> 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable/red_marker.png
Normal file
BIN
app/src/main/res/drawable/red_marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -4,6 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:mapbox="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".ShareLocationActivity">
|
tools:context=".ShareLocationActivity">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
@@ -36,26 +37,39 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:src="@android:drawable/ic_menu_compass"
|
android:src="@android:drawable/ic_menu_compass"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/map"
|
app:layout_constraintBottom_toBottomOf="@id/mapbox"
|
||||||
app:layout_constraintEnd_toEndOf="@id/map"
|
app:layout_constraintEnd_toEndOf="@id/mapbox"
|
||||||
android:layout_marginBottom="25dp"
|
android:layout_marginBottom="25dp"
|
||||||
android:layout_marginEnd="25dp"
|
android:layout_marginEnd="25dp"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<com.mapbox.maps.MapView
|
||||||
|
android:id="@+id/mapbox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/active_share_container"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/share_location_info_box"
|
||||||
|
app:mapbox_locationComponentEnabled="true"
|
||||||
|
app:mapbox_locationComponentPuckBearing="course"
|
||||||
|
mapbox:mapbox_styleUri="@string/map_box_style_uri" />
|
||||||
|
|
||||||
|
<!--
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/map"
|
android:id="@+id/map"
|
||||||
android:name="com.google.android.gms.maps.SupportMapFragment"
|
android:name="com.google.android.gms.maps.SupportMapFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/active_share_container"
|
app:layout_constraintTop_toBottomOf="@id/active_share_container"
|
||||||
app:layout_constraintBottom_toTopOf="@id/share_location_info_box"/>
|
app:layout_constraintBottom_toTopOf="@id/share_location_info_box"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
-->
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
android:id="@+id/switch_start_stop_sharing_location"
|
android:id="@+id/switch_start_stop_sharing_location"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="@id/map"
|
app:layout_constraintTop_toTopOf="@id/mapbox"
|
||||||
app:layout_constraintEnd_toEndOf="@id/map"
|
app:layout_constraintEnd_toEndOf="@id/mapbox"
|
||||||
android:text="@string/share_location_toggle_button"/>
|
android:text="@string/share_location_toggle_button"/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
<item name="colorSurfaceContainerHigh">@color/md_theme_surfaceContainerHigh</item>
|
<item name="colorSurfaceContainerHigh">@color/md_theme_surfaceContainerHigh</item>
|
||||||
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest</item>
|
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest</item>
|
||||||
</style>
|
</style>
|
||||||
|
<string name="map_box_style_uri" translatable="false">mapbox://styles/mapbox/navigation-night-v1</string>
|
||||||
|
|
||||||
<style name="HelperGroupTileStyke" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="HelperGroupTileStyke" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<item name="colorPrimary">@color/helperGroupTileBackground_NightMode</item>
|
<item name="colorPrimary">@color/helperGroupTileBackground_NightMode</item>
|
||||||
|
|||||||
4
app/src/main/res/values/mapbox_access_token.xml
Normal file
4
app/src/main/res/values/mapbox_access_token.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoiemFuZGVyMTYwOSIsImEiOiJjbTl0bDE4Y3cwMDB1MmxyMHRmd3hnNng5In0.Je5tLeuVjl8Y2-ssvqXxLg</string>
|
||||||
|
</resources>
|
||||||
@@ -49,5 +49,7 @@
|
|||||||
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest</item>
|
<item name="colorSurfaceContainerHighest">@color/md_theme_surfaceContainerHighest</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<string name="map_box_style_uri" translatable="false">mapbox://styles/mapbox/navigation-day-v1</string>
|
||||||
|
|
||||||
<style name="Theme.MyApplication" parent="Base.Theme.MyApplication" />
|
<style name="Theme.MyApplication" parent="Base.Theme.MyApplication" />
|
||||||
</resources>
|
</resources>
|
||||||
@@ -9,5 +9,8 @@ allprojects {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
|
maven {
|
||||||
|
url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,9 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user