From f6abb8fac0ed7070a44fa583928f03433242b6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20D=C3=B6rflinger?= Date: Wed, 19 Mar 2025 16:05:13 +0100 Subject: [PATCH] [ShareLocation] Initial "read data from db" logic Added some base GoogleMap widget logic and a way to listen to ones own location updates. In future this needs to be extended with a way to listen to the updates of other users. --- app/build.gradle | 8 + app/src/main/AndroidManifest.xml | 20 +- ...DoerflingerHelpersDeviceAdminReceiver.java | 6 +- .../familyhelpers/HelperGridActivity.java | 5 +- .../familyhelpers/ShareLocationActivity.java | 215 ++++++++++++++++++ .../familyhelpers/ui/HelperGroupTile.java | 8 +- .../familyhelpers/utils/GlobalConstants.java | 2 + .../familyhelpers/workers/DatabaseHelper.java | 54 ++++- .../familyhelpers/workers/LocationHelper.java | 10 +- .../workers/SleepTimerHelper.java | 3 - .../main/res/drawable/ic_current_location.xml | 13 ++ .../res/layout/activity_share_location.xml | 69 ++++++ app/src/main/res/raw/map_style_day.json | 46 ++++ app/src/main/res/raw/map_style_night.json | 205 +++++++++++++++++ app/src/main/res/values/strings.xml | 7 +- 15 files changed, 648 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java create mode 100644 app/src/main/res/drawable/ic_current_location.xml create mode 100644 app/src/main/res/layout/activity_share_location.xml create mode 100644 app/src/main/res/raw/map_style_day.json create mode 100644 app/src/main/res/raw/map_style_night.json diff --git a/app/build.gradle b/app/build.gradle index 177acbe..fa33149 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,6 +37,14 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation 'io.reactivex.rxjava3:rxjava:3.1.5' + //Google Maps SDK + implementation 'com.google.android.gms:play-services-maps:19.1.0' + + // Glide + implementation 'com.github.bumptech.glide:glide:4.16.0' // Check for the latest version + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' + + //Firebase Dependencies implementation platform('com.google.firebase:firebase-bom:33.10.0') implementation 'com.google.firebase:firebase-analytics' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d2d6e8a..533cec3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,10 @@ android:supportsRtl="true" android:theme="@style/Theme.MyApplication" tools:targetApi="31"> + + @@ -29,11 +33,16 @@ - + - + + + @@ -43,7 +52,8 @@ - diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/DoerflingerHelpersDeviceAdminReceiver.java b/app/src/main/java/com/aldo/apps/familyhelpers/DoerflingerHelpersDeviceAdminReceiver.java index 9c2db80..114981b 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/DoerflingerHelpersDeviceAdminReceiver.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/DoerflingerHelpersDeviceAdminReceiver.java @@ -4,14 +4,16 @@ import android.app.admin.DeviceAdminReceiver; import android.content.Context; import android.content.Intent; +import androidx.annotation.NonNull; + public class DoerflingerHelpersDeviceAdminReceiver extends DeviceAdminReceiver { @Override - public void onEnabled(final Context context,final Intent intent) { + public void onEnabled(@NonNull final Context context, @NonNull final Intent intent) { // Called when the app is enabled as a device administrator. } @Override - public void onDisabled(final Context context, final Intent intent) { + public void onDisabled(@NonNull final Context context, @NonNull final Intent intent) { // Called when the app is disabled as a device administrator. } } \ No newline at end of file diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java index 4b00439..26780f2 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java @@ -162,15 +162,16 @@ public class HelperGridActivity extends AppCompatActivity { if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this)) { Log.d(TAG, "launchHelper: Permission already granted"); mLocationHelper.toggleUpdate(HelperGridActivity.this); + final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class); + startActivity(intent); } } }; mShareLocationTile.setLogo(R.drawable.ic_location_helper); mShareLocationTile.setTitle(R.string.title_share_location); + } - - /** * Helper method to request the NotificationPermission as it is required for the service to run. * diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java new file mode 100644 index 0000000..2a0893c --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java @@ -0,0 +1,215 @@ +package com.aldo.apps.familyhelpers; + +import static com.aldo.apps.familyhelpers.utils.GlobalConstants.METER_PER_SECOND_TO_KMH_CONVERTER; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.util.Log; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import com.aldo.apps.familyhelpers.model.LocationObject; +import com.aldo.apps.familyhelpers.workers.DatabaseHelper; +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.MarkerOptions; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import io.reactivex.rxjava3.disposables.Disposable; + +/** + * Activity showing a Map to display the shared location plus additional information. + */ +public class ShareLocationActivity extends AppCompatActivity implements OnMapReadyCallback { + + /** + * Tag for debugging purpose. + */ + private static final String TAG = "ShareLocationActivity"; + + /** + * The {@link GoogleMap} view. + */ + private GoogleMap mGmap; + + /** + * The {@link DatabaseHelper} to read and load data from. + */ + private DatabaseHelper mDbHelper; + + /** + * The currently logged in {@link FirebaseUser}. + */ + private FirebaseUser mCurrentUser; + + /** + * The ID of the location updates to listen to. + */ + private String mShareId; + + /** + * The InfoBox {@link ImageView} showing the user icon. + */ + private ImageView mInfoBoxIcon; + + /** + * The InfoBox {@link TextView} showing the title. + */ + private TextView mInfoBoxTitle; + + /** + * The InfoBox {@link TextView} showing the Location information. + */ + private TextView mInfoBoxLocation; + + /** + * The InfoBox {@link TextView} showing the altitude.. + */ + private TextView mInfoBoxAltitude; + + /** + * The InfoBox {@link TextView} showing the last received speed. + */ + private TextView mInfoBoxSpeed; + + /** + * The InfoBox {@link TextView} showing the timestamp of the last update. + */ + private TextView mInfoBoxTimeStamp; + + /** + * The {@link Disposable} holding the subscription to the {@link LocationObject} + * {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable. + */ + private Disposable mLocationUpdateSubscription; + + /** + * Boolean flag indicating whether the system is currently in night mode or not. + */ + private boolean mIsNightMode; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_share_location); + + final SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() + .findFragmentById(R.id.map); + mCurrentUser = FirebaseAuth.getInstance().getCurrentUser(); + mInfoBoxIcon = findViewById(R.id.share_location_info_user_icon); + Glide.with(this) + .load(mCurrentUser.getPhotoUrl()) + .into(mInfoBoxIcon); + mInfoBoxTitle = findViewById(R.id.share_location_info_title); + mInfoBoxLocation = findViewById(R.id.share_location_info_location); + mInfoBoxAltitude = findViewById(R.id.share_location_info_altitude); + mInfoBoxSpeed = findViewById(R.id.share_location_info_speed); + mInfoBoxTimeStamp = findViewById(R.id.share_location_info_timestamp); + mapFragment.getMapAsync(this); + mDbHelper = new DatabaseHelper(); + } + + @Override + protected void onResume() { + super.onResume(); + final Configuration configuration = getResources().getConfiguration(); + + mIsNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + + if (mShareId == null) { + mShareId = mCurrentUser.getUid(); + } + mDbHelper.startListeningForLocationUpdates(mShareId); + if (mLocationUpdateSubscription != null) { + mLocationUpdateSubscription.dispose(); + } + mLocationUpdateSubscription = mDbHelper.getLocationSubject() + .subscribe(this::handleReceivedLocation, this::handleSubscriptionError); + } + + @Override + protected void onPause() { + super.onPause(); + if (mLocationUpdateSubscription != null) { + mLocationUpdateSubscription.dispose(); + } + } + + /** + * Helper method to handle the received location by updating both the Map and the Info box. + * + * @param locationObject The received {@link LocationObject} holding the new information. + */ + private void handleReceivedLocation(final LocationObject locationObject) { + Log.d(TAG, "handleReceivedLocation() called with: locationObject = [" + locationObject + "]"); + mInfoBoxTitle.setText(String.format(getString(R.string.share_location_info_title), mCurrentUser.getDisplayName())); + mInfoBoxLocation.setText(String.format(getString(R.string.share_location_info_location), + locationObject.getLatitude(), locationObject.getLongitude())); + mInfoBoxAltitude.setText(String.format(getString(R.string.share_location_info_altitude), + locationObject.getAltitude())); + mInfoBoxSpeed.setText(String.format(getString(R.string.share_location_info_speed), + locationObject.getSpeed(), + locationObject.getSpeed() * METER_PER_SECOND_TO_KMH_CONVERTER)); + mInfoBoxTimeStamp.setText(String.format(getString(R.string.share_location_info_timestamp), formatTime(locationObject.getTimestamp()))); + + final LatLng received = new LatLng(locationObject.getLatitude(), locationObject.getLongitude()); + + mGmap.addMarker(new MarkerOptions() + .position(received) + .title(mCurrentUser.getDisplayName())); + mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(received, 15.0f)); + + } + + /** + * Handle potential errors on the subscription to the Location. + * + * @param error The error that occurred. + */ + private void handleSubscriptionError(final Throwable error) { + Log.e(TAG, "handleSubscriptionError: ", error); + } + + /** + * Formats the provided time in milliseconds in a human readable format. + * + * @param millis The remaining time in milliseconds. + * + * @return The String representation of the milliseconds. + */ + private String formatTime(final long millis) { + final Date date = new Date(millis); + final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy - HH:mm:ss", Locale.getDefault()); + return simpleDateFormat.format(date); + } + + @Override + public void onMapReady(@NonNull final GoogleMap googleMap) { + 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(48.41965746149261, 9.909289365473684); + + mGmap.addMarker(new MarkerOptions().position(defaultHome).title("Default - Home")); + mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultHome, 15.0f)); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java index 2b176b9..a3bbb31 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java @@ -38,7 +38,7 @@ public abstract class HelperGroupTile implements View.OnClickListener { /** * The {@link WeakReference} of the {@link Context} from where this was instantiated. */ - private WeakReference mContextRef; + private final WeakReference mContextRef; /** * The name of the helper, mainly used for logging purpose. @@ -50,7 +50,7 @@ public abstract class HelperGroupTile implements View.OnClickListener { * * @param rootLayout The previously inflated view of the root layout. */ - public HelperGroupTile(final View rootLayout) { + protected HelperGroupTile(final View rootLayout) { mContextRef = new WeakReference<>(rootLayout.getContext()); mHelperLogo = rootLayout.findViewById(R.id.iv_helper_group_icon); mHelperTitle = rootLayout.findViewById(R.id.tv_helper_group_title); @@ -77,7 +77,7 @@ public abstract class HelperGroupTile implements View.OnClickListener { if (mHelperTitle != null) { mHelperTitle.setText(titleId); } else { - Log.d(TAG, "setLogo: Cannot set Logo for [" + mHelperName + "]"); + Log.d(TAG, "setTitle: Cannot set Title for [" + mHelperName + "]"); } } @@ -91,7 +91,7 @@ public abstract class HelperGroupTile implements View.OnClickListener { if (mHelperTitle != null) { mHelperTitle.setText(groupTitle); } else { - Log.d(TAG, "setLogo: Cannot set Logo for [" + mHelperName + "]"); + Log.d(TAG, "setTitleString: Cannot set TitleId for [" + mHelperName + "]"); } } diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java b/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java index 6d4c210..28bb884 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java @@ -73,6 +73,8 @@ public final class GlobalConstants { */ public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS = 5; + public static final double METER_PER_SECOND_TO_KMH_CONVERTER = 3.6; + /** * List of available Firebase signIn/Login providers. */ diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java b/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java index f94aea2..040d92e 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java @@ -6,12 +6,12 @@ import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_DOC_USER_ID import android.util.Log; - import com.aldo.apps.familyhelpers.model.LocationObject; import com.aldo.apps.familyhelpers.model.User; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.ListenerRegistration; import java.util.ArrayList; import java.util.List; @@ -43,6 +43,13 @@ public class DatabaseHelper { */ private final BehaviorSubject> mAllUsers = BehaviorSubject.create(); + /** + * Local {@link BehaviorSubject} representation holding the {@link LocationObject} that is to be observed. + */ + private BehaviorSubject mObservedLocation = BehaviorSubject.create(); + + private ListenerRegistration mLocationUpdateListener; + /** * C'tor. */ @@ -84,6 +91,24 @@ public class DatabaseHelper { }); } + /** + * Returns the {@link BehaviorSubject} containing the {@link List} of all {@link User} objects. + * + * @return The {@link BehaviorSubject} containing the {@link List} of all {@link User} objects. + */ + public BehaviorSubject> getAllUsers() { + return mAllUsers; + } + + /** + * Returns the {@link BehaviorSubject} containing the {@link LocationObject} to be observed. + * + * @return The {@link BehaviorSubject} containing the {@link LocationObject} to be observed. + */ + public BehaviorSubject getLocationSubject() { + return mObservedLocation; + } + /** * Helper method to insert or update a {@link LocationObject} in the database. * @@ -95,6 +120,30 @@ public class DatabaseHelper { .set(locationObject); } + /** + * Start listening and publishing {@link LocationObject} updates on {@link #mObservedLocation}. + * + * @param shareId The ID to be observed. + */ + public void startListeningForLocationUpdates(final String shareId) { + mObservedLocation = BehaviorSubject.create(); + mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION) + .document(shareId) + .addSnapshotListener((value, error) -> { + if (value == null) { + Log.d(TAG, "onEvent: Location was deleted"); + mObservedLocation.onComplete(); + return; + } + final LocationObject updatedLocation = value.toObject(LocationObject.class); + if (updatedLocation == null) { + Log.w(TAG, "onEvent: Error while parsing, ignore"); + return; + } + mObservedLocation.onNext(updatedLocation); + }); + } + /** * Cancels (by deleting) the ongoing {@link LocationObject} sharing in the database. */ @@ -103,6 +152,9 @@ public class DatabaseHelper { Log.w(TAG, "cancelOngoingLocationSharing: No user logged in, cannot stop sharing"); return; } + if (mLocationUpdateListener != null) { + mLocationUpdateListener.remove(); + } mDatabase.collection(DB_COLL_LOCATION).document(mCurrentUser.getUid()).delete(); } diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/workers/LocationHelper.java b/app/src/main/java/com/aldo/apps/familyhelpers/workers/LocationHelper.java index a862fe9..6551e4a 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/workers/LocationHelper.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/workers/LocationHelper.java @@ -67,17 +67,17 @@ public final class LocationHelper implements LocationListener { * The {@link BehaviorSubject} for the {@link LocationObject} for clients to subscribe to. * TODO: Check if needed. */ - private BehaviorSubject mLocationSubject = BehaviorSubject.create(); + private final BehaviorSubject mLocationSubject = BehaviorSubject.create(); /** * The {@link DatabaseHelper} for db access. */ - private DatabaseHelper mDbHelper = new DatabaseHelper(); + private final DatabaseHelper mDbHelper = new DatabaseHelper(); /** * The {@link NotificationHelper} to show and update {@link Notification}s. */ - private NotificationHelper mNotificationHelper; + private final NotificationHelper mNotificationHelper; /** * Private C'Tor for singleton instance. @@ -93,10 +93,10 @@ public final class LocationHelper implements LocationListener { } /** - * Returns the singleton instance of thie {@link LocationHelper}. + * Returns the singleton instance of this {@link LocationHelper}. * * @param context The {@link Context) from where this was instantiated. - * @return The singleton instance of thie {@link LocationHelper}. + * @return The singleton instance of this {@link LocationHelper}. */ public static LocationHelper getInstance(final Context context) { if (sInstance == null) { diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/workers/SleepTimerHelper.java b/app/src/main/java/com/aldo/apps/familyhelpers/workers/SleepTimerHelper.java index b6ec9d4..aea0cc5 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/workers/SleepTimerHelper.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/workers/SleepTimerHelper.java @@ -8,12 +8,9 @@ import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SLEEP_TIMER_DURA import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SLEEP_TIMER_NOTIFICATION_ID; import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; -import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; diff --git a/app/src/main/res/drawable/ic_current_location.xml b/app/src/main/res/drawable/ic_current_location.xml new file mode 100644 index 0000000..a7911aa --- /dev/null +++ b/app/src/main/res/drawable/ic_current_location.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/layout/activity_share_location.xml b/app/src/main/res/layout/activity_share_location.xml new file mode 100644 index 0000000..ec59725 --- /dev/null +++ b/app/src/main/res/layout/activity_share_location.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/map_style_day.json b/app/src/main/res/raw/map_style_day.json new file mode 100644 index 0000000..5d3963d --- /dev/null +++ b/app/src/main/res/raw/map_style_day.json @@ -0,0 +1,46 @@ +[ + { + "featureType": "administrative.land_parcel", + "elementType": "labels", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi.business", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "road.local", + "elementType": "labels", + "stylers": [ + { + "visibility": "off" + } + ] + } +] \ No newline at end of file diff --git a/app/src/main/res/raw/map_style_night.json b/app/src/main/res/raw/map_style_night.json new file mode 100644 index 0000000..29769c6 --- /dev/null +++ b/app/src/main/res/raw/map_style_night.json @@ -0,0 +1,205 @@ +[ + { + "elementType": "geometry", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "featureType": "administrative.land_parcel", + "elementType": "labels", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "administrative.locality", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi.business", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [ + { + "color": "#263c3f" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#6b9a76" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [ + { + "color": "#38414e" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#212a37" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9ca5b3" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#1f2835" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#f3d19c" + } + ] + }, + { + "featureType": "road.local", + "elementType": "labels", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "transit", + "elementType": "geometry", + "stylers": [ + { + "color": "#2f3948" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#17263c" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#515c6d" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#17263c" + } + ] + } +] \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5d901ee..a42fd49 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,9 +19,14 @@ %02d:%02d:%02d" %02d:%02d" - + Share Location ShareLocation You are sharing your current location within the family + You are following %s + Last Received Location = %f;%f + Last received latitude = %.2f Meters + The last received velocity was %.2f m/s (%.1f km/h) + This update was received at: %s \ No newline at end of file