commit 78b9021fd998c706175abb05a22a2b55e931d93f
parent 74b132cd9148fd71ef4ae9b1f8f82b1474d9dfe6
Author: John Oberhauser <j.git-global@obez.io>
Date: Tue, 16 Dec 2025 16:04:17 +0000
Bug 2004410: Part 2 - Adding the Privacy Notice banner UI and it's controller and interactor r=android-reviewers,gmalekpour,twhite
Differential Revision: https://phabricator.services.mozilla.com/D275849
Diffstat:
5 files changed, 237 insertions(+), 0 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBanner.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBanner.kt
@@ -0,0 +1,91 @@
+/* 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.home.termsofuse
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import mozilla.components.compose.base.Banner
+import org.mozilla.fenix.R
+import org.mozilla.fenix.compose.LinkText
+import org.mozilla.fenix.compose.LinkTextState
+import org.mozilla.fenix.settings.SupportUtils
+import org.mozilla.fenix.theme.FirefoxTheme
+
+/**
+ * Privacy Notice banner on the homepage.
+ *
+ * @param interactor interaction interface for the banner.
+ * @param modifier the [Modifier] for the composable
+ */
+@Composable
+fun PrivacyNoticeBanner(
+ interactor: PrivacyNoticeBannerInteractor,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+ LaunchedEffect(Unit) {
+ interactor.onPrivacyNoticeBannerDisplayed()
+ }
+
+ Banner(
+ modifier = modifier,
+ message = {
+ LinkText(
+ text = stringResource(
+ R.string.privacy_notice_updated_homepage_message,
+ stringResource(R.string.privacy_notice_updated_homepage_message_privacy_notice),
+ stringResource(R.string.privacy_notice_updated_homepage_message_learn_more),
+ ),
+ linkTextStates = listOf(
+ LinkTextState(
+ text = stringResource(R.string.privacy_notice_updated_homepage_message_privacy_notice),
+ url = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE_NEXT),
+ onClick = { url ->
+ interactor.onPrivacyNoticeBannerPrivacyNoticeClicked()
+ SupportUtils.launchSandboxCustomTab(
+ context = context,
+ url = url,
+ )
+ },
+ ),
+ LinkTextState(
+ text = stringResource(R.string.privacy_notice_updated_homepage_message_learn_more),
+ url = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE_UPDATE),
+ onClick = { url ->
+ interactor.onPrivacyNoticeBannerLearnMoreClicked()
+ SupportUtils.launchSandboxCustomTab(
+ context = context,
+ url = url,
+ )
+ },
+ ),
+ ),
+ textAlign = TextAlign.Start,
+ linkTextDecoration = TextDecoration.Underline,
+ )
+ },
+ border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant),
+ onCloseButtonClick = interactor::onPrivacyNoticeBannerCloseClicked,
+ )
+}
+
+@Composable
+@PreviewLightDark
+private fun PrivacyNoticeBannerPreview() {
+ FirefoxTheme {
+ PrivacyNoticeBanner(
+ interactor = PrivacyNoticeBannerInteractorNoOp,
+ )
+ }
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerController.kt
@@ -0,0 +1,56 @@
+/* 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.home.termsofuse
+
+import org.mozilla.fenix.termsofuse.store.PrivacyNoticeBannerAction
+import org.mozilla.fenix.termsofuse.store.PrivacyNoticeBannerStore
+
+/**
+ * Interface for handling actions from the Privacy Notice banner.
+ */
+interface PrivacyNoticeBannerController {
+ /**
+ * Called when the user clicks the close button.
+ */
+ fun onBannerCloseClicked()
+
+ /**
+ * Called when the user clicks the Privacy Notice link.
+ */
+ fun onBannerPrivacyNoticeClicked()
+
+ /**
+ * Called when the user clicks the Learn more link.
+ */
+ fun onBannerLearnMoreClicked()
+
+ /**
+ * Called when the banner is displayed.
+ */
+ fun onBannerDisplayed()
+}
+
+/**
+ * The default implementation of [PrivacyNoticeBannerController].
+ */
+class DefaultPrivacyNoticeBannerController(
+ private val privacyNoticeBannerStore: PrivacyNoticeBannerStore,
+) : PrivacyNoticeBannerController {
+ override fun onBannerCloseClicked() {
+ privacyNoticeBannerStore.dispatch(PrivacyNoticeBannerAction.OnCloseClicked)
+ }
+
+ override fun onBannerPrivacyNoticeClicked() {
+ privacyNoticeBannerStore.dispatch(PrivacyNoticeBannerAction.OnPrivacyNoticeClicked)
+ }
+
+ override fun onBannerLearnMoreClicked() {
+ privacyNoticeBannerStore.dispatch(PrivacyNoticeBannerAction.OnLearnMoreClicked)
+ }
+
+ override fun onBannerDisplayed() {
+ privacyNoticeBannerStore.dispatch(PrivacyNoticeBannerAction.OnBannerDisplayed)
+ }
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerInteractor.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerInteractor.kt
@@ -0,0 +1,40 @@
+/* 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.home.termsofuse
+
+/**
+ * Interface for interactions with the privacy notice banner.
+ */
+interface PrivacyNoticeBannerInteractor {
+ /**
+ * Called when the user clicks the close button.
+ */
+ fun onPrivacyNoticeBannerCloseClicked()
+
+ /**
+ * Called when the user clicks the Privacy Notice link.
+ */
+ fun onPrivacyNoticeBannerPrivacyNoticeClicked()
+
+ /**
+ * Called when the user clicks the Learn more link.
+ */
+ fun onPrivacyNoticeBannerLearnMoreClicked()
+
+ /**
+ * Called when the banner is displayed.
+ */
+ fun onPrivacyNoticeBannerDisplayed()
+}
+
+/**
+ * NoOp implementation for compose previews.
+ */
+object PrivacyNoticeBannerInteractorNoOp : PrivacyNoticeBannerInteractor {
+ override fun onPrivacyNoticeBannerCloseClicked() = Unit
+ override fun onPrivacyNoticeBannerPrivacyNoticeClicked() = Unit
+ override fun onPrivacyNoticeBannerLearnMoreClicked() = Unit
+ override fun onPrivacyNoticeBannerDisplayed() = Unit
+}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt
@@ -78,6 +78,8 @@ object SupportUtils {
enum class MozillaPage(internal val path: String) {
PRIVATE_NOTICE("privacy/firefox/"),
+ PRIVATE_NOTICE_UPDATE("${PRIVATE_NOTICE.path}update/"),
+ PRIVATE_NOTICE_NEXT("${PRIVATE_NOTICE.path}next/"),
MANIFESTO("about/manifesto/"),
TERMS_OF_SERVICE("about/legal/terms/firefox/"),
}
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerControllerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerControllerTest.kt
@@ -0,0 +1,48 @@
+/* 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.home.termsofuse
+
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.Test
+import org.mozilla.fenix.termsofuse.store.PrivacyNoticeBannerAction
+import org.mozilla.fenix.termsofuse.store.PrivacyNoticeBannerStore
+
+class PrivacyNoticeBannerControllerTest {
+
+ private val store: PrivacyNoticeBannerStore = mockk(relaxed = true)
+
+ private val subject = DefaultPrivacyNoticeBannerController(
+ privacyNoticeBannerStore = store,
+ )
+
+ @Test
+ fun `WHEN onBannerCloseClicked is called THEN the OnCloseClicked action is dispatched`() {
+ subject.onBannerCloseClicked()
+
+ verify { store.dispatch(PrivacyNoticeBannerAction.OnCloseClicked) }
+ }
+
+ @Test
+ fun `WHEN onBannerPrivacyNoticeClicked is called THEN the OnPrivacyNoticeClicked action is dispatched`() {
+ subject.onBannerPrivacyNoticeClicked()
+
+ verify { store.dispatch(PrivacyNoticeBannerAction.OnPrivacyNoticeClicked) }
+ }
+
+ @Test
+ fun `WHEN onBannerLearnMoreClicked is called THEN the OnLearnMoreClicked action is dispatched`() {
+ subject.onBannerLearnMoreClicked()
+
+ verify { store.dispatch(PrivacyNoticeBannerAction.OnLearnMoreClicked) }
+ }
+
+ @Test
+ fun `WHEN onBannerSeen is called THEN the OnBannerDisplayed action is dispatched`() {
+ subject.onBannerDisplayed()
+
+ verify { store.dispatch(PrivacyNoticeBannerAction.OnBannerDisplayed) }
+ }
+}