* location centre button with nice animation

* location accuracyArea and direction indicator
* POST on manual selection
* removing markers after 5mins
This commit is contained in:
Adam Domagalski 2018-11-04 01:37:57 +01:00
parent 8a0ccfa60a
commit cad9f466c2
5 changed files with 240 additions and 43 deletions

View File

@ -44,7 +44,7 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation "io.swagger:swagger-annotations:1.5.15" implementation "io.swagger:swagger-annotations:1.5.15"
implementation "org.threeten:threetenbp:1.3.5" implementation "org.threeten:threetenbp:1.3.5"
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:6.4.0' implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:6.6.5'
implementation 'io.reactivex.rxjava2:rxjava:2.2.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'com.android.support:recyclerview-v7:27.1.1' implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'

View File

@ -1,12 +1,12 @@
package com.uam.wmi.findmytutor.activity; package com.uam.wmi.findmytutor.activity;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
@ -15,17 +15,27 @@ import android.util.Log;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException; import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.Icon;
import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.IconFactory;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.LocationComponentOptions;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.style.layers.CircleLayer; import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.VectorSource; import com.mapbox.mapboxsdk.style.sources.VectorSource;
import static com.mapbox.mapboxsdk.style.layers.Property.NONE; import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE; import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
import android.graphics.Color; import android.graphics.Color;
import android.view.View; import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.Button; import android.widget.Button;
import android.widget.Toast; import android.widget.Toast;
@ -40,34 +50,31 @@ import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.uam.wmi.findmytutor.R; import com.uam.wmi.findmytutor.R;
import com.uam.wmi.findmytutor.model.Coordinate; import com.uam.wmi.findmytutor.model.Coordinate;
import com.uam.wmi.findmytutor.network.ApiClient; import com.uam.wmi.findmytutor.network.ApiClient;
import com.uam.wmi.findmytutor.network.RetrofitClientInstance;
import com.uam.wmi.findmytutor.service.CoordinateService; import com.uam.wmi.findmytutor.service.CoordinateService;
import com.uam.wmi.findmytutor.utils.PrefUtils; import com.uam.wmi.findmytutor.utils.PrefUtils;
import com.uam.wmi.findmytutor.utils.RestApiHelper; import com.uam.wmi.findmytutor.utils.RestApiHelper;
import com.uam.wmi.findmytutor.utils.mapUtils; import com.uam.wmi.findmytutor.utils.mapUtils;
import java.nio.ByteBuffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import timber.log.Timber;
public class MapActivity extends BaseActivity public class MapActivity extends BaseActivity
implements OnMapReadyCallback { implements PermissionsListener, OnMapReadyCallback {
private String tag = getClass().getName();
String tag = getClass().getName();
private PermissionsManager permissionsManager;
private LocationComponent locationComponent;
private CoordinateService coordinateService; private CoordinateService coordinateService;
private CompositeDisposable disposable = new CompositeDisposable(); private CompositeDisposable disposable = new CompositeDisposable();
@ -82,6 +89,9 @@ public class MapActivity extends BaseActivity
private HashMap<String, Coordinate> coordsMap = new HashMap<>(); private HashMap<String, Coordinate> coordsMap = new HashMap<>();
private HashMap<String, Marker> markerHash = new HashMap<>(); private HashMap<String, Marker> markerHash = new HashMap<>();
private int zoomParam = 17;
private int bearingParam = 180;
private int tiltParam = 30;
@Override @Override
@ -115,7 +125,9 @@ public class MapActivity extends BaseActivity
@Override @Override
public void onMapReady(MapboxMap mapboxMap) { public void onMapReady(MapboxMap mapboxMap) {
MapActivity.this.mapboxMap = mapboxMap; MapActivity.this.mapboxMap = mapboxMap;
// mapboxMap = map; enableLocationPlugin();
// TODO floating marker to remove later
final Marker marker = mapboxMap.addMarker(new MarkerViewOptions() final Marker marker = mapboxMap.addMarker(new MarkerViewOptions()
.position(new LatLng(52.466782, 16.927549))); .position(new LatLng(52.466782, 16.927549)));
mStatusChecker.run(); mStatusChecker.run();
@ -131,16 +143,13 @@ public class MapActivity extends BaseActivity
}); });
// TODO what should happend on click? // // TODO what should happend on click?
mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() { // mapboxMap.setOnMarkerClickListener(marker1 -> {
@Override //
public boolean onMarkerClick(@NonNull Marker marker) { // // Show a toast with the title of the selected marker
//// Toast.makeText(MapActivity.this, marker.getTitle(), Toast.LENGTH_SHORT).show();
// Show a toast with the title of the selected marker // return true;
Toast.makeText(MapActivity.this, marker.getTitle(), Toast.LENGTH_SHORT).show(); // });
return true;
}
});
setOnMapLongClickListener(); setOnMapLongClickListener();
@ -148,6 +157,7 @@ public class MapActivity extends BaseActivity
} }
private void setOnMapLongClickListener() { private void setOnMapLongClickListener() {
final boolean[] cancel = {false}; final boolean[] cancel = {false};
Button selectLocationButton = findViewById(R.id.select_location_button); Button selectLocationButton = findViewById(R.id.select_location_button);
@ -164,7 +174,7 @@ public class MapActivity extends BaseActivity
.title("My Loc") .title("My Loc")
.setSnippet("Snipecik")); .setSnippet("Snipecik"));
} else if (!cancel[0]){ } else if (!cancel[0]) {
ValueAnimator markerAnimator = ObjectAnimator.ofObject(droppedMarker, "position", ValueAnimator markerAnimator = ObjectAnimator.ofObject(droppedMarker, "position",
new mapUtils.LatLngEvaluator(), droppedMarker.getPosition(), latLng); new mapUtils.LatLngEvaluator(), droppedMarker.getPosition(), latLng);
markerAnimator.setDuration(2000); markerAnimator.setDuration(2000);
@ -173,8 +183,65 @@ public class MapActivity extends BaseActivity
selectLocationButton.setOnClickListener((View view) -> { selectLocationButton.setOnClickListener((View view) -> {
if (!cancel[0] && droppedMarker != null){ if (!cancel[0] && droppedMarker != null) {
// Toast instructing user to tap on the mapboxMap // Toast instructing user to tap on the mapboxMap
// TODO PUT MANUAL CORD
try {
Coordinate coordinate = new Coordinate(
latLng.getLatitude(),
latLng.getLongitude(),
latLng.getAltitude(),
PrefUtils.getUserFirstName(getApplicationContext()) + " " + PrefUtils.getUserLastName(getApplicationContext()),
PrefUtils.getUserId(getApplicationContext())
);
disposable.add(
coordinateService
.postCoordinate(coordinate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Coordinate>() {
@SuppressLint("LongLogTag")
@Override
public void onSuccess(Coordinate coord) {
Log.e(tag+"POST", String.valueOf(coord));
}
@SuppressLint("LongLogTag")
@Override
public void onError(Throwable e) {
Log.e(tag+"onError", e.getMessage());
if (e instanceof HttpException) {
ResponseBody responseBody = ((HttpException) e).response().errorBody();
Log.e(tag+"onError", RestApiHelper.getErrorMessage(responseBody));
}
}
}));
} catch (IllegalArgumentException e) {
Timber.e(String.valueOf(e));
}
Toast.makeText( Toast.makeText(
MapActivity.this, MapActivity.this,
"Manual Locations selected!" + latLng, "Manual Locations selected!" + latLng,
@ -183,10 +250,14 @@ public class MapActivity extends BaseActivity
selectLocationButton.setBackgroundColor( selectLocationButton.setBackgroundColor(
ContextCompat.getColor(MapActivity.this, R.color.colorAccent)); ContextCompat.getColor(MapActivity.this, R.color.colorAccent));
selectLocationButton.setText("Rm my loc"); selectLocationButton.setText("Remove Manual location");
selectLocationButton.setVisibility(View.VISIBLE); selectLocationButton.setVisibility(View.VISIBLE);
cancel[0] = true; cancel[0] = true;
} else { } else {
// TODO REMOVE Manual Locatio
mapboxMap.removeMarker(droppedMarker); mapboxMap.removeMarker(droppedMarker);
droppedMarker = null; droppedMarker = null;
@ -202,8 +273,6 @@ public class MapActivity extends BaseActivity
selectLocationButton.setBackgroundColor( selectLocationButton.setBackgroundColor(
ContextCompat.getColor(MapActivity.this, R.color.colorPrimary)); ContextCompat.getColor(MapActivity.this, R.color.colorPrimary));
} }
}); });
}); });
@ -241,48 +310,57 @@ public class MapActivity extends BaseActivity
} }
private void fetchTopCoords() { private void fetchTopCoords() {
disposable.add( disposable.add(
// coordinateService.getTopCoordinates() // coordinateService.getTopCoordinates()
coordinateService.getOnlineCoordinates() coordinateService.getOnlineCoordinates()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<List<Coordinate>>() { .subscribeWith(new DisposableSingleObserver<List<Coordinate>>() {
@Override @Override
public void onSuccess(List<Coordinate> coordsList) { public void onSuccess(List<Coordinate> coordsList) {
Log.e(tag, ""); Log.e(tag, "");
for (Coordinate element : coordsList) { for (Coordinate element : coordsList) {
String id = element.getUserId(); String id = element.getUserId();
Coordinate coordinate = coordsMap.get(id); Coordinate coordinate = coordsMap.get(id);
Log.e(tag, "hashMapSize: "+ coordsMap.size()); Log.e(tag, "hashMapSize: " + coordsMap.size());
Log.e(tag, "markerMapSize: "+ coordsMap.size()); Log.e(tag, "markerMapSize: " + coordsMap.size());
if (coordinate != null) { if (coordinate != null) {
Log.e(tag, "Coordin: " + coordinate.getLatitude()+" | " + coordinate.getLongitude()); Log.e(tag, "Coordin: " + coordinate.getLatitude() + " | " + coordinate.getLongitude());
// Log.e(tag, "Element: " + element.getLatitude()+" | " + element.getLongitude()); // Log.e(tag, "Element: " + element.getLatitude()+" | " + element.getLongitude());
// Log.e(tag, "isEqual: " + coordinate.getLatitude().equals(element.getLatitude())); // Log.e(tag, "isEqual: " + coordinate.getLatitude().equals(element.getLatitude()));
// Log.e(tag, "isEqual: " + coordinate.getLongitude().equals(element.getLongitude())); // Log.e(tag, "isEqual: " + coordinate.getLongitude().equals(element.getLongitude()));
boolean statement = coordinate.getLatitude().equals(element.getLatitude()) || coordinate.getLongitude().equals(element.getLongitude());
boolean statement = coordinate.getLatitude().equals(element.getLatitude()) || coordinate.getLongitude().equals(element.getLongitude());
Log.e(tag, "diff || diff: " + !statement); Log.e(tag, "diff || diff: " + !statement);
if (!statement) { if (!statement) {
Log.e(tag, "replace and animate"); Log.e(tag, "replace and animate");
ValueAnimator markerAnimator = ObjectAnimator.ofObject(markerHash.get(id), "position", Marker marker = markerHash.get(id);
new mapUtils.LatLngEvaluator(), markerHash.get(id).getPosition(), LatLng toDestination = new LatLng(element.getLatitude(), element.getLongitude());
new LatLng(element.getLatitude(), element.getLongitude()));
ValueAnimator markerAnimator = ObjectAnimator.ofObject(marker, "position",
new mapUtils.LatLngEvaluator(),
marker.getPosition(),
toDestination);
markerAnimator.setDuration(2000); markerAnimator.setDuration(2000);
markerAnimator.setInterpolator(new LinearInterpolator());
markerAnimator.start(); markerAnimator.start();
coordsMap.put(id, element);
markerHash.get(id).setPosition(new LatLng(element.getLatitude(), element.getLongitude()));
} else if(!coordinate.getTimeStamp().equals(element.getTimeStamp())) { // chba niepotrzbene
Log.e(tag, "timestamp changed"); mapboxMap.getMarkerViewManager().update();
coordsMap.put(id, element);
marker.setPosition(toDestination);
} }
} else { } else {
Log.e(tag, "Marker Added: " + id); Log.e(tag, "Marker Added: " + id);
coordsMap.put(id, element); coordsMap.put(id, element);
@ -292,7 +370,22 @@ public class MapActivity extends BaseActivity
markerHash.put(id, marker); markerHash.put(id, marker);
} }
} }
for (Coordinate coordinate : coordsMap.values()) {
// 300000 = 5mins
if ((System.currentTimeMillis() - coordinate.getTimeStamp()) > (long) 300000) {
String id = coordinate.getUserId();
Marker markerToRemove = markerHash.get(id);
markerHash.remove(id);
coordsMap.remove(id);
mapboxMap.removeMarker(markerToRemove);
} }
}
}
@SuppressLint("LongLogTag") @SuppressLint("LongLogTag")
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
@ -307,7 +400,6 @@ public class MapActivity extends BaseActivity
} }
}) })
); );
} }
private void configureLogoutButton() { private void configureLogoutButton() {
@ -327,33 +419,37 @@ public class MapActivity extends BaseActivity
}); });
} }
// Add the mapView lifecycle to the activity's lifecycle methods // Add the mapView lifecycle to the activity's lifecycle methods
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
mapView.onResume(); mapView.onResume();
} }
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
mapView.onStart(); mapView.onStart();
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
mapView.onStop(); mapView.onStop();
} }
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
mapView.onPause(); mapView.onPause();
} }
@Override @Override
public void onLowMemory() { public void onLowMemory() {
super.onLowMemory(); super.onLowMemory();
mapView.onLowMemory(); mapView.onLowMemory();
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -361,17 +457,94 @@ public class MapActivity extends BaseActivity
mHandler.removeCallbacks(mStatusChecker); mHandler.removeCallbacks(mStatusChecker);
disposable.dispose(); disposable.dispose();
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState);
} }
@Override @Override
protected int getContentViewId() { protected int getContentViewId() {
return R.layout.activity_map; return R.layout.activity_map;
} }
@Override @Override
protected int getNavigationMenuItemId() { protected int getNavigationMenuItemId() {
return R.id.nav_map; return R.id.nav_map;
} }
@SuppressWarnings({"MissingPermission"})
private void enableLocationPlugin() {
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(this)) {
LocationComponentOptions options = LocationComponentOptions.builder(this)
.trackingGesturesManagement(true)
.accuracyColor(ContextCompat.getColor(this, R.color.mapboxGray))
.build();
// Get an instance of the component
locationComponent = mapboxMap.getLocationComponent();
// TODO mieszko tu jest lepsza lokalizacja
// locationComponent.getLastKnownLocation();
// Log.e(tag + "Last", getLastKnownLocation() + "");
// Activate with options
locationComponent.activateLocationComponent(this, options);
// Enable to make component visible
locationComponent.setLocationComponentEnabled(true);
// Set the component's camera mode
locationComponent.setCameraMode(CameraMode.TRACKING);
locationComponent.setRenderMode(RenderMode.COMPASS);
// Button 4 centring
FloatingActionButton myLocFAB = findViewById(R.id.myLocationButton);
myLocFAB.setOnClickListener(v -> {
Location lastKnownLocation = locationComponent.getLastKnownLocation();
if (lastKnownLocation != null) {
CameraPosition position = new CameraPosition.Builder()
.target(new LatLng(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())) // Sets the new camera position
.zoom(zoomParam) // Sets the zoom
.bearing(bearingParam) // Rotate the camera
.tilt(tiltParam) // Set the camera tilt
.build(); // Creates a CameraPosition from the builder
mapboxMap.animateCamera(CameraUpdateFactory
.newCameraPosition(position), 4000);
}
zoomParam = (zoomParam == 17) ? 19 : 17;
bearingParam += 90;
tiltParam = (tiltParam == 30) ? 0 : 30;
});
} else {
permissionsManager = new PermissionsManager(this);
permissionsManager.requestLocationPermissions(this);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @android.support.annotation.NonNull String[] permissions, @android.support.annotation.NonNull int[] grantResults) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
}
@Override
public void onPermissionResult(boolean granted) {
if (granted) {
enableLocationPlugin();
} else {
Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();
finish();
}
}
} }

