tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 32b64690ec2dee5777ff9a526e4f11b5116b6d7f
parent baf64f20f33d129bc68b69b608252fe4b6a67a1a
Author: mike a. <mavduevskiy@mozilla.com>
Date:   Tue, 18 Nov 2025 06:20:16 +0000

Bug 1955887 - Part 5: Refactor app icon state to have a separate warning state member r=android-reviewers,marcin,twhite

Differential Revision: https://phabricator.services.mozilla.com/D271726

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconReducer.kt | 18+++++++++++++++---
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconState.kt | 17+++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelection.kt | 8+++++---
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/iconpicker/AppIconReducerTest.kt | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
4 files changed, 102 insertions(+), 10 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconReducer.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconReducer.kt @@ -14,9 +14,16 @@ internal fun appIconReducer(state: AppIconState, action: AppIconAction) = when ( private fun AppIconState.handleUserAction(action: UserAction): AppIconState { return when (action) { - is UserAction.Selected -> this.copy(userSelectedAppIcon = action.appIcon) + is UserAction.Selected -> this.copy( + userSelectedAppIcon = action.appIcon, + warningDialogState = AppIconWarningDialog.Presenting(newIcon = action.appIcon), + ) + is UserAction.Confirmed -> this - is UserAction.Dismissed -> this.copy(userSelectedAppIcon = null) + is UserAction.Dismissed -> this.copy( + userSelectedAppIcon = null, + warningDialogState = AppIconWarningDialog.None, + ) } } @@ -25,12 +32,17 @@ private fun AppIconState.handleSystemAction(action: SystemAction): AppIconState is SystemAction.Applied -> this.copy( currentAppIcon = action.newIcon, userSelectedAppIcon = null, + warningDialogState = AppIconWarningDialog.None, + ) + SystemAction.DialogDismissed -> this.copy( + userSelectedAppIcon = null, + warningDialogState = AppIconWarningDialog.None, ) - SystemAction.DialogDismissed -> this.copy(userSelectedAppIcon = null) SystemAction.SnackbarDismissed -> this.copy(snackbarState = AppIconSnackbarState.None) SystemAction.UpdateFailed -> this.copy( userSelectedAppIcon = null, snackbarState = AppIconSnackbarState.ApplyingNewIconError, + warningDialogState = AppIconWarningDialog.None, ) is SystemAction.EnvironmentRehydrated -> this } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconState.kt @@ -13,12 +13,14 @@ import mozilla.components.lib.state.State * @property userSelectedAppIcon The icon the user has selected in the picker, if any. * @property groupedIconOptions A map of all available app icons. * @property snackbarState The current snackbar state. + * @property warningDialogState The current warning dialog state. */ data class AppIconState( val currentAppIcon: AppIcon = AppIcon.AppDefault, val userSelectedAppIcon: AppIcon? = null, val groupedIconOptions: Map<IconGroupTitle, List<AppIcon>> = mapOf(), val snackbarState: AppIconSnackbarState = AppIconSnackbarState.None, + val warningDialogState: AppIconWarningDialog = AppIconWarningDialog.None, ) : State /** @@ -35,3 +37,18 @@ sealed class AppIconSnackbarState { */ data object ApplyingNewIconError : AppIconSnackbarState() } + +/** + * The state of the app reset warning dialog + */ +sealed class AppIconWarningDialog { + /** + * No dialog to display. + */ + data object None : AppIconWarningDialog() + + /** + * The dialog is visible. + */ + data class Presenting(val newIcon: AppIcon) : AppIconWarningDialog() +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelection.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelection.kt @@ -61,6 +61,7 @@ import org.mozilla.fenix.iconpicker.AppIcon import org.mozilla.fenix.iconpicker.AppIconSnackbarState import org.mozilla.fenix.iconpicker.AppIconState import org.mozilla.fenix.iconpicker.AppIconStore +import org.mozilla.fenix.iconpicker.AppIconWarningDialog import org.mozilla.fenix.iconpicker.DefaultAppIconRepository import org.mozilla.fenix.iconpicker.DefaultPackageManagerWrapper import org.mozilla.fenix.iconpicker.IconBackground @@ -127,13 +128,13 @@ fun AppIconSelection( ) } - state.userSelectedAppIcon?.let { - RestartWarningDialog( + when (val warning = state.warningDialogState) { + is AppIconWarningDialog.Presenting -> RestartWarningDialog( onConfirmClicked = { store.dispatch( UserAction.Confirmed( oldIcon = state.currentAppIcon, - newIcon = it, + newIcon = warning.newIcon, ), ) }, @@ -144,6 +145,7 @@ fun AppIconSelection( store.dispatch(SystemAction.DialogDismissed) }, ) + else -> Unit } } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/iconpicker/AppIconReducerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/iconpicker/AppIconReducerTest.kt @@ -9,7 +9,7 @@ import org.junit.Test class AppIconReducerTest { @Test - fun `GIVEN Selected user action WHEN reducer is called THEN state is updated with the selected icon`() { + fun `GIVEN Selected user action WHEN reducer is called THEN state is updated with the selected icon and the warning is displayed`() { val initialState = AppIconState() val newIcon = AppIcon.AppRetro2004 @@ -17,6 +17,7 @@ class AppIconReducerTest { val result = appIconReducer(initialState, UserAction.Selected(newIcon)) + assert(result.warningDialogState is AppIconWarningDialog.Presenting) assertEquals(newIcon, result.userSelectedAppIcon) } @@ -33,13 +34,17 @@ class AppIconReducerTest { } @Test - fun `GIVEN Dismissed user action WHEN reducer is called THEN state resets the user selected icon to null`() { - val initialState = AppIconState(userSelectedAppIcon = AppIcon.AppRetro2004) + fun `GIVEN Dismissed user action WHEN reducer is called THEN state resets the warning state to none and the user selected icon to null`() { + val initialState = AppIconState( + userSelectedAppIcon = AppIcon.AppRetro2004, + warningDialogState = AppIconWarningDialog.Presenting(AppIcon.AppRetro2004), + ) assertEquals(AppIcon.AppRetro2004, initialState.userSelectedAppIcon) val result = appIconReducer(initialState, UserAction.Dismissed) + assertEquals(AppIconWarningDialog.None, result.warningDialogState) assertEquals(null, result.userSelectedAppIcon) } @@ -55,20 +60,76 @@ class AppIconReducerTest { } @Test - fun `GIVEN Applied user action WHEN reducer is called THEN the new icon becomes the new current and the user selected icon resets to null`() { + fun `GIVEN DialogDismissed system action WHEN reducer is called THEN the warning dialog is hidden and the user selected icon is reset to null`() { + val initialState = AppIconState( + userSelectedAppIcon = AppIcon.AppRetro2004, + warningDialogState = AppIconWarningDialog.Presenting(AppIcon.AppRetro2004), + ) + + assertEquals(AppIcon.AppRetro2004, initialState.userSelectedAppIcon) + + val result = appIconReducer(initialState, SystemAction.DialogDismissed) + + assertEquals(AppIconWarningDialog.None, result.warningDialogState) + assertEquals(null, result.userSelectedAppIcon) + } + + @Test + fun `GIVEN Applied system action WHEN reducer is called THEN the new icon becomes the new current and the user selected icon resets to null and the dialog is hidden`() { val newIcon = AppIcon.AppRetro2004 val currentIcon = AppIcon.AppDefault + val dialogState = AppIconWarningDialog.Presenting(newIcon) val initialState = AppIconState( currentAppIcon = currentIcon, userSelectedAppIcon = newIcon, + warningDialogState = dialogState, ) assertEquals(currentIcon, initialState.currentAppIcon) assertEquals(newIcon, initialState.userSelectedAppIcon) + assertEquals(dialogState, initialState.warningDialogState) val result = appIconReducer(initialState, SystemAction.Applied(newIcon = newIcon)) assertEquals(newIcon, result.currentAppIcon) assertEquals(null, result.userSelectedAppIcon) + assertEquals(AppIconWarningDialog.None, result.warningDialogState) + } + + @Test + fun `GIVEN UpdateFailed system action WHEN reducer is called THEN the user selected icon resets to null and the dialog is hidden and the snackbar is shown`() { + val newIcon = AppIcon.AppRetro2004 + val currentIcon = AppIcon.AppDefault + val dialogState = AppIconWarningDialog.Presenting(newIcon) + val initialState = AppIconState( + currentAppIcon = currentIcon, + userSelectedAppIcon = newIcon, + warningDialogState = dialogState, + ) + + assertEquals(newIcon, initialState.userSelectedAppIcon) + assertEquals(dialogState, initialState.warningDialogState) + assertEquals(AppIconSnackbarState.None, initialState.snackbarState) + + val result = appIconReducer(initialState, SystemAction.UpdateFailed) + + assertEquals(null, result.userSelectedAppIcon) + assertEquals(AppIconWarningDialog.None, result.warningDialogState) + assertEquals(AppIconSnackbarState.ApplyingNewIconError, result.snackbarState) + } + + @Test + fun `GIVEN SnackbarDismissed system action WHEN reducer is called THEN the snackbar is hidden`() { + val currentIcon = AppIcon.AppDefault + val initialState = AppIconState( + currentAppIcon = currentIcon, + snackbarState = AppIconSnackbarState.ApplyingNewIconError, + ) + + assertEquals(AppIconSnackbarState.ApplyingNewIconError, initialState.snackbarState) + + val result = appIconReducer(initialState, SystemAction.SnackbarDismissed) + + assertEquals(AppIconSnackbarState.None, result.snackbarState) } }