commit 62f36d18a9eb6f6bf63e5e22109f0c0d43296033
parent 5c8d2d6296c7b83deaaabe2c1f3b2fb386c3e9ad
Author: mike a. <mavduevskiy@mozilla.com>
Date: Tue, 18 Nov 2025 19:12:50 +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:
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)
}
}