Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show a warning dialog when attempting to close data collection screen #3024

Merged
merged 8 commits into from
Jan 30, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,58 @@
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 {

Check warning on line 69 in app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt#L69

Added line #L69 was not covered by tests
ConfirmationDialog(
title = R.string.data_collection_cancellation_title,
description = R.string.data_collection_cancellation_description,
confirmButtonText = R.string.data_collection_cancellation_confirm_button,
onConfirmClicked = {},
)
}

Check warning on line 76 in app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/com/google/android/ground/ui/compose/ConfirmationDialog.kt#L71-L76

Added lines #L71 - L76 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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 @@
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 @@
}
}

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() },
)

Check warning on line 223 in app/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionFragment.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionFragment.kt#L223

Added line #L223 was not covered by tests
}
}
}
)
}

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.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 @@
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 @@
}
}

@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() },
)

Check warning on line 145 in app/src/main/java/com/google/android/ground/ui/surveyselector/SurveySelectorFragment.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/com/google/android/ground/ui/surveyselector/SurveySelectorFragment.kt#L145

Added line #L145 was not covered by tests
}
}
}
(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