tor-browser

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

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:
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBanner.kt | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerController.kt | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerInteractor.kt | 40++++++++++++++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt | 2++
Amobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/termsofuse/PrivacyNoticeBannerControllerTest.kt | 48++++++++++++++++++++++++++++++++++++++++++++++++
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) } + } +}