Skip to content

Commit

Permalink
Show a warning dialog when attempting to close data collection screen (
Browse files Browse the repository at this point in the history
…#3024)

* Add strings for new warning dialog

* Refactor ConfirmationDialog composable for better usage

*   Add `ConfirmationDialog` preview.
*   Update `ConfirmationDialog` to track dialog state internally.
*   Refactor `showConfirmationDialog` to use new `ConfirmationDialog` implementation.
*   Remove unnecessary mutable state.
*   Update string resource names to match their purpose.

* Refactor: Display a warning dialog when exiting data collection

- Replaced the default navigation action with a custom warning dialog.
- Added a new function `showConfirmationDialog` to display the exit warning dialog.
- Modified `onBack` to trigger the warning dialog when appropriate.
- Moved clearDraft() call to onConfirm action.

* Update unit test

* revert changes to button text var

* Add string translations

* Small corrections of Spanish translations

* Small correction of French translation

---------

Co-authored-by: Jonas Spekker <[email protected]>
  • Loading branch information
shobhitagarwal1612 and jo-spek authored Jan 30, 2025
1 parent 1f021ac commit 00d18fa
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,58 @@ 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(
@StringRes title: Int,
@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 = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 =
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,8 @@
<string name="allow_location_confirmation">Permitir ubicación</string>
<string name="allow_location_title">Permitir el uso compartido de la ubicación</string>
<string name="allow_location_description">Si no permite que Ground acceda a la ubicación de este dispositivo, no podrá continuar recopilando datos para este sitio.</string>

<string name="data_collection_cancellation_title">¿Quiere parar de recopilar datos?</string>
<string name="data_collection_cancellation_description">Si sale, los datos que aún no hayan enviado se descartarán.</string>
<string name="data_collection_cancellation_confirm_button">Confirmar</string>
</resources>
6 changes: 5 additions & 1 deletion app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

<string name="add_point">Ajouter point</string>
<string name="complete_polygon">Finir</string>

<string name="road_map">Carte routière</string>
<string name="satellite">Satellite</string>
<string name="terrain">Relief</string>
Expand Down Expand Up @@ -195,4 +195,8 @@
<string name="allow_location_confirmation">Autoriser la localisation</string>
<string name="allow_location_title">Autoriser le partage de la localisation</string>
<string name="allow_location_description">Si vous n&#8217;autorisez pas Ground à accéder à la localisation de cet appareil, vous ne pourrez pas continuer à collecter des données pour ce site.</string>

<string name="data_collection_cancellation_title">Arrêter la collecte de données ?</string>
<string name="data_collection_cancellation_description">Si vous quittez, les données que vous n&#8217;avez pas encore soumises seront supprimées.</string>
<string name="data_collection_cancellation_confirm_button">Confirmer</string>
</resources>
8 changes: 6 additions & 2 deletions app/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
<string name="map_location">Localização do mapa:</string>
<string name="sign_out_dialog_title">Aviso de fim de sessão</string>
<string name="sign_out_dialog_body">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?</string>

<string name="suggest_data_collection_hint">Amplie para começar a coletar dados</string>
<string name="read_only_data_collection_hint">Inquérito apenas para leitura</string>
<string name="predefined_data_collection_hint">Amplie até um local de coleta de dados para coletar informações</string>
Expand All @@ -154,7 +154,7 @@
<string name="about">Sobre</string>
<string name="terms_of_service">Termos de serviço</string>
<string name="about_ground">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 <annotation type="apache_license">Apache License, Version 2.0</annotation>. \n\nVeja outras <annotation type="all_licenses">licenças</annotation></string>

<string name="failed">Falhou</string>
<string name="synced">Sincronizado</string>
<string name="syncing">Sincronizando</string>
Expand Down Expand Up @@ -196,4 +196,8 @@
<string name="allow_location_confirmation">Permitir localizaçã</string>
<string name="allow_location_title">Permitir compartilhamento de localização</string>
<string name="allow_location_description">Se você não permitir que o Ground acesse a localização deste dispositivo, não poderá continuar coletando dados para este local.</string>

<string name="data_collection_cancellation_title">Parar de coletar dados?</string>
<string name="data_collection_cancellation_description">Se você sair, os dados que ainda não enviou serão descartados.</string>
<string name="data_collection_cancellation_confirm_button">Confirmar</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,8 @@
<string name="allow_location_confirmation">Allow location</string>
<string name="allow_location_title">Allow location sharing</string>
<string name="allow_location_description">If you don&#8217;t allow Ground to access this device&#8217;s location, you won&#8217;t be able to continue collecting data for this site.</string>

<string name="data_collection_cancellation_title">Stop collecting data?</string>
<string name="data_collection_cancellation_description">If you exit, data you haven&#8217;t submitted yet will be discarded.</string>
<string name="data_collection_cancellation_confirm_button">Confirm</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -188,7 +191,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
runner()
.inputText(TASK_1_RESPONSE)
.clickNextButton()
.pressBackButton(true)
.pressBackButton()
.validateTextIsDisplayed(TASK_1_NAME)
.validateTextIsNotDisplayed(TASK_2_NAME)

Expand Down Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 00d18fa

Please sign in to comment.