commit f6758121fd3acd273fe46a78857ac1a938f5dd35
parent 8e267f9570961ef588d8a98f9d48c63e4288cc3f
Author: t-p-white <towhite@mozilla.com>
Date: Mon, 24 Nov 2025 17:19:33 +0000
Bug 2001968 - Part 1: Add the Notification and Set search widgets cards to the redesign flow. r=android-reviewers,mavduevskiy
Differential Revision: https://phabricator.services.mozilla.com/D273800
Diffstat:
6 files changed, 183 insertions(+), 10 deletions(-)
diff --git a/mobile/android/fenix/app/onboarding.fml.yaml b/mobile/android/fenix/app/onboarding.fml.yaml
@@ -335,7 +335,6 @@ features:
secondary-button-label: onboarding_redesign_sync_negative_button
toolbar-placement:
- ordering: 17
title: onboarding_redesign_customize_toolbar_title
body: onboarding_customize_toolbar_description
image-res: ic_onboarding_customize_toolbar
@@ -360,13 +359,7 @@ features:
body-line-one-link-text: onboarding_marketing_redesign_learn_more
body-line-two-text: onboarding_marketing_redesign_opt_out_checkbox
- # Disable the defaults that are not included in the redesign
- add-search-widget:
- enabled: false
-
- notification-permission:
- enabled: false
-
+ # Disable the defaults that are not included in the redesign.
theme-selection:
enabled: false
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt
@@ -252,6 +252,21 @@ class OnboardingFragment : Fragment() {
pagesToDisplay.sequencePosition(OnboardingPageUiData.Type.SYNC_SIGN_IN),
)
},
+ onNotificationPermissionButtonClick = {
+ requireComponents.notificationsDelegate.requestNotificationPermission()
+ telemetryRecorder.onNotificationPermissionClick(
+ sequenceId = pagesToDisplay.telemetrySequenceId(),
+ sequencePosition =
+ pagesToDisplay.sequencePosition(OnboardingPageUiData.Type.NOTIFICATION_PERMISSION),
+ )
+ },
+ onSkipNotificationClick = {
+ telemetryRecorder.onSkipTurnOnNotificationsClick(
+ sequenceId = pagesToDisplay.telemetrySequenceId(),
+ sequencePosition =
+ pagesToDisplay.sequencePosition(OnboardingPageUiData.Type.NOTIFICATION_PERMISSION),
+ )
+ },
onAddFirefoxWidgetClick = {
telemetryRecorder.onAddSearchWidgetClick(
pagesToDisplay.telemetrySequenceId(),
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/notification/NotificationMainImage.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/notification/NotificationMainImage.kt
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.onboarding.notification
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import org.mozilla.fenix.R
+import org.mozilla.fenix.theme.FirefoxTheme
+
+private val IMAGE_HEIGHT = 200.dp
+
+/**
+ * Renders the main image for the "Notification" onboarding screen.
+ *
+ * **This is a non-interactive, static image view only**.
+ */
+@Composable
+fun NotificationMainImage() {
+ Image(
+ painter = painterResource(R.drawable.ic_notification_permission),
+ contentDescription = null, // Decorative only
+ modifier = Modifier.height(IMAGE_HEIGHT),
+ )
+}
+
+@PreviewLightDark
+@Composable
+private fun NotificationMainImagePreview() {
+ FirefoxTheme {
+ Box(Modifier.background(FirefoxTheme.colors.layer1)) {
+ NotificationMainImage()
+ }
+ }
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/redesign/view/OnboardingPageRedesign.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/redesign/view/OnboardingPageRedesign.kt
@@ -28,10 +28,12 @@ import androidx.compose.ui.unit.dp
import mozilla.components.compose.base.button.FilledButton
import mozilla.components.compose.base.button.TextButton
import org.mozilla.fenix.R
+import org.mozilla.fenix.onboarding.notification.NotificationMainImage
import org.mozilla.fenix.onboarding.redesign.view.defaultbrowser.SetToDefaultMainImage
import org.mozilla.fenix.onboarding.redesign.view.sync.SyncMainImage
import org.mozilla.fenix.onboarding.view.Action
import org.mozilla.fenix.onboarding.view.OnboardingPageState
+import org.mozilla.fenix.onboarding.widget.SetSearchWidgetMainImage
import org.mozilla.fenix.theme.FirefoxTheme
const val TITLE_TOP_SPACER_WEIGHT = 0.3f
@@ -193,3 +195,51 @@ private fun OnboardingPageSyncPreview() {
)
}
}
+
+@PreviewLightDark
+@Composable
+private fun OnboardingPageNotificationPreview() {
+ FirefoxTheme {
+ OnboardingPageRedesign(
+ pageState = OnboardingPageState(
+ imageRes = R.drawable.ic_notification_permission, // Unused in the redesign.
+ title = stringResource(R.string.juno_onboarding_enable_notifications_title_nimbus_2),
+ description = stringResource(R.string.juno_onboarding_enable_notifications_description_nimbus_2),
+ primaryButton = Action(
+ text = stringResource(R.string.juno_onboarding_enable_notifications_positive_button),
+ onClick = {},
+ ),
+ secondaryButton = Action(
+ text = stringResource(R.string.juno_onboarding_enable_notifications_negative_button),
+ onClick = {},
+ ),
+ onRecordImpressionEvent = {},
+ ),
+ mainImage = { NotificationMainImage() },
+ )
+ }
+}
+
+@PreviewLightDark
+@Composable
+private fun OnboardingPageSearchWidgetPreview() {
+ FirefoxTheme {
+ OnboardingPageRedesign(
+ pageState = OnboardingPageState(
+ imageRes = R.drawable.ic_notification_permission, // Unused in the redesign.
+ title = stringResource(R.string.juno_onboarding_add_search_widget_title),
+ description = stringResource(R.string.juno_onboarding_add_search_widget_description),
+ primaryButton = Action(
+ text = stringResource(R.string.juno_onboarding_add_search_widget_positive_button),
+ onClick = {},
+ ),
+ secondaryButton = Action(
+ text = stringResource(R.string.juno_onboarding_add_search_widget_negative_button),
+ onClick = {},
+ ),
+ onRecordImpressionEvent = {},
+ ),
+ mainImage = { SetSearchWidgetMainImage() },
+ )
+ }
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/redesign/view/OnboardingScreenRedesign.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/redesign/view/OnboardingScreenRedesign.kt
@@ -56,6 +56,7 @@ import org.mozilla.fenix.compose.LinkTextState
import org.mozilla.fenix.compose.PagerIndicator
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.onboarding.WidgetPinnedReceiver.WidgetPinnedState
+import org.mozilla.fenix.onboarding.notification.NotificationMainImage
import org.mozilla.fenix.onboarding.redesign.view.defaultbrowser.SetToDefaultMainImage
import org.mozilla.fenix.onboarding.redesign.view.sync.SyncMainImage
import org.mozilla.fenix.onboarding.store.OnboardingAction.OnboardingToolbarAction
@@ -68,6 +69,7 @@ import org.mozilla.fenix.onboarding.view.OnboardingTermsOfServiceEventHandler
import org.mozilla.fenix.onboarding.view.ToolbarOption
import org.mozilla.fenix.onboarding.view.ToolbarOptionType
import org.mozilla.fenix.onboarding.view.mapToOnboardingPageState
+import org.mozilla.fenix.onboarding.widget.SetSearchWidgetMainImage
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.utils.isLargeScreenSize
@@ -104,6 +106,9 @@ private object GradientColors {
* @param onSkipDefaultClick Invoked when negative button on default browser page is clicked.
* @param onSignInButtonClick Invoked when the positive button on the sign in page is clicked.
* @param onSkipSignInClick Invoked when the negative button on the sign in page is clicked.
+ * @param onNotificationPermissionButtonClick Invoked when positive button on notification page is
+ * clicked.
+ * @param onSkipNotificationClick Invoked when negative button on notification page is clicked.
* @param onAddFirefoxWidgetClick Invoked when positive button on add search widget page is clicked.
* @param onSkipFirefoxWidgetClick Invoked when negative button on add search widget page is clicked.
* @param onboardingStore The store which contains all the state related to the add-ons onboarding screen.
@@ -125,6 +130,8 @@ fun OnboardingScreenRedesign(
onSkipDefaultClick: () -> Unit,
onSignInButtonClick: () -> Unit,
onSkipSignInClick: () -> Unit,
+ onNotificationPermissionButtonClick: () -> Unit,
+ onSkipNotificationClick: () -> Unit,
onAddFirefoxWidgetClick: () -> Unit,
onSkipFirefoxWidgetClick: () -> Unit,
onboardingStore: OnboardingStore? = null,
@@ -218,6 +225,14 @@ fun OnboardingScreenRedesign(
scrollToNextPageOrDismiss()
onSkipSignInClick()
},
+ onNotificationPermissionButtonClick = {
+ scrollToNextPageOrDismiss()
+ onNotificationPermissionButtonClick()
+ },
+ onNotificationPermissionSkipClick = {
+ scrollToNextPageOrDismiss()
+ onSkipNotificationClick()
+ },
onAddFirefoxWidgetClick = {
if (isWidgetPinnedState) {
scrollToNextPageOrDismiss()
@@ -279,6 +294,8 @@ private fun OnboardingContent(
onMakeFirefoxDefaultSkipClick: () -> Unit,
onSignInButtonClick: () -> Unit,
onSignInSkipClick: () -> Unit,
+ onNotificationPermissionButtonClick: () -> Unit,
+ onNotificationPermissionSkipClick: () -> Unit,
onAddFirefoxWidgetClick: () -> Unit,
onSkipFirefoxWidgetClick: () -> Unit,
onboardingStore: OnboardingStore? = null,
@@ -334,6 +351,8 @@ private fun OnboardingContent(
onMakeFirefoxDefaultSkipClick = onMakeFirefoxDefaultSkipClick,
onSignInButtonClick = onSignInButtonClick,
onSignInSkipClick = onSignInSkipClick,
+ onNotificationPermissionButtonClick = onNotificationPermissionButtonClick,
+ onNotificationPermissionSkipClick = onNotificationPermissionSkipClick,
onAddFirefoxWidgetClick = onAddFirefoxWidgetClick,
onAddFirefoxWidgetSkipClick = onSkipFirefoxWidgetClick,
onCustomizeToolbarButtonClick = onCustomizeToolbarButtonClick,
@@ -398,6 +417,16 @@ private fun OnboardingPageForType(
mainImage = { SyncMainImage() },
)
+ OnboardingPageUiData.Type.ADD_SEARCH_WIDGET -> OnboardingPageRedesign(
+ pageState = state,
+ mainImage = { SetSearchWidgetMainImage() },
+ )
+
+ OnboardingPageUiData.Type.NOTIFICATION_PERMISSION -> OnboardingPageRedesign(
+ pageState = state,
+ mainImage = { NotificationMainImage() },
+ )
+
OnboardingPageUiData.Type.TOOLBAR_PLACEMENT -> {
val context = LocalContext.current
onboardingStore?.let { store ->
@@ -430,8 +459,6 @@ private fun OnboardingPageForType(
)
// no-ops
- OnboardingPageUiData.Type.ADD_SEARCH_WIDGET,
- OnboardingPageUiData.Type.NOTIFICATION_PERMISSION,
OnboardingPageUiData.Type.THEME_SELECTION,
-> {
logger.error("Unsupported page type: $type used for onboarding redesign.")
@@ -550,6 +577,8 @@ private fun OnboardingScreenPreview() {
onMarketingDataLearnMoreClick = {},
onMarketingOptInToggle = {},
onMarketingDataContinueClick = {},
+ onNotificationPermissionButtonClick = {},
+ onNotificationPermissionSkipClick = {},
)
}
}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/widget/SetSearchWidgetMainImage.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/onboarding/widget/SetSearchWidgetMainImage.kt
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.onboarding.widget
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import org.mozilla.fenix.R
+import org.mozilla.fenix.theme.FirefoxTheme
+
+private val IMAGE_HEIGHT = 200.dp
+
+/**
+ * Renders the main image for the "Set search widget" onboarding screen.
+ *
+ * **This is a non-interactive, static image view only**.
+ */
+@Composable
+fun SetSearchWidgetMainImage() {
+ Image(
+ painter = painterResource(R.drawable.ic_onboarding_search_widget),
+ contentDescription = null, // Decorative only
+ modifier = Modifier.height(IMAGE_HEIGHT),
+ )
+}
+
+@PreviewLightDark
+@Composable
+private fun SetSearchWidgetMainImagePreview() {
+ FirefoxTheme {
+ Box(Modifier.background(FirefoxTheme.colors.layer1)) {
+ SetSearchWidgetMainImage()
+ }
+ }
+}