diff --git a/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerFragment.java index 296f10efbf..dd3864706f 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerFragment.java @@ -22,12 +22,10 @@ import static java8.util.stream.StreamSupport.stream; import android.os.Bundle; -import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; @@ -37,39 +35,32 @@ import com.google.android.gnd.model.feature.Feature; import com.google.android.gnd.model.feature.Point; import com.google.android.gnd.model.feature.PointFeature; -import com.google.android.gnd.persistence.mbtiles.MbtilesFootprintParser; import com.google.android.gnd.repository.MapsRepository; import com.google.android.gnd.rx.BooleanOrError; import com.google.android.gnd.rx.Loadable; import com.google.android.gnd.system.PermissionsManager.PermissionDeniedException; import com.google.android.gnd.system.SettingsManager.SettingsChangeRequestCanceled; -import com.google.android.gnd.ui.common.AbstractFragment; +import com.google.android.gnd.ui.common.AbstractMapViewerFragment; import com.google.android.gnd.ui.home.BottomSheetState; import com.google.android.gnd.ui.home.HomeScreenViewModel; import com.google.android.gnd.ui.home.mapcontainer.MapContainerViewModel.Mode; -import com.google.android.gnd.ui.map.MapAdapter; -import com.google.android.gnd.ui.map.MapProvider; -import com.google.android.gnd.ui.util.FileUtil; +import com.google.android.gnd.ui.map.MapFragment; +import com.google.android.gnd.ui.map.MapType; import com.google.common.collect.ImmutableList; import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.Single; import java8.util.Optional; import javax.inject.Inject; import timber.log.Timber; /** Main app view, displaying the map and related controls (center cross-hairs, add button, etc). */ @AndroidEntryPoint -public class MapContainerFragment extends AbstractFragment { +public class MapContainerFragment extends AbstractMapViewerFragment { - private static final String MAP_FRAGMENT_KEY = MapProvider.class.getName() + "#fragment"; - - @Inject FileUtil fileUtil; - @Inject MbtilesFootprintParser mbtilesFootprintParser; - @Inject MapProvider mapProvider; @Inject MapsRepository mapsRepository; PolygonDrawingViewModel polygonDrawingViewModel; private MapContainerViewModel mapContainerViewModel; private HomeScreenViewModel homeScreenViewModel; + private MapContainerFragBinding binding; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -79,38 +70,30 @@ public void onCreate(@Nullable Bundle savedInstanceState) { FeatureRepositionViewModel featureRepositionViewModel = getViewModel(FeatureRepositionViewModel.class); polygonDrawingViewModel = getViewModel(PolygonDrawingViewModel.class); - Single mapAdapter = mapProvider.getMapAdapter(); - mapAdapter.as(autoDisposable(this)).subscribe(this::onMapReady); - mapAdapter - .toObservable() - .flatMap(MapAdapter::getMapPinClicks) + getMapFragment() + .getMapPinClicks() .as(disposeOnDestroy(this)) .subscribe(mapContainerViewModel::onMarkerClick); - mapAdapter - .toObservable() - .flatMap(MapAdapter::getMapPinClicks) + getMapFragment() + .getMapPinClicks() .as(disposeOnDestroy(this)) .subscribe(homeScreenViewModel::onMarkerClick); - mapAdapter - .toObservable() - .flatMap(MapAdapter::getFeatureClicks) + getMapFragment() + .getFeatureClicks() .as(disposeOnDestroy(this)) .subscribe(homeScreenViewModel::onFeatureClick); - mapAdapter - .toFlowable() - .flatMap(MapAdapter::getStartDragEvents) + getMapFragment() + .getStartDragEvents() .onBackpressureLatest() .as(disposeOnDestroy(this)) .subscribe(__ -> mapContainerViewModel.onMapDrag()); - mapAdapter - .toFlowable() - .flatMap(MapAdapter::getCameraMovedEvents) + getMapFragment() + .getCameraMovedEvents() .onBackpressureLatest() .as(disposeOnDestroy(this)) .subscribe(mapContainerViewModel::onCameraMove); - mapAdapter - .toObservable() - .flatMap(MapAdapter::getTileProviders) + getMapFragment() + .getTileProviders() .as(disposeOnDestroy(this)) .subscribe(mapContainerViewModel::queueTileProvider); @@ -138,7 +121,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - MapContainerFragBinding binding = MapContainerFragBinding.inflate(inflater, container, false); + binding = MapContainerFragBinding.inflate(inflater, container, false); binding.setViewModel(mapContainerViewModel); binding.setHomeScreenViewModel(homeScreenViewModel); binding.setLifecycleOwner(this); @@ -148,18 +131,17 @@ public View onCreateView( @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - disableAddFeatureBtn(); - - if (savedInstanceState == null) { - replaceFragment(R.id.map, mapProvider.getFragment()); - } else { - mapProvider.restore(restoreChildFragment(savedInstanceState, MAP_FRAGMENT_KEY)); - } } - private void onMapReady(MapAdapter map) { + @Override + protected void onMapReady(MapFragment map) { Timber.d("MapAdapter ready. Updating subscriptions"); + + // Custom views rely on the same instance of MapFragment. That couldn't be injected via Dagger. + // Hence, initializing them here instead of inflating in layout. + attachCustomViews(map); + mapContainerViewModel.setLocationLockEnabled(true); polygonDrawingViewModel.setLocationLockEnabled(true); @@ -182,19 +164,32 @@ private void onMapReady(MapAdapter map) { map.setMapType(mapsRepository.getSavedMapType()); } + private void attachCustomViews(MapFragment map) { + FeatureRepositionView repositionView = new FeatureRepositionView(getContext(), map); + mapContainerViewModel.getMoveFeatureVisibility().observe(this, repositionView::setVisibility); + binding.mapOverlay.addView(repositionView); + + PolygonDrawingView polygonDrawingView = new PolygonDrawingView(getContext(), map); + mapContainerViewModel + .isAddPolygonButtonVisible() + .observe(this, polygonDrawingView::setVisibility); + binding.mapOverlay.addView(polygonDrawingView); + } + private void showMapTypeSelectorDialog() { - ImmutableList> mapTypes = mapProvider.getMapTypes(); - ImmutableList typeNos = stream(mapTypes).map(p -> p.first).collect(toImmutableList()); - int selectedIdx = typeNos.indexOf(mapProvider.getMapType()); - String[] labels = stream(mapTypes).map(p -> p.second).toArray(String[]::new); + ImmutableList mapTypes = getMapFragment().getAvailableMapTypes(); + ImmutableList mapTypeValues = + stream(mapTypes).map(MapType::getType).collect(toImmutableList()); + int selectedIdx = mapTypeValues.indexOf(getMapFragment().getMapType()); + String[] labels = stream(mapTypes).map(p -> getString(p.getLabelId())).toArray(String[]::new); new AlertDialog.Builder(requireContext()) .setTitle(R.string.select_map_type) .setSingleChoiceItems( labels, selectedIdx, (dialog, which) -> { - int mapType = typeNos.get(which); - mapProvider.setMapType(mapType); + int mapType = mapTypeValues.get(which); + getMapFragment().setMapType(mapType); mapsRepository.saveMapType(mapType); dialog.dismiss(); }) @@ -227,7 +222,7 @@ private void moveToNewPosition(Point point) { homeScreenViewModel.updateFeature(newFeature); } - private void onBottomSheetStateChange(BottomSheetState state, MapAdapter map) { + private void onBottomSheetStateChange(BottomSheetState state, MapFragment map) { mapContainerViewModel.setSelectedFeature(state.getFeature()); switch (state.getVisibility()) { case VISIBLE: @@ -271,7 +266,7 @@ private void disableAddFeatureBtn() { mapContainerViewModel.setFeatureButtonBackgroundTint(R.color.colorGrey500); } - private void onLocationLockStateChange(BooleanOrError result, MapAdapter map) { + private void onLocationLockStateChange(BooleanOrError result, MapFragment map) { result.error().ifPresent(this::onLocationLockError); if (result.isTrue()) { Timber.d("Location lock enabled"); @@ -295,7 +290,7 @@ private void showUserActionFailureMessage(@StringRes int resId) { Toast.makeText(getContext(), resId, Toast.LENGTH_LONG).show(); } - private void onCameraUpdate(MapContainerViewModel.CameraUpdate update, MapAdapter map) { + private void onCameraUpdate(MapContainerViewModel.CameraUpdate update, MapFragment map) { Timber.v("Update camera: %s", update); if (update.getZoomLevel().isPresent()) { float zoomLevel = update.getZoomLevel().get(); @@ -308,12 +303,6 @@ private void onCameraUpdate(MapContainerViewModel.CameraUpdate update, MapAdapte } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - saveChildFragment(outState, mapProvider.getFragment(), MAP_FRAGMENT_KEY); - } - @Override public void onDestroy() { mapContainerViewModel.closeProviders(); diff --git a/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerViewModel.java b/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerViewModel.java index 17d6a9f403..4e6e9656e6 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerViewModel.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/home/mapcontainer/MapContainerViewModel.java @@ -104,7 +104,7 @@ public class MapContainerViewModel extends AbstractViewModel { @Hot(replays = true) private final MutableLiveData mapControlsVisibility = new MutableLiveData<>(VISIBLE); - private final MutableLiveData addPolygonVisible = new MutableLiveData<>(false); + private final MutableLiveData addPolygonVisible = new MutableLiveData<>(GONE); @Hot(replays = true) private final MutableLiveData moveFeaturesVisibility = new MutableLiveData<>(GONE); @@ -445,7 +445,7 @@ public void closeProviders() { public void setViewMode(Mode viewMode) { mapControlsVisibility.postValue(viewMode == Mode.DEFAULT ? VISIBLE : GONE); moveFeaturesVisibility.postValue(viewMode == Mode.REPOSITION ? VISIBLE : GONE); - addPolygonVisible.postValue(viewMode == Mode.DRAW_POLYGON); + addPolygonVisible.postValue(viewMode == Mode.DRAW_POLYGON ? VISIBLE : GONE); } public void onMapTypeButtonClicked() { @@ -472,7 +472,7 @@ public LiveData getMoveFeatureVisibility() { return moveFeaturesVisibility; } - public LiveData isAddPolygonButtonVisible() { + public LiveData isAddPolygonButtonVisible() { return addPolygonVisible; } diff --git a/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/selector/OfflineBaseMapSelectorFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/selector/OfflineBaseMapSelectorFragment.java index 43f441291a..0ecc29ad05 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/selector/OfflineBaseMapSelectorFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/selector/OfflineBaseMapSelectorFragment.java @@ -29,23 +29,18 @@ import com.google.android.gnd.R; import com.google.android.gnd.databinding.OfflineBaseMapSelectorFragBinding; import com.google.android.gnd.model.basemap.tile.TileSource; -import com.google.android.gnd.ui.common.AbstractFragment; +import com.google.android.gnd.ui.common.AbstractMapViewerFragment; import com.google.android.gnd.ui.common.EphemeralPopups; import com.google.android.gnd.ui.common.Navigator; -import com.google.android.gnd.ui.map.MapAdapter; -import com.google.android.gnd.ui.map.MapProvider; +import com.google.android.gnd.ui.map.MapFragment; import com.google.android.gnd.ui.offlinebasemap.selector.OfflineBaseMapSelectorViewModel.DownloadMessage; import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.Single; import javax.inject.Inject; @AndroidEntryPoint -public class OfflineBaseMapSelectorFragment extends AbstractFragment { - - private static final String MAP_FRAGMENT = MapProvider.class.getName() + "#fragment"; +public class OfflineBaseMapSelectorFragment extends AbstractMapViewerFragment { @Inject Navigator navigator; - @Inject MapProvider mapProvider; @Inject EphemeralPopups popups; private OfflineBaseMapSelectorViewModel viewModel; @@ -58,9 +53,6 @@ public static OfflineBaseMapSelectorFragment newInstance() { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = getViewModel(OfflineBaseMapSelectorViewModel.class); - // TODO: use the viewmodel - Single mapAdapter = mapProvider.getMapAdapter(); - mapAdapter.as(autoDisposable(this)).subscribe(this::onMapReady); viewModel.getDownloadMessages().observe(this, e -> e.ifUnhandled(this::onDownloadMessage)); } @@ -91,16 +83,7 @@ public View onCreateView( } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (savedInstanceState == null) { - replaceFragment(R.id.map, mapProvider.getFragment()); - } else { - mapProvider.restore(restoreChildFragment(savedInstanceState, MAP_FRAGMENT)); - } - } - - private void onMapReady(MapAdapter map) { + protected void onMapReady(MapFragment map) { viewModel .getRemoteTileSources() .map(tileSources -> stream(tileSources).map(TileSource::getUrl).collect(toImmutableList())) diff --git a/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/viewer/OfflineBaseMapViewerFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/viewer/OfflineBaseMapViewerFragment.java index be00b8dd30..05544c65c5 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/viewer/OfflineBaseMapViewerFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/offlinebasemap/viewer/OfflineBaseMapViewerFragment.java @@ -16,21 +16,17 @@ package com.google.android.gnd.ui.offlinebasemap.viewer; -import static com.google.android.gnd.rx.RxAutoDispose.autoDisposable; - import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.Nullable; import com.google.android.gnd.MainActivity; -import com.google.android.gnd.R; import com.google.android.gnd.databinding.OfflineBaseMapViewerFragBinding; import com.google.android.gnd.model.basemap.OfflineBaseMap; -import com.google.android.gnd.ui.common.AbstractFragment; +import com.google.android.gnd.ui.common.AbstractMapViewerFragment; import com.google.android.gnd.ui.common.Navigator; -import com.google.android.gnd.ui.map.MapAdapter; -import com.google.android.gnd.ui.map.MapProvider; +import com.google.android.gnd.ui.map.MapFragment; import dagger.hilt.android.AndroidEntryPoint; import javax.inject.Inject; @@ -39,15 +35,11 @@ * device. */ @AndroidEntryPoint -public class OfflineBaseMapViewerFragment extends AbstractFragment { - - private static final String MAP_FRAGMENT = MapProvider.class.getName() + "#fragment"; +public class OfflineBaseMapViewerFragment extends AbstractMapViewerFragment { @Inject Navigator navigator; - @Inject MapProvider mapProvider; private OfflineBaseMapViewerViewModel viewModel; - @Nullable private MapAdapter map; @Inject public OfflineBaseMapViewerFragment() {} @@ -59,7 +51,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { OfflineBaseMapViewerFragmentArgs.fromBundle(getArguments()); viewModel = getViewModel(OfflineBaseMapViewerViewModel.class); viewModel.loadOfflineArea(args); - mapProvider.getMapAdapter().as(autoDisposable(this)).subscribe(this::onMapReady); viewModel.getOfflineArea().observe(this, this::panMap); } @@ -77,26 +68,12 @@ public View onCreateView( } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (savedInstanceState == null) { - replaceFragment(R.id.map, mapProvider.getFragment()); - } else { - mapProvider.restore(restoreChildFragment(savedInstanceState, MAP_FRAGMENT)); - } - } - - private void onMapReady(MapAdapter map) { - this.map = map; + protected void onMapReady(MapFragment map) { map.disable(); } private void panMap(OfflineBaseMap offlineBaseMap) { - if (map == null) { - return; - } - - map.setBounds(offlineBaseMap.getBounds()); + getMapFragment().setBounds(offlineBaseMap.getBounds()); } /** Removes the area associated with this fragment from the user's device. */