diff --git a/app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt b/app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt
index b7d00f40ef..83762b2155 100644
--- a/app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt
+++ b/app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt
@@ -20,8 +20,13 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.google.android.ground.ExcludeFromJacocoGeneratedReport
import com.google.android.ground.R
+import com.google.android.ground.ui.theme.AppTheme
@Composable
fun ConfirmationDialog(
@@ -29,18 +34,44 @@ fun ConfirmationDialog(
@StringRes description: Int,
@StringRes dismissButtonText: Int = R.string.cancel,
@StringRes confirmButtonText: Int,
- signOutCallback: () -> Unit,
- dismissCallback: () -> Unit,
+ onConfirmClicked: () -> Unit,
) {
- AlertDialog(
- onDismissRequest = { dismissCallback() },
- title = { Text(text = stringResource(title)) },
- text = { Text(text = stringResource(description)) },
- dismissButton = {
- TextButton(onClick = { dismissCallback() }) { Text(text = stringResource(dismissButtonText)) }
- },
- confirmButton = {
- TextButton(onClick = { signOutCallback() }) { Text(text = stringResource(confirmButtonText)) }
- },
- )
+ val showRemoveWarningDialog = remember { mutableStateOf(true) }
+
+ fun onConfirm() {
+ showRemoveWarningDialog.value = false
+ onConfirmClicked()
+ }
+
+ fun onDismiss() {
+ showRemoveWarningDialog.value = false
+ }
+
+ if (showRemoveWarningDialog.value) {
+ AlertDialog(
+ onDismissRequest = { onDismiss() },
+ title = { Text(text = stringResource(title)) },
+ text = { Text(text = stringResource(description)) },
+ dismissButton = {
+ TextButton(onClick = { onDismiss() }) { Text(text = stringResource(dismissButtonText)) }
+ },
+ confirmButton = {
+ TextButton(onClick = { onConfirm() }) { Text(text = stringResource(confirmButtonText)) }
+ },
+ )
+ }
+}
+
+@Composable
+@Preview
+@ExcludeFromJacocoGeneratedReport
+fun PreviewConfirmationDialog() {
+ AppTheme {
+ ConfirmationDialog(
+ title = R.string.data_collection_cancellation_title,
+ description = R.string.data_collection_cancellation_description,
+ confirmButtonText = R.string.data_collection_cancellation_confirm_button,
+ onConfirmClicked = {},
+ )
+ }
}
diff --git a/app/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionFragment.kt b/app/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionFragment.kt
index a86d43d864..6a1a6f1f02 100644
--- a/app/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionFragment.kt
+++ b/app/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionFragment.kt
@@ -37,6 +37,7 @@ import com.google.android.ground.databinding.DataCollectionFragBinding
import com.google.android.ground.model.task.Task
import com.google.android.ground.ui.common.AbstractFragment
import com.google.android.ground.ui.common.BackPressListener
+import com.google.android.ground.ui.compose.ConfirmationDialog
import com.google.android.ground.ui.home.HomeScreenFragmentDirections
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
@@ -71,11 +72,7 @@ class DataCollectionFragment : AbstractFragment(), BackPressListener {
guideline = binding.progressBarGuideline
getAbstractActivity().setSupportActionBar(binding.dataCollectionToolbar)
- binding.dataCollectionToolbar.setNavigationOnClickListener {
- isNavigatingUp = true
- viewModel.clearDraft()
- findNavController().navigateUp()
- }
+ binding.dataCollectionToolbar.setNavigationOnClickListener { showExitWarningDialog() }
return binding.root
}
@@ -196,18 +193,39 @@ class DataCollectionFragment : AbstractFragment(), BackPressListener {
}
}
- override fun onBack(): Boolean =
+ override fun onBack(): Boolean {
if (viewPager.currentItem == 0) {
- isNavigatingUp = true
- // If the user is currently looking at the first step, allow the system to handle the
- // Back button. This calls finish() on this activity and pops the back stack.
- viewModel.clearDraft()
- false
+ showExitWarningDialog()
} else {
- // Otherwise, select the previous task.
viewModel.moveToPreviousTask()
- true
}
+ return true
+ }
+
+ private fun showExitWarningDialog() {
+ showConfirmationDialog {
+ isNavigatingUp = true
+ viewModel.clearDraft()
+ findNavController().navigateUp()
+ }
+ }
+
+ private fun showConfirmationDialog(onConfirm: () -> Unit) {
+ (view as ViewGroup).addView(
+ ComposeView(requireContext()).apply {
+ setContent {
+ AppTheme {
+ ConfirmationDialog(
+ title = R.string.data_collection_cancellation_title,
+ description = R.string.data_collection_cancellation_description,
+ confirmButtonText = R.string.data_collection_cancellation_confirm_button,
+ onConfirmClicked = { onConfirm() },
+ )
+ }
+ }
+ }
+ )
+ }
private companion object {
private const val PROGRESS_SCALE = 100
diff --git a/app/src/main/java/com/google/android/ground/ui/surveyselector/SurveySelectorFragment.kt b/app/src/main/java/com/google/android/ground/ui/surveyselector/SurveySelectorFragment.kt
index 28f6f17837..1fad6e5676 100644
--- a/app/src/main/java/com/google/android/ground/ui/surveyselector/SurveySelectorFragment.kt
+++ b/app/src/main/java/com/google/android/ground/ui/surveyselector/SurveySelectorFragment.kt
@@ -22,9 +22,6 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.navigation.fragment.findNavController
import com.google.android.ground.R
@@ -124,7 +121,7 @@ class SurveySelectorFragment : AbstractFragment(), BackPressListener {
object : PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
if (item.itemId == R.id.remove_offline_access_menu_item) {
- showRemoveConfirmationDialogs { viewModel.deleteSurvey(surveyId) }
+ showConfirmationDialog { viewModel.deleteSurvey(surveyId) }
return true
}
return false
@@ -135,30 +132,21 @@ class SurveySelectorFragment : AbstractFragment(), BackPressListener {
}
}
- @Suppress("UnrememberedMutableState")
- private fun showRemoveConfirmationDialogs(onConfirm: () -> Unit) {
- val dialogComposeView =
+ private fun showConfirmationDialog(onConfirm: () -> Unit) {
+ (view as ViewGroup).addView(
ComposeView(requireContext()).apply {
setContent {
- var showRemoveWarningDialog by mutableStateOf(true)
-
AppTheme {
- if (showRemoveWarningDialog) {
- ConfirmationDialog(
- title = R.string.remove_offline_access_warning_title,
- description = R.string.remove_offline_access_warning_dialog_body,
- confirmButtonText = R.string.remove_offline_access_warning_confirm_button,
- signOutCallback = {
- onConfirm()
- showRemoveWarningDialog = false
- },
- dismissCallback = { showRemoveWarningDialog = false },
- )
- }
+ ConfirmationDialog(
+ title = R.string.remove_offline_access_warning_title,
+ description = R.string.remove_offline_access_warning_dialog_body,
+ confirmButtonText = R.string.remove_offline_access_warning_confirm_button,
+ onConfirmClicked = { onConfirm() },
+ )
}
}
}
- (view as ViewGroup).addView(dialogComposeView)
+ )
}
private fun shouldExitApp(): Boolean =
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index a2e697845e..91bbe9c56d 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -195,4 +195,8 @@
Permitir ubicación
Permitir el uso compartido de la ubicación
Si no permite que Ground acceda a la ubicación de este dispositivo, no podrá continuar recopilando datos para este sitio.
+
+ ¿Quiere parar de recopilar datos?
+ Si sale, los datos que aún no hayan enviado se descartarán.
+ Confirmar
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0ec9ebc723..16e46c441a 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -70,7 +70,7 @@
Ajouter point
Finir
-
+
Carte routière
Satellite
Relief
@@ -195,4 +195,8 @@
Autoriser la localisation
Autoriser le partage de la localisation
Si vous n’autorisez pas Ground à accéder à la localisation de cet appareil, vous ne pourrez pas continuer à collecter des données pour ce site.
+
+ Arrêter la collecte de données ?
+ Si vous quittez, les données que vous n’avez pas encore soumises seront supprimées.
+ Confirmer
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 77c4e40af0..1aba439497 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -133,7 +133,7 @@
Localização do mapa:
Aviso de fim de sessão
Se você terminar sua sessão:\n \u2022 Quaisquer dados não sincronizados serão descartados.\n \u2022 Você não poderá iniciar sessão se estiver offline\n \u2022 Inquéritos offline não estarão disponíveis até você iniciar sessão novamente\n\nTem certeza de que deseja sair?
-
+
Amplie para começar a coletar dados
Inquérito apenas para leitura
Amplie até um local de coleta de dados para coletar informações
@@ -154,7 +154,7 @@
Sobre
Termos de serviço
Ground é um projeto comunitário de código aberto desenvolvido pelo Google e pela FAO no âmbito da “Forest Data Partnership”, com a ajuda do SIG, Ecam e dos contribuintes da comunidade de código aberto. \n\nEle é licenciado sob a Apache License, Version 2.0. \n\nVeja outras licenças
-
+
Falhou
Sincronizado
Sincronizando
@@ -196,4 +196,8 @@
Permitir localizaçã
Permitir compartilhamento de localização
Se você não permitir que o Ground acesse a localização deste dispositivo, não poderá continuar coletando dados para este local.
+
+ Parar de coletar dados?
+ Se você sair, os dados que ainda não enviou serão descartados.
+ Confirmar
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e0524fa941..ee2edc01ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -196,4 +196,8 @@
Allow location
Allow location sharing
If you don’t allow Ground to access this device’s location, you won’t be able to continue collecting data for this site.
+
+ Stop collecting data?
+ If you exit, data you haven’t submitted yet will be discarded.
+ Confirm
diff --git a/app/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt b/app/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt
index 7a54a39521..a65eb8a1c0 100644
--- a/app/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt
+++ b/app/src/test/java/com/google/android/ground/ui/datacollection/DataCollectionFragmentTest.kt
@@ -16,6 +16,9 @@
package com.google.android.ground.ui.datacollection
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
import com.google.android.ground.BaseHiltTest
import com.google.android.ground.FakeData
import com.google.android.ground.FakeData.LOCATION_OF_INTEREST
@@ -188,7 +191,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
runner()
.inputText(TASK_1_RESPONSE)
.clickNextButton()
- .pressBackButton(true)
+ .pressBackButton()
.validateTextIsDisplayed(TASK_1_NAME)
.validateTextIsNotDisplayed(TASK_2_NAME)
@@ -287,16 +290,28 @@ class DataCollectionFragmentTest : BaseHiltTest() {
}
@Test
- fun `Clicking back button on first task clears the draft and returns false`() =
+ fun `Clicking back button on first task displays a confirmation dialog and clears the draft`() =
runWithTestDispatcher {
setupFragment()
- runner()
- .inputText(TASK_1_RESPONSE)
- .clickNextButton()
- .pressBackButton(true)
- .pressBackButton(false)
+ // Save the draft and move back to first task.
+ runner().inputText(TASK_1_RESPONSE).clickNextButton().pressBackButton()
+
+ // Click back on first draft
+ runner().pressBackButton()
+
+ // Assert that confirmation dialog is shown
+ composeTestRule
+ .onNodeWithText(fragment.getString(R.string.data_collection_cancellation_title))
+ .assertIsDisplayed()
+
+ // Click confirm button
+ composeTestRule
+ .onNodeWithText(fragment.getString(R.string.data_collection_cancellation_confirm_button))
+ .performClick()
+ advanceUntilIdle()
+ // Assert that draft is cleared on confirmation
assertNoDraftSaved()
}
diff --git a/app/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt b/app/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt
index c2555e97fa..484b128b51 100644
--- a/app/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt
+++ b/app/src/test/java/com/google/android/ground/ui/datacollection/TaskFragmentRunner.kt
@@ -185,8 +185,8 @@ class TaskFragmentRunner(
return this
}
- internal fun pressBackButton(result: Boolean): TaskFragmentRunner {
- waitUntilDone { assertThat(fragment?.onBack()).isEqualTo(result) }
+ internal fun pressBackButton(): TaskFragmentRunner {
+ waitUntilDone { assertThat(fragment?.onBack()).isTrue() }
return this
}