Skip to content

Commit

Permalink
For mozilla-mobile#26224 - New TCP CFR popup
Browse files Browse the repository at this point in the history
If Total Cookie Protection is enabled when first accessing a normal tab
(not a custom tab) a new Contextual Feature Recommendation popup will be shown
informing about the added protection and allowing the user to open a support
page with more data about the new option for privacy protection.
  • Loading branch information
Mugurell committed Jul 28, 2022
1 parent acb214e commit 4810a91
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 5 deletions.
120 changes: 120 additions & 0 deletions app/src/main/java/org/mozilla/fenix/components/toolbar/CFRPresenter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* 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 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.store.BrowserStore
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.lib.state.ext.flowScoped
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.cfr.CFRPopup
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.theme.FirefoxTheme
import org.mozilla.fenix.utils.Settings

/**
* Delegate for handling all the business logic for showing CFRs in the toolbar.
*
* @param context [Context] used for various Android interactions.
* @param store [BrowserStore] which will be observed for tabs updates
* @param settings [Settings] allowing to read and write persistent user settings
* @param toolbar [BrowserToolbar] that will serve as anchor for the CFRs
* @param sessionId [String] optional custom tab id.
*/
class CFRPresenter(
private val context: Context,
private val store: BrowserStore,
private val settings: Settings,
private val toolbar: BrowserToolbar,
private val sessionId: String? = null
) {
@VisibleForTesting
internal var tcpCfrScope: CoroutineScope? = null
@VisibleForTesting
internal var tcpCfr: CFRPopup? = null

/**
* Start observing [store] for updates which may trigger showing a CFR.
*/
@Suppress("MagicNumber")
fun start() {
if (settings.shouldShowTotalCookieProtectionCFR) {
tcpCfrScope = store.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 [store] 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(
indicatorDirection = if (settings.toolbarPosition == ToolbarPosition.TOP) {
CFRPopup.IndicatorDirection.UP
} else {
CFRPopup.IndicatorDirection.DOWN
},
),
) {
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
)
)
tcpCfr?.dismiss()
},
style = FirefoxTheme.typography.body2.copy(
textDecoration = TextDecoration.Underline
)
)
}.apply {
settings.shouldShowTotalCookieProtectionCFR = false
tcpCfr = this
show()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.mozilla.fenix.components.toolbar

import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
Expand Down Expand Up @@ -95,6 +96,15 @@ class DefaultToolbarIntegration(
renderStyle = ToolbarFeature.RenderStyle.UncoloredUrl
) {

@VisibleForTesting
internal var cfrPresenter = CFRPresenter(
context = context,
store = context.components.core.store,
settings = context.settings(),
toolbar = toolbar,
sessionId = sessionId
)

init {
toolbar.display.menuBuilder = toolbarMenu.menuBuilder
toolbar.private = isPrivate
Expand Down Expand Up @@ -150,4 +160,14 @@ class DefaultToolbarIntegration(
}
}
}

override fun start() {
super.start()
cfrPresenter.start()
}

override fun stop() {
cfrPresenter.stop()
super.stop()
}
}
10 changes: 5 additions & 5 deletions app/src/main/java/org/mozilla/fenix/compose/cfr/CFRPopup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ data class CFRPopupProperties(
* @param action Optional other composable to show just below the popup text.
*/
class CFRPopup(
private val text: String,
private val anchor: View,
private val properties: CFRPopupProperties = CFRPopupProperties(),
private val onDismiss: (Boolean) -> Unit = {},
private val action: @Composable (() -> Unit) = {}
@get:VisibleForTesting internal val text: String,
@get:VisibleForTesting internal val anchor: View,
@get:VisibleForTesting internal val properties: CFRPopupProperties = CFRPopupProperties(),
@get:VisibleForTesting internal val onDismiss: (Boolean) -> Unit = {},
@get:VisibleForTesting internal val action: @Composable (() -> Unit) = {}
) {
// This is just a facade for the CFRPopupFullScreenLayout composable offering a cleaner API.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ object SupportUtils {
PRIVATE_BROWSING_MYTHS("common-myths-about-private-browsing"),
YOUR_RIGHTS("your-rights"),
TRACKING_PROTECTION("tracking-protection-firefox-android"),
TOTAL_COOKIE_PROTECTION("enhanced-tracking-protection-android"),
WHATS_NEW("whats-new-firefox-preview"),
OPT_OUT_STUDIES("how-opt-out-studies-firefox-android"),
SEND_TABS("send-tab-preview"),
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/org/mozilla/fenix/utils/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
FxNimbus.features.engineSettings.value().totalCookieProtectionEnabled
}

/**
* Indicates if the total cookie protection CRF should be shown.
*/
var shouldShowTotalCookieProtectionCFR by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_should_show_total_cookie_protection_popup),
default = FxNimbus.features.engineSettings.value().totalCookieProtectionEnabled
)

val blockCookiesSelectionInCustomTrackingProtection by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
appContext.getString(R.string.social)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/preference_keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@
<string name="pref_key_has_inactive_tabs_auto_close_dialog_dismissed" translatable="false">pref_key_has_inactive_tabs_auto_close_dialog_dismissed</string>
<!-- A value of `true` means the jump back in onboarding popup has not been shown yet -->
<string name="pref_key_should_show_jump_back_in_tabs_popup" translatable="false">pref_key_should_show_jump_back_in_tabs_popup</string>
<!-- A value of `true` means the total cookie protection popup has not been shown yet -->
<string name="pref_key_should_show_total_cookie_protection_popup" translatable="false">pref_key_should_show_total_cookie_protection_popup</string>

<string name="pref_key_debug_settings" translatable="false">pref_key_debug_settings</string>

Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
<!-- Content description for close button used in "contextual feature recommendation" (CFR) popups -->
<string name="cfr_dismiss_button_default_content_description">Dismiss</string>

<!-- Total cookie protection "contextual feature recommendation" (CFR) -->
<!-- Text for the main message. -->
<string name="tcp_cfr_message">Our most powerful privacy feature yet isolates cross-site trackers.</string>
<!-- Text displayed that links to website containing documentation about the "Total cookie protection" feature. -->
<string name="tcp_cfr_learn_more">Learn about Total Cookie Protection</string>

<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Camera access needed. Go to Android settings, tap permissions, and tap allow.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
Expand Down
Loading

0 comments on commit 4810a91

Please sign in to comment.