From 4390a14694ffd142b554f05e9d2bf5d16199ad18 Mon Sep 17 00:00:00 2001 From: Akshay Nandwana Date: Wed, 8 Jan 2025 17:06:23 +0530 Subject: [PATCH 1/3] remove file --- ground/src/debug/local/google-services.json | 34 ------------------- .../android/ground/system/NetworkManager.kt | 14 +++++--- .../selector/OfflineAreaSelectorFragment.kt | 13 +++++++ .../selector/OfflineAreaSelectorViewModel.kt | 11 ++++++ ground/src/main/res/values/strings.xml | 1 + .../ui/tos/TermsOfServiceFragmentTest.kt | 7 ++++ 6 files changed, 42 insertions(+), 38 deletions(-) delete mode 100644 ground/src/debug/local/google-services.json diff --git a/ground/src/debug/local/google-services.json b/ground/src/debug/local/google-services.json deleted file mode 100644 index e5c36e3c29..0000000000 --- a/ground/src/debug/local/google-services.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "project_info": { - "project_number": "334087903719", - "project_id": "local", - "storage_bucket": "local.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:334087903719:android:277f0bf3c1887739f40542", - "android_client_info": { - "package_name": "com.google.android.ground" - } - }, - "oauth_client": [ - { - "client_id": "local.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBKXBjsrQzgpbhgP9RwuOG6xWww83eKi_Q" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt b/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt index 35e728b6ed..96783b4d7e 100644 --- a/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt @@ -27,6 +27,7 @@ import javax.inject.Singleton import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import timber.log.Timber enum class NetworkStatus { AVAILABLE, @@ -61,11 +62,16 @@ class NetworkManager @Inject constructor(@ApplicationContext private val context } } - /** Returns true iff the device has internet connectivity, false otherwise. */ + /** Returns true if the device has internet connectivity, false otherwise. */ @RequiresPermission("android.permission.ACCESS_NETWORK_STATE") fun isNetworkConnected(): Boolean { - val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val networkInfo = cm.activeNetworkInfo - return networkInfo?.isConnected ?: false + val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork = connectivityManager.activeNetwork + val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) + + val isConnected = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET) ?: false + Timber.d("Network connected: $isConnected") + return isConnected } } diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorFragment.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorFragment.kt index 7c2664ca06..b4e138dec3 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorFragment.kt @@ -26,15 +26,18 @@ import androidx.compose.runtime.remember import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import com.google.android.ground.R import com.google.android.ground.databinding.OfflineAreaSelectorFragBinding import com.google.android.ground.ui.common.AbstractMapContainerFragment import com.google.android.ground.ui.common.BaseMapViewModel +import com.google.android.ground.ui.common.EphemeralPopups import com.google.android.ground.ui.common.MapConfig import com.google.android.ground.ui.home.mapcontainer.HomeScreenMapContainerViewModel import com.google.android.ground.ui.map.MapFragment import com.google.android.ground.ui.map.MapType import com.google.android.ground.ui.theme.AppTheme import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlinx.coroutines.launch /** Map UI used to select areas for download and viewing offline. */ @@ -44,6 +47,8 @@ class OfflineAreaSelectorFragment : AbstractMapContainerFragment() { private lateinit var viewModel: OfflineAreaSelectorViewModel private lateinit var mapContainerViewModel: HomeScreenMapContainerViewModel + @Inject lateinit var popups: EphemeralPopups + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mapContainerViewModel = getViewModel(HomeScreenMapContainerViewModel::class.java) @@ -81,6 +86,14 @@ class OfflineAreaSelectorFragment : AbstractMapContainerFragment() { } } } + + lifecycleScope.launch { + viewModel.showPopupEvent.collect { show -> + if (show) { + popups.ErrorPopup().show(R.string.connect_to_download_message) + } + } + } } override fun getMapConfig(): MapConfig = diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt index 22e9d39454..69a87a903a 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt @@ -25,6 +25,7 @@ import com.google.android.ground.repository.MapStateRepository import com.google.android.ground.repository.OfflineAreaRepository import com.google.android.ground.repository.SurveyRepository import com.google.android.ground.system.LocationManager +import com.google.android.ground.system.NetworkManager import com.google.android.ground.system.PermissionsManager import com.google.android.ground.system.SettingsManager import com.google.android.ground.ui.common.BaseMapViewModel @@ -56,6 +57,7 @@ internal constructor( settingsManager: SettingsManager, permissionsManager: PermissionsManager, locationOfInterestRepository: LocationOfInterestRepository, + private val networkManager: NetworkManager, ) : BaseMapViewModel( locationManager, @@ -79,7 +81,16 @@ internal constructor( private val _navigate = MutableSharedFlow(replay = 0) val navigate = _navigate.asSharedFlow() + private val _showPopupEvent = MutableSharedFlow() + val showPopupEvent = _showPopupEvent.asSharedFlow() + fun onDownloadClick() { + val isConnected = networkManager.isNetworkConnected() + if (!isConnected) { + viewModelScope.launch { _showPopupEvent.emit(true) } + return + } + if (viewport == null) { // Download was likely clicked before map was ready. return diff --git a/ground/src/main/res/values/strings.xml b/ground/src/main/res/values/strings.xml index 81343f21d4..77d6c74df4 100644 --- a/ground/src/main/res/values/strings.xml +++ b/ground/src/main/res/values/strings.xml @@ -56,6 +56,7 @@ Select area No map imagery downloaded for offline use + You are offline. Connect to download offline map imager Signing in Build %s diff --git a/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt index 82e04c7dac..2f2beede2f 100644 --- a/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt @@ -31,8 +31,10 @@ import com.google.android.ground.launchFragmentInHiltContainer import com.google.android.ground.launchFragmentWithNavController import com.google.android.ground.model.TermsOfService import com.google.android.ground.repository.TermsOfServiceRepository +import com.google.android.ground.system.NetworkManager import com.google.common.truth.Truth.assertThat import com.sharedtest.persistence.remote.FakeRemoteDataStore +import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidTest import javax.inject.Inject import org.hamcrest.BaseMatcher @@ -41,6 +43,8 @@ import org.hamcrest.Matcher import org.hamcrest.Matchers.not import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner @HiltAndroidTest @@ -53,6 +57,8 @@ class TermsOfServiceFragmentTest : BaseHiltTest() { @Inject lateinit var viewModel: TermsOfServiceViewModel private lateinit var navController: NavController + @BindValue @Mock lateinit var networkManager: NetworkManager + private fun withHtml(html: String): Matcher = object : BaseMatcher() { override fun describeTo(description: Description?) { @@ -77,6 +83,7 @@ class TermsOfServiceFragmentTest : BaseHiltTest() { @Test fun termsOfServiceText_shouldBeDisplayed() { + whenever(networkManager.isNetworkConnected()).thenReturn(true) launchFragmentInHiltContainer(bundleOf(Pair("isViewOnly", false))) onView(withId(R.id.termsText)) From 63524cfe2a8f39965d9a5f0870b55a71bab3dd5d Mon Sep 17 00:00:00 2001 From: Akshay Nandwana Date: Wed, 8 Jan 2025 17:07:28 +0530 Subject: [PATCH 2/3] added local file back --- ground/src/debug/local/google-services.json | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ground/src/debug/local/google-services.json diff --git a/ground/src/debug/local/google-services.json b/ground/src/debug/local/google-services.json new file mode 100644 index 0000000000..e5c36e3c29 --- /dev/null +++ b/ground/src/debug/local/google-services.json @@ -0,0 +1,34 @@ +{ + "project_info": { + "project_number": "334087903719", + "project_id": "local", + "storage_bucket": "local.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:334087903719:android:277f0bf3c1887739f40542", + "android_client_info": { + "package_name": "com.google.android.ground" + } + }, + "oauth_client": [ + { + "client_id": "local.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBKXBjsrQzgpbhgP9RwuOG6xWww83eKi_Q" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file From 93a37297941512d6c777a8018c6ee647698bbe2d Mon Sep 17 00:00:00 2001 From: Akshay Nandwana Date: Wed, 8 Jan 2025 17:10:22 +0530 Subject: [PATCH 3/3] nit revert --- .../google/android/ground/system/NetworkManager.kt | 14 ++++---------- .../ground/ui/tos/TermsOfServiceFragmentTest.kt | 7 ------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt b/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt index 96783b4d7e..35e728b6ed 100644 --- a/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/NetworkManager.kt @@ -27,7 +27,6 @@ import javax.inject.Singleton import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import timber.log.Timber enum class NetworkStatus { AVAILABLE, @@ -62,16 +61,11 @@ class NetworkManager @Inject constructor(@ApplicationContext private val context } } - /** Returns true if the device has internet connectivity, false otherwise. */ + /** Returns true iff the device has internet connectivity, false otherwise. */ @RequiresPermission("android.permission.ACCESS_NETWORK_STATE") fun isNetworkConnected(): Boolean { - val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetwork = connectivityManager.activeNetwork - val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) - - val isConnected = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET) ?: false - Timber.d("Network connected: $isConnected") - return isConnected + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val networkInfo = cm.activeNetworkInfo + return networkInfo?.isConnected ?: false } } diff --git a/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt b/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt index 2f2beede2f..82e04c7dac 100644 --- a/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt +++ b/ground/src/test/java/com/google/android/ground/ui/tos/TermsOfServiceFragmentTest.kt @@ -31,10 +31,8 @@ import com.google.android.ground.launchFragmentInHiltContainer import com.google.android.ground.launchFragmentWithNavController import com.google.android.ground.model.TermsOfService import com.google.android.ground.repository.TermsOfServiceRepository -import com.google.android.ground.system.NetworkManager import com.google.common.truth.Truth.assertThat import com.sharedtest.persistence.remote.FakeRemoteDataStore -import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidTest import javax.inject.Inject import org.hamcrest.BaseMatcher @@ -43,8 +41,6 @@ import org.hamcrest.Matcher import org.hamcrest.Matchers.not import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner @HiltAndroidTest @@ -57,8 +53,6 @@ class TermsOfServiceFragmentTest : BaseHiltTest() { @Inject lateinit var viewModel: TermsOfServiceViewModel private lateinit var navController: NavController - @BindValue @Mock lateinit var networkManager: NetworkManager - private fun withHtml(html: String): Matcher = object : BaseMatcher() { override fun describeTo(description: Description?) { @@ -83,7 +77,6 @@ class TermsOfServiceFragmentTest : BaseHiltTest() { @Test fun termsOfServiceText_shouldBeDisplayed() { - whenever(networkManager.isNetworkConnected()).thenReturn(true) launchFragmentInHiltContainer(bundleOf(Pair("isViewOnly", false))) onView(withId(R.id.termsText))