From 734fbde1b6981a39371c30f846e4e22d2b2e7c3c Mon Sep 17 00:00:00 2001 From: Scott Olsen Date: Thu, 8 Aug 2019 22:21:18 -0400 Subject: [PATCH 1/6] Offline Imagery: Add basemap selector UI This change adds a fragment, view model, layout, and other components to support a distinct UI for selecting offline imagery. Keeping this UI separate ensures we can manage the feature independently, as well as gives users a better experience (the basemap selection map doesn't render markers, as these are noisy in the context). This particular commit doesn't implement any interesting functionality. It is effectively just a shell for the basemap selection UI. As a minimal first step, we do render the map in this commit. This UI is in the nav graph, so users can utilize the android navigation back arrow to return to the main home screen. --- .../android/gnd/MainActivityModule.java | 6 ++ .../BasemapSelectorFragment.java | 74 +++++++++++++++++++ .../BasemapSelectorModule.java | 16 ++++ .../BasemapSelectorViewModel.java | 12 +++ .../android/gnd/ui/common/Navigator.java | 8 ++ .../gnd/ui/common/ViewModelModule.java | 6 ++ .../gnd/ui/home/HomeScreenFragment.java | 8 ++ .../gnd/ui/home/HomeScreenViewModel.java | 4 + .../main/res/layout/basemap_selector_frag.xml | 32 ++++++++ gnd/src/main/res/menu/nav_drawer_menu.xml | 4 + gnd/src/main/res/navigation/nav_graph.xml | 14 ++++ gnd/src/main/res/values/strings.xml | 1 + 12 files changed, 185 insertions(+) create mode 100644 gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java create mode 100644 gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorModule.java create mode 100644 gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java create mode 100644 gnd/src/main/res/layout/basemap_selector_frag.xml diff --git a/gnd/src/main/java/com/google/android/gnd/MainActivityModule.java b/gnd/src/main/java/com/google/android/gnd/MainActivityModule.java index 1c32425d0c..96a48b06aa 100644 --- a/gnd/src/main/java/com/google/android/gnd/MainActivityModule.java +++ b/gnd/src/main/java/com/google/android/gnd/MainActivityModule.java @@ -19,6 +19,8 @@ import androidx.appcompat.app.AppCompatActivity; import com.google.android.gnd.inject.ActivityScoped; import com.google.android.gnd.inject.FragmentScoped; +import com.google.android.gnd.ui.basemapselector.BasemapSelectorFragment; +import com.google.android.gnd.ui.basemapselector.BasemapSelectorModule; import com.google.android.gnd.ui.editrecord.EditRecordFragment; import com.google.android.gnd.ui.editrecord.EditRecordModule; import com.google.android.gnd.ui.home.AddFeatureDialogFragment; @@ -89,4 +91,8 @@ public abstract class MainActivityModule { @FragmentScoped @ContributesAndroidInjector(modules = EditRecordModule.class) abstract EditRecordFragment editRecordFragmentInjector(); + + @FragmentScoped + @ContributesAndroidInjector(modules = BasemapSelectorModule.class) + abstract BasemapSelectorFragment basemapSelectorFragmentInjector(); } diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java new file mode 100644 index 0000000000..17167aeb68 --- /dev/null +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java @@ -0,0 +1,74 @@ +package com.google.android.gnd.ui.basemapselector; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.android.gnd.MainViewModel; +import com.google.android.gnd.R; +import com.google.android.gnd.databinding.BasemapSelectorFragBinding; +import com.google.android.gnd.inject.ActivityScoped; +import com.google.android.gnd.ui.common.AbstractFragment; +import com.google.android.gnd.ui.map.MapProvider; +import com.google.android.gnd.ui.map.MapProvider.MapAdapter; + +import javax.inject.Inject; + +import io.reactivex.Single; + +@ActivityScoped +public class BasemapSelectorFragment extends AbstractFragment { + + private static final String TAG = BasemapSelectorFragment.class.getName(); + private static final String MAP_FRAGMENT_KEY = MapProvider.class.getName() + "#fragment"; + private BasemapSelectorViewModel viewModel; + private MainViewModel mainViewModel; + + @Inject MapProvider mapProvider; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = getViewModel(BasemapSelectorViewModel.class); + mainViewModel = getViewModel(MainViewModel.class); + Single mapAdapter = mapProvider.getMapAdapter(); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + BasemapSelectorFragBinding binding = + BasemapSelectorFragBinding.inflate(inflater, container, false); + binding.setViewModel(viewModel); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull 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_KEY)); + } + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mainViewModel.getWindowInsets().observe(this, this::onApplyWindowInsets); + // viewModel.getWindowInsets().observe(this, this::onApplyWindowInsets); + } + + private void onApplyWindowInsets(WindowInsetsCompat windowInsets) { + ViewCompat.onApplyWindowInsets(mapProvider.getFragment().getView(), windowInsets); + // TODO: Once we add control UI elements, translate them based on the inset to avoid collision + // with the android navbar. + } +} diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorModule.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorModule.java new file mode 100644 index 0000000000..ab4358966b --- /dev/null +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorModule.java @@ -0,0 +1,16 @@ +package com.google.android.gnd.ui.basemapselector; + +import androidx.fragment.app.Fragment; + +import com.google.android.gnd.inject.FragmentScoped; + +import dagger.Binds; +import dagger.Module; + +@Module +public abstract class BasemapSelectorModule { + + @Binds + @FragmentScoped + abstract Fragment fragment(BasemapSelectorFragment fragment); +} diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java new file mode 100644 index 0000000000..3cd0ee25a1 --- /dev/null +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java @@ -0,0 +1,12 @@ +package com.google.android.gnd.ui.basemapselector; + +import androidx.lifecycle.ViewModel; + +import javax.inject.Inject; + +public class BasemapSelectorViewModel extends ViewModel { + @Inject + BasemapSelectorViewModel() {} + + // TODO: Implement view model. +} diff --git a/gnd/src/main/java/com/google/android/gnd/ui/common/Navigator.java b/gnd/src/main/java/com/google/android/gnd/ui/common/Navigator.java index 4e45befb0d..f2a6f6d17c 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/common/Navigator.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/common/Navigator.java @@ -69,6 +69,14 @@ public void showRecordDetails(String projectId, String featureId, String recordI navigate(HomeScreenFragmentDirections.showRecordDetails(projectId, featureId, recordId)); } + /** + * Navigates from a {@link com.google.android.gnd.ui.home.HomeScreenFragment} to a {@link + * com.google.android.gnd.ui.basemapselector.BasemapSelectorFragment}. + */ + public void showBasemapSelector() { + navigate(HomeScreenFragmentDirections.showBasemapSelector()); + } + /** * Navigates from the {@link com.google.android.gnd.ui.home.HomeScreenFragment} to a {@link * com.google.android.gnd.ui.editrecord.EditRecordFragment} initialized with a new empty record diff --git a/gnd/src/main/java/com/google/android/gnd/ui/common/ViewModelModule.java b/gnd/src/main/java/com/google/android/gnd/ui/common/ViewModelModule.java index f17c81fc3b..42e9fab7ba 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/common/ViewModelModule.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/common/ViewModelModule.java @@ -19,6 +19,7 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import com.google.android.gnd.MainViewModel; +import com.google.android.gnd.ui.basemapselector.BasemapSelectorViewModel; import com.google.android.gnd.ui.editrecord.EditRecordViewModel; import com.google.android.gnd.ui.home.HomeScreenViewModel; import com.google.android.gnd.ui.home.featuresheet.FeatureSheetViewModel; @@ -38,6 +39,11 @@ public abstract class ViewModelModule { @ViewModelKey(MapContainerViewModel.class) abstract ViewModel bindMapContainerViewModel(MapContainerViewModel viewModel); + @Binds + @IntoMap + @ViewModelKey(BasemapSelectorViewModel.class) + abstract ViewModel bindBasemapSelectorViewModel(BasemapSelectorViewModel viewModel); + @Binds @IntoMap @ViewModelKey(MainViewModel.class) diff --git a/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenFragment.java index 67105d9e0b..50bc26a51a 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenFragment.java @@ -224,6 +224,10 @@ private void showProjectSelector() { ProjectSelectorDialogFragment.show(getFragmentManager()); } + private void showBasemapSelector() { + viewModel.showBasemapSelector(); + } + private void onApplyWindowInsets(WindowInsetsCompat insets) { statusBarScrim.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0); toolbarWrapper.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0); @@ -335,6 +339,10 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { showProjectSelector(); closeDrawer(); break; + case R.id.nav_offline_maps: + showBasemapSelector(); + closeDrawer(); + break; case R.id.nav_sign_out: authenticationManager.signOut(); break; diff --git a/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenViewModel.java b/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenViewModel.java index 3d3d596b37..ba0649d97c 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenViewModel.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/home/HomeScreenViewModel.java @@ -146,6 +146,10 @@ public void addRecord() { navigator.addRecord(feature.getProject().getId(), feature.getId(), selectedForm.getId()); } + public void showBasemapSelector() { + navigator.showBasemapSelector(); + } + /** * Reactivates the last active project, emitting true once loaded, or false if no project was * previously activated. diff --git a/gnd/src/main/res/layout/basemap_selector_frag.xml b/gnd/src/main/res/layout/basemap_selector_frag.xml new file mode 100644 index 0000000000..68a6190d25 --- /dev/null +++ b/gnd/src/main/res/layout/basemap_selector_frag.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/gnd/src/main/res/menu/nav_drawer_menu.xml b/gnd/src/main/res/menu/nav_drawer_menu.xml index 15c30d38a5..1b9f3a2ee4 100644 --- a/gnd/src/main/res/menu/nav_drawer_menu.xml +++ b/gnd/src/main/res/menu/nav_drawer_menu.xml @@ -21,6 +21,10 @@ android:id="@+id/nav_join_project" android:icon="@drawable/ic_menu_project" android:title="@string/select_project_menu_item"/> + + + + + Google Play Services installation failed Sign in unsuccessful Sign out + Offline Maps From c3aef4cdadd657eaf927bcd7b2b2f14563714fdc Mon Sep 17 00:00:00 2001 From: Scott Olsen Date: Wed, 14 Aug 2019 10:59:51 -0400 Subject: [PATCH 2/6] Offline Imagery: Add java doc to BasemapSelector Added brief javadoc comments describing the purpose and intentions behind the BasemapSelectorFragement and BasemapSelectorViewModel. --- .../gnd/ui/basemapselector/BasemapSelectorFragment.java | 6 +++++- .../gnd/ui/basemapselector/BasemapSelectorViewModel.java | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java index 17167aeb68..63df3d3ce5 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java @@ -22,6 +22,11 @@ import io.reactivex.Single; +/** + * This fragment represents a basemap selector for the application's offline imagery functionality. + * The fragment is presented as an immersive experience in which users can select portions of a + * basemap for offline viewing. Upon selection, basemap tiles are saved to the device. + */ @ActivityScoped public class BasemapSelectorFragment extends AbstractFragment { @@ -63,7 +68,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mainViewModel.getWindowInsets().observe(this, this::onApplyWindowInsets); - // viewModel.getWindowInsets().observe(this, this::onApplyWindowInsets); } private void onApplyWindowInsets(WindowInsetsCompat windowInsets) { diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java index 3cd0ee25a1..a178496b5b 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorViewModel.java @@ -4,6 +4,13 @@ import javax.inject.Inject; +/** + * This view model is responsible for managing state for the {@link BasemapSelectorFragment}. + * Together, they constitute a basemap selector that users can interact with to select portions of a + * basemap for offline viewing. Among other things, this view model is responsible for receiving + * requests to download basemap files and for scheduling those requests with an {@link + * com.google.android.gnd.workers.FileDownloadWorker}. + */ public class BasemapSelectorViewModel extends ViewModel { @Inject BasemapSelectorViewModel() {} From 0b407ec25afe793447e5e1694094d43dec7abe51 Mon Sep 17 00:00:00 2001 From: Scott Olsen Date: Wed, 14 Aug 2019 12:03:55 -0400 Subject: [PATCH 3/6] Offline Imagery: Remove comment in Basemap layout Removed an extraneous comment in the basemap selector layout; also deleted an excess newline. --- gnd/src/main/res/layout/basemap_selector_frag.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/gnd/src/main/res/layout/basemap_selector_frag.xml b/gnd/src/main/res/layout/basemap_selector_frag.xml index 68a6190d25..4dddcb085f 100644 --- a/gnd/src/main/res/layout/basemap_selector_frag.xml +++ b/gnd/src/main/res/layout/basemap_selector_frag.xml @@ -2,7 +2,6 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - @@ -20,7 +19,6 @@ android:gravity="center" app:layout_behavior="com.google.android.gnd.ui.home.mapcontainer.MapLayoutBehavior"> - Date: Wed, 14 Aug 2019 12:05:15 -0400 Subject: [PATCH 4/6] Offline Imagery: Use string resource Use a reference to the offline maps string resource in the nav graph. Additionally, I've converted the word "maps" to lowercase. --- gnd/src/main/res/navigation/nav_graph.xml | 2 +- gnd/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gnd/src/main/res/navigation/nav_graph.xml b/gnd/src/main/res/navigation/nav_graph.xml index c9151343d6..a437fa39d7 100644 --- a/gnd/src/main/res/navigation/nav_graph.xml +++ b/gnd/src/main/res/navigation/nav_graph.xml @@ -75,7 +75,7 @@ Google Play Services installation failed Sign in unsuccessful Sign out - Offline Maps + Offline maps From 2626d08ee3f2050258f2a9bf4d2b988d820a8583 Mon Sep 17 00:00:00 2001 From: Gino Miceli <228050+gino-m@users.noreply.github.com> Date: Wed, 14 Aug 2019 17:20:55 -0400 Subject: [PATCH 5/6] Suggestions for wording of fragment javadoc I misused the term "immersive" in my original suggestionI think --- .../gnd/ui/basemapselector/BasemapSelectorFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java index 63df3d3ce5..d797ded097 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java @@ -23,9 +23,9 @@ import io.reactivex.Single; /** - * This fragment represents a basemap selector for the application's offline imagery functionality. - * The fragment is presented as an immersive experience in which users can select portions of a - * basemap for offline viewing. Upon selection, basemap tiles are saved to the device. + * Allows the user to select specific areas on a map for offline display. Users can toggle sections of + * the to add remove imagery. Upon selection, basemap tiles are queued for download. When deselected, + * they are removed from the device. */ @ActivityScoped public class BasemapSelectorFragment extends AbstractFragment { From c255e0777412b3c4e4682cf1cf0df1f72b028938 Mon Sep 17 00:00:00 2001 From: Scott Olsen Date: Wed, 14 Aug 2019 17:30:11 -0400 Subject: [PATCH 6/6] Offline Imagery: Small Javadoc typo edits --- .../gnd/ui/basemapselector/BasemapSelectorFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java index d797ded097..2204bcd482 100644 --- a/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java +++ b/gnd/src/main/java/com/google/android/gnd/ui/basemapselector/BasemapSelectorFragment.java @@ -24,8 +24,8 @@ /** * Allows the user to select specific areas on a map for offline display. Users can toggle sections of - * the to add remove imagery. Upon selection, basemap tiles are queued for download. When deselected, - * they are removed from the device. + * the map to add or remove imagery. Upon selection, basemap tiles are queued for download. When + * deselected, they are removed from the device. */ @ActivityScoped public class BasemapSelectorFragment extends AbstractFragment {