Compare commits

1 Commits

Author SHA1 Message Date
Alexander Dörflinger
e482093832 [Maps] Replaces gMaps with MapBox
In order to circumvent the usage quota of Google Maps
the map provider was changed to be MapBox now. Additionally
the maps implementation was refactored to be better extendable
in future.
2025-04-23 14:04:24 +02:00
15 changed files with 733 additions and 168 deletions

1
.idea/gradle.xml generated
View File

@@ -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>

View File

@@ -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'

View File

@@ -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)
.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); mRecenterFollowBtn.setVisibility(View.VISIBLE);
} }
});
mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultHome, 8.0f));
}
} }

View File

@@ -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 + "]";
} }
} }

View File

@@ -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;
}
}

View File

@@ -0,0 +1,6 @@
package com.aldo.apps.familyhelpers.ui.map;
public interface IMapMovementListener {
void onMapMovementDetected();
}

View File

@@ -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();
}
});
}
}

View File

@@ -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.
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -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

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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")
}
} }
} }

View File

@@ -10,6 +10,9 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven {
url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
}
} }
} }