View File

@ -24,7 +24,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="bottom" android:gravity="bottom"
android:orientation="vertical"> android:orientation="vertical"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="1dp">
<!-- Implementation of find my location button -->
<android.support.design.widget.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:id="@+id/logoutButton" android:id="@+id/logoutButton"
@ -49,6 +53,23 @@
mapbox:srcCompat="@android:drawable/ic_dialog_map" mapbox:srcCompat="@android:drawable/ic_dialog_map"
tools:layout_editor_absoluteX="0dp" /> tools:layout_editor_absoluteX="0dp" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/myLocationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
android:background="@color/half_black"
android:backgroundTint="@color/materialDarkGrey"
android:src="@android:drawable/ic_menu_mylocation"
app:borderWidth="0dp"
app:elevation="6dp"
app:fabSize="normal"
app:fab_colorDisabled="@color/mapboxGray"
app:fab_colorNormal="@color/mapboxGray"
app:pressedTranslationZ="12dp" />
<com.getbase.floatingactionbutton.FloatingActionsMenu <com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/multiple_actions_parent_fab" android:id="@+id/multiple_actions_parent_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -90,6 +111,7 @@
fab:fab_title="@string/fab_title_attractions" /> fab:fab_title="@string/fab_title_attractions" />
</com.getbase.floatingactionbutton.FloatingActionsMenu> </com.getbase.floatingactionbutton.FloatingActionsMenu>
<Button <Button
android:id="@+id/select_location_button" android:id="@+id/select_location_button"
android:layout_width="fill_parent" android:layout_width="fill_parent"

View File

@ -15,7 +15,7 @@
<color name="materialGrey">#F5F5F5</color> <color name="materialGrey">#F5F5F5</color>
<color name="materialDarkGrey">#DFDFDF</color> <color name="materialDarkGrey">#dfdfdf</color>
<color name="mapboxWhite">#FFFFFF</color> <color name="mapboxWhite">#FFFFFF</color>

View File

@ -203,5 +203,7 @@ functionality.</string>
<string name="fab_title_parks">Parks</string> <string name="fab_title_parks">Parks</string>
<string name="fab_title_attractions">Attractions</string> <string name="fab_title_attractions">Attractions</string>
<string name="select_a_location">Select a location</string> <string name="select_a_location">Select a location</string>
<string name="user_location_permission_explanation">This app needs location permissions in order to show its functionality.</string>
<string name="user_location_permission_not_granted">You didn\'t grant location permissions.</string>
</resources> </resources>