/* 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.components.toolbar import android.content.Context import androidx.annotation.VisibleForTesting import androidx.compose.foundation.clickable import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.navigation.findNavController import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.transformWhile import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.lib.state.ext.flowScoped import mozilla.components.service.glean.private.NoExtras import org.mozilla.fenix.GleanMetrics.TrackingProtection import org.mozilla.fenix.R import org.mozilla.fenix.compose.cfr.CFRPopup import org.mozilla.fenix.compose.cfr.CFRPopup.PopupAlignment.INDICATOR_CENTERED_IN_ANCHOR import org.mozilla.fenix.compose.cfr.CFRPopupProperties import org.mozilla.fenix.ext.components import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils.SumoTopic.TOTAL_COOKIE_PROTECTION import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.dialog.CookieBannerReEngagementDialogUtils import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.utils.Settings /** * Vertical padding needed to improve the visual alignment of the popup and respect the UX design. */ private const val CFR_TO_ANCHOR_VERTICAL_PADDING = -6 /** * Delegate for handling all the business logic for showing CFRs in the toolbar. * * @param context used for various Android interactions. * @param browserStore will be observed for tabs updates * @param settings used to read and write persistent user settings * @param toolbar will serve as anchor for the CFRs * @param sessionId optional custom tab id used to identify the custom tab in which to show a CFR. */ class BrowserToolbarCFRPresenter( private val context: Context, private val browserStore: BrowserStore, private val settings: Settings, private val toolbar: BrowserToolbar, private val sessionId: String? = null, ) { @VisibleForTesting internal var tcpCfrScope: CoroutineScope? = null @VisibleForTesting internal var tcpCfrPopup: CFRPopup? = null /** * Start observing [browserStore] for updates which may trigger showing a CFR. */ @Suppress("MagicNumber") fun start() { if (settings.shouldShowTotalCookieProtectionCFR) { tcpCfrScope = browserStore.flowScoped { flow -> flow .mapNotNull { it.findCustomTabOrSelectedTab(sessionId)?.content?.progress } // The "transformWhile" below ensures that the 100% progress is only collected once. .transformWhile { progress -> emit(progress) progress != 100 } .filter { it == 100 } .collect { tcpCfrScope?.cancel() showTcpCfr() } } } } /** * Stop listening for [browserStore] updates. * CFRs already shown are not automatically dismissed. */ fun stop() { tcpCfrScope?.cancel() } @VisibleForTesting internal fun showTcpCfr() { CFRPopup( text = context.getString(R.string.tcp_cfr_message), anchor = toolbar.findViewById( R.id.mozac_browser_toolbar_security_indicator, ), properties = CFRPopupProperties( popupAlignment = INDICATOR_CENTERED_IN_ANCHOR, indicatorDirection = if (settings.toolbarPosition == ToolbarPosition.TOP) { CFRPopup.IndicatorDirection.UP } else { CFRPopup.IndicatorDirection.DOWN }, popupVerticalOffset = CFR_TO_ANCHOR_VERTICAL_PADDING.dp, ), onDismiss = { when (it) { true -> TrackingProtection.tcpCfrExplicitDismissal.record(NoExtras()) false -> TrackingProtection.tcpCfrImplicitDismissal.record(NoExtras()) } tryToShowCookieBannerDialogIfNeeded() }, ) { Text( text = context.getString(R.string.tcp_cfr_learn_more), color = FirefoxTheme.colors.textOnColorPrimary, modifier = Modifier.clickable { context.components.useCases.tabsUseCases.selectOrAddTab.invoke( SupportUtils.getSumoURLForTopic( context, TOTAL_COOKIE_PROTECTION, ), ) TrackingProtection.tcpSumoLinkClicked.record(NoExtras()) tcpCfrPopup?.dismiss() }, style = FirefoxTheme.typography.body2.copy( textDecoration = TextDecoration.Underline, ), ) }.run { settings.shouldShowTotalCookieProtectionCFR = false tcpCfrPopup = this show() TrackingProtection.tcpCfrShown.record(NoExtras()) } } @VisibleForTesting internal fun tryToShowCookieBannerDialogIfNeeded() { browserStore.state.selectedTab?.let { tab -> CookieBannerReEngagementDialogUtils.tryToShowReEngagementDialog( settings = settings, status = tab.cookieBanner, navController = toolbar.findNavController(), ) } } }