Skip to content

Commit

Permalink
Merge pull request #406 from shobhitagarwal1612/map-type
Browse files Browse the repository at this point in the history
[Feature] Add button to switch map type
  • Loading branch information
gino-m committed Mar 30, 2020
2 parents e6e8f74 + a1b26df commit 325491c
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 38 deletions.
Expand Up @@ -21,14 +21,14 @@

import android.content.res.ColorStateList;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import butterknife.BindView;
Expand All @@ -48,10 +48,11 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import io.reactivex.Single;
import javax.inject.Inject;
import timber.log.Timber;

/** Main app view, displaying the map and related controls (center cross-hairs, add button, etc). */
public class MapContainerFragment extends AbstractFragment {
private static final String TAG = MapContainerFragment.class.getSimpleName();

private static final String MAP_FRAGMENT_KEY = MapProvider.class.getName() + "#fragment";

@Inject MapProvider mapProvider;
Expand All @@ -72,6 +73,21 @@ public class MapContainerFragment extends AbstractFragment {
private HomeScreenViewModel homeScreenViewModel;
private MainViewModel mainViewModel;

private void showMapTypeSelectorDialog() {
new AlertDialog.Builder(getContext())
.setTitle(R.string.select_map_type)
.setSingleChoiceItems(
mapProvider.getMapTypes().values().toArray(new String[0]),
mapProvider.getMapType(),
(dialog, which) -> {
mapProvider.setMapType(which);
dialog.dismiss();
})
.setCancelable(true)
.create()
.show();
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down Expand Up @@ -120,10 +136,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
} else {
mapProvider.restore(restoreChildFragment(savedInstanceState, MAP_FRAGMENT_KEY));
}

mapContainerViewModel
.getShowMapTypeSelectorRequests()
.observe(getViewLifecycleOwner(), __ -> showMapTypeSelectorDialog());
}

private void onMapReady(MapAdapter map) {
Log.d(TAG, "MapAdapter ready. Updating subscriptions");
Timber.d("MapAdapter ready. Updating subscriptions");
// Observe events emitted by the ViewModel.
mapContainerViewModel.getMapPins().observe(this, map::setMapPins);
mapContainerViewModel
Expand Down Expand Up @@ -157,7 +177,7 @@ private void onFeatureSheetStateChange(FeatureSheetState state, MapAdapter map)
map.enable();
break;
default:
Log.e(TAG, "Unhandled visibility: " + state.getVisibility());
Timber.e("Unhandled visibility: %s", state.getVisibility());
break;
}
}
Expand Down Expand Up @@ -189,11 +209,11 @@ private void disableAddFeatureBtn() {
private void onLocationLockStateChange(BooleanOrError result, MapAdapter map) {
result.error().ifPresent(this::onLocationLockError);
if (result.isTrue()) {
Log.d(TAG, "Location lock enabled");
Timber.d("Location lock enabled");
map.enableCurrentLocationIndicator();
locationLockBtn.setImageResource(R.drawable.ic_gps_blue);
} else {
Log.d(TAG, "Location lock disabled");
Timber.d("Location lock disabled");
locationLockBtn.setImageResource(R.drawable.ic_gps_grey600);
}
}
Expand All @@ -213,7 +233,7 @@ private void showUserActionFailureMessage(int resId) {
}

private void onCameraUpdate(MapContainerViewModel.CameraUpdate update, MapAdapter map) {
Log.v(TAG, "Update camera: " + update);
Timber.v("Update camera: %s", update);
if (update.getMinZoomLevel().isPresent()) {
map.moveCamera(
update.getCenter(), Math.max(update.getMinZoomLevel().get(), map.getCurrentZoomLevel()));
Expand Down
Expand Up @@ -19,7 +19,6 @@
import static com.google.android.gnd.util.ImmutableSetCollector.toImmutableSet;
import static java8.util.stream.StreamSupport.stream;

import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.LiveDataReactiveStreams;
import androidx.lifecycle.MutableLiveData;
Expand All @@ -29,7 +28,9 @@
import com.google.android.gnd.repository.FeatureRepository;
import com.google.android.gnd.repository.ProjectRepository;
import com.google.android.gnd.rx.BooleanOrError;
import com.google.android.gnd.rx.Event;
import com.google.android.gnd.rx.Loadable;
import com.google.android.gnd.rx.Nil;
import com.google.android.gnd.system.LocationManager;
import com.google.android.gnd.ui.common.AbstractViewModel;
import com.google.android.gnd.ui.common.SharedViewModel;
Expand All @@ -42,11 +43,11 @@
import java.util.concurrent.TimeUnit;
import java8.util.Optional;
import javax.inject.Inject;
import timber.log.Timber;

@SharedViewModel
public class MapContainerViewModel extends AbstractViewModel {

private static final String TAG = MapContainerViewModel.class.getSimpleName();
private static final float DEFAULT_ZOOM_LEVEL = 20.0f;
private final LiveData<Loadable<Project>> activeProject;
private final LiveData<ImmutableSet<MapPin>> mapPins;
Expand All @@ -57,6 +58,7 @@ public class MapContainerViewModel extends AbstractViewModel {
private final FeatureRepository featureRepository;
private final Subject<Boolean> locationLockChangeRequests;
private final Subject<CameraUpdate> cameraUpdateSubject;
private final MutableLiveData<Event<Nil>> showMapTypeSelectorRequests = new MutableLiveData<>();

@Inject
MapContainerViewModel(
Expand Down Expand Up @@ -131,6 +133,10 @@ private Flowable<ImmutableSet<Feature>> getFeaturesStream(Optional<Project> acti
.orElse(Flowable.just(ImmutableSet.of()));
}

public void onMapTypeButtonClicked() {
showMapTypeSelectorRequests.setValue(Event.create(Nil.NIL));
}

private static ImmutableSet<MapPin> toMapPins(ImmutableSet<Feature> features) {
return stream(features).map(MapContainerViewModel::toMapPin).collect(toImmutableSet());
}
Expand Down Expand Up @@ -174,7 +180,7 @@ public void onCameraMove(Point newCameraPosition) {

public void onMapDrag(Point newCameraPosition) {
if (isLocationLockEnabled()) {
Log.d(TAG, "User dragged map. Disabling location lock");
Timber.d("User dragged map. Disabling location lock");
locationLockChangeRequests.onNext(false);
}
}
Expand All @@ -191,6 +197,10 @@ public void onLocationLockClick() {
locationLockChangeRequests.onNext(!isLocationLockEnabled());
}

LiveData<Event<Nil>> getShowMapTypeSelectorRequests() {
return showMapTypeSelectorRequests;
}

static class CameraUpdate {

private Point center;
Expand Down
Expand Up @@ -70,6 +70,12 @@ public interface MapAdapter {
/** Update map pins shown on map. */
void setMapPins(ImmutableSet<MapPin> pins);

/** Get current map type. */
int getMapType();

/** Update map type. */
void setMapType(int mapType);

/** Returns the bounds of the currently visibly viewport. */
LatLngBounds getViewport();
}
16 changes: 15 additions & 1 deletion gnd/src/main/java/com/google/android/gnd/ui/map/MapProvider.java
Expand Up @@ -17,13 +17,27 @@
package com.google.android.gnd.ui.map;

import androidx.fragment.app.Fragment;
import com.google.common.collect.ImmutableMap;
import io.reactivex.Single;

/** Common interface for various map provider libraries. */
/**
* Common interface for various map provider libraries.
*
* <p>Map Type refers to the basemap shown below map features and offline satellite imagery. It's
* called "map styles" in Mapbox and "basemaps" in Leaflet.
*/
public interface MapProvider {
void restore(Fragment fragment);

Fragment getFragment();

Single<MapAdapter> getMapAdapter();

int getMapType();

// TODO: Use ENUM instead of int with a superset of basemap types.
// https://github.com/google/ground-android/pull/406#discussion_r398726351
void setMapType(int mapType);

ImmutableMap<Integer, String> getMapTypes();
}
Expand Up @@ -54,17 +54,16 @@ class GoogleMapsMapAdapter implements MapAdapter {
private final GoogleMap map;
private final Context context;
private final MarkerIconFactory markerIconFactory;
private final PublishSubject<MapPin> markerClickSubject = PublishSubject.create();
private final PublishSubject<Point> dragInteractionSubject = PublishSubject.create();
private final BehaviorSubject<Point> cameraMoves = BehaviorSubject.create();

/**
* References to Google Maps SDK Markers present on the map. Used to sync and update markers with
* current view and data state.
*/
private Set<Marker> markers = new HashSet<>();

private final PublishSubject<MapPin> markerClickSubject = PublishSubject.create();
private final PublishSubject<Point> dragInteractionSubject = PublishSubject.create();
private final BehaviorSubject<Point> cameraMoves = BehaviorSubject.create();

@Nullable private LatLng cameraTargetBeforeDrag;

public GoogleMapsMapAdapter(GoogleMap map, Context context, MarkerIconFactory markerIconFactory) {
Expand All @@ -86,6 +85,14 @@ public GoogleMapsMapAdapter(GoogleMap map, Context context, MarkerIconFactory ma
onCameraMove();
}

private static Point fromLatLng(LatLng latLng) {
return Point.newBuilder().setLatitude(latLng.latitude).setLongitude(latLng.longitude).build();
}

private static LatLng toLatLng(Point point) {
return new LatLng(point.getLatitude(), point.getLongitude());
}

private boolean onMarkerClick(Marker marker) {
if (map.getUiSettings().isZoomGesturesEnabled()) {
markerClickSubject.onNext((MapPin) marker.getTag());
Expand Down Expand Up @@ -187,12 +194,14 @@ public void setMapPins(ImmutableSet<MapPin> updatedPins) {
stream(pinsToAdd).forEach(this::addMapPin);
}

private static Point fromLatLng(LatLng latLng) {
return Point.newBuilder().setLatitude(latLng.latitude).setLongitude(latLng.longitude).build();
@Override
public int getMapType() {
return map.getMapType();
}

private static LatLng toLatLng(Point point) {
return new LatLng(point.getLatitude(), point.getLongitude());
@Override
public void setMapType(int mapType) {
map.setMapType(mapType);
}

private void removeMarker(Marker marker) {
Expand Down
Expand Up @@ -18,11 +18,15 @@

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gnd.ui.MarkerIconFactory;
import com.google.android.gnd.ui.map.MapAdapter;
import com.google.android.gnd.ui.map.MapProvider;
import com.google.common.collect.ImmutableMap;
import io.reactivex.Single;
import io.reactivex.subjects.SingleSubject;
import java.util.HashMap;
import java.util.Map;

/** Ground map adapter implementation for Google Maps API. */
public class GoogleMapsMapProvider implements MapProvider {
Expand Down Expand Up @@ -68,4 +72,32 @@ public Fragment getFragment() {
public Single<MapAdapter> getMapAdapter() {
return map;
}

@Override
public int getMapType() {
if (map == null) {
throw new IllegalStateException("MapAdapter is null");
}
return map.getValue().getMapType();
}

@Override
public void setMapType(int mapType) {
if (map == null) {
throw new IllegalStateException("MapAdapter is null");
}
map.getValue().setMapType(mapType);
}

@Override
public ImmutableMap<Integer, String> getMapTypes() {
// TODO: i18n
Map<Integer, String> map = new HashMap<>();
map.put(GoogleMap.MAP_TYPE_NONE, "None");
map.put(GoogleMap.MAP_TYPE_NORMAL, "Normal");
map.put(GoogleMap.MAP_TYPE_SATELLITE, "Satellite");
map.put(GoogleMap.MAP_TYPE_TERRAIN, "Terrain");
map.put(GoogleMap.MAP_TYPE_HYBRID, "Hybrid");
return ImmutableMap.copyOf(map);
}
}
29 changes: 29 additions & 0 deletions gnd/src/main/res/drawable/map_layers.xml
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>

<!--
~ Copyright 2020 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path android:pathData="M0 0h24v24H0V0z" />
<path
android:fillColor="#000000"
android:pathData="M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16zm0-11.47L17.74 9 12 13.47 6.26 9 12 4.53z" />
</vector>

0 comments on commit 325491c

Please sign in to comment.