Bug 1923874 - [Menu Redesign] Show extension promotion with updated strings when all installed extensions have been disabled r=android-reviewers,petru

Differential Revision: https://phabricator.services.mozilla.com/D225510
This commit is contained in:
iorgamgabriel
2024-10-15 13:05:58 +00:00
parent 439e22cce5
commit 60e832e7f6
13 changed files with 172 additions and 11 deletions

View File

@@ -18,6 +18,8 @@ import mozilla.components.concept.engine.webextension.WebExtensionPageAction
* @property enabled Whether or not this web extension is enabled, defaults to true.
* @property allowedInPrivateBrowsing Whether or not this web extension is allowed in private browsing
* mode. Defaults to false.
* @property isBuiltIn Whether or not this web extension is built-in (packaged with the
* APK file) or coming from an external source.
* @property browserAction The browser action state of this extension.
* @property pageAction The page action state of this extension.
* @property popupSessionId The ID of the session displaying
@@ -30,6 +32,7 @@ data class WebExtensionState(
val name: String? = null,
val enabled: Boolean = true,
val allowedInPrivateBrowsing: Boolean = false,
val isBuiltIn: Boolean = false,
val browserAction: WebExtensionBrowserAction? = null,
val pageAction: WebExtensionPageAction? = null,
val popupSessionId: String? = null,

View File

@@ -547,6 +547,7 @@ object WebExtensionSupport {
getMetadata()?.name,
isEnabled(),
isAllowedInPrivateBrowsing(),
isBuiltIn(),
)
private fun SessionState.isCustomTab() = this is CustomTabSessionState

View File

@@ -303,6 +303,10 @@ class MenuDialogFragment : BottomSheetDialogFragment() {
state.extensionMenuState.showExtensionsOnboarding
}
val showDisabledExtensionsOnboarding by store.observeAsState(initialValue = false) { state ->
state.extensionMenuState.showDisabledExtensionsOnboarding
}
val initRoute = when (args.accesspoint) {
MenuAccessPoint.Browser,
MenuAccessPoint.Home,
@@ -546,6 +550,7 @@ class MenuDialogFragment : BottomSheetDialogFragment() {
recommendedAddons = recommendedAddons,
addonInstallationInProgress = addonInstallationInProgress,
showExtensionsOnboarding = showExtensionsOnboarding,
showDisabledExtensionsOnboarding = showDisabledExtensionsOnboarding,
showManageExtensions = updateManageExtensionsMenuItemVisibility,
webExtensionMenuItems = browserWebExtensionMenuItem,
onBackButtonClick = {

View File

@@ -61,7 +61,6 @@ class WebExtensionsMenuBinding(
}
.collect { webExtensionsFlowState ->
val eligibleExtensions = webExtensionsFlowState.browserState.extensions.values
.filter { it.enabled }
.filterNot {
!it.allowedInPrivateBrowsing &&
webExtensionsFlowState.sessionState.content.private
@@ -89,6 +88,14 @@ class WebExtensionsMenuBinding(
}
}
if (browserWebExtensionMenuItems.isEmpty() && eligibleExtensions.filter { !it.isBuiltIn }
.all { !it.enabled }
) {
menuStore.dispatch(MenuAction.UpdateShowDisabledExtensionsOnboarding(true))
} else {
menuStore.dispatch(MenuAction.UpdateShowDisabledExtensionsOnboarding(false))
}
menuStore.dispatch(
MenuAction.UpdateWebExtensionBrowserMenuItems(browserWebExtensionMenuItems),
)
@@ -98,12 +105,17 @@ class WebExtensionsMenuBinding(
}
}
@Suppress("ReturnCount")
private suspend fun getWebExtensionMenuItem(
extension: WebExtensionState,
webExtensionsFlowState: WebExtensionsFlowState,
globalAction: Action,
isPageAction: Boolean = false,
): WebExtensionMenuItem? {
if (!extension.enabled) {
return null
}
val tabAction = if (isPageAction) {
webExtensionsFlowState.sessionState.extensionState[extension.id]?.pageAction
} else {

View File

@@ -40,12 +40,13 @@ import org.mozilla.fenix.compose.list.TextListItem
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LongMethod")
@Composable
internal fun ExtensionsSubmenu(
recommendedAddons: List<Addon>,
webExtensionMenuItems: List<WebExtensionMenuItem.WebExtensionBrowserMenuItem>,
showExtensionsOnboarding: Boolean,
showDisabledExtensionsOnboarding: Boolean,
showManageExtensions: Boolean,
addonInstallationInProgress: Addon?,
onBackButtonClick: () -> Unit,
@@ -65,16 +66,27 @@ internal fun ExtensionsSubmenu(
)
},
) {
if (showExtensionsOnboarding) {
if (showExtensionsOnboarding || showDisabledExtensionsOnboarding) {
ExtensionsSubmenuBanner(
title = stringResource(
R.string.browser_menu_extensions_banner_onboarding_header,
stringResource(R.string.app_name),
),
description = stringResource(
R.string.browser_menu_extensions_banner_onboarding_body,
stringResource(R.string.app_name),
),
title = if (showExtensionsOnboarding) {
stringResource(
R.string.browser_menu_extensions_banner_onboarding_header,
stringResource(R.string.app_name),
)
} else {
stringResource(R.string.browser_menu_disabled_extensions_banner_onboarding_header)
},
description = if (showExtensionsOnboarding) {
stringResource(
R.string.browser_menu_extensions_banner_onboarding_body,
stringResource(R.string.app_name),
)
} else {
stringResource(
R.string.browser_menu_disabled_extensions_banner_onboarding_body,
stringResource(R.string.browser_menu_manage_extensions),
)
},
linkText = stringResource(R.string.browser_menu_extensions_banner_learn_more),
onClick = onExtensionsLearnMoreClick,
)
@@ -205,6 +217,7 @@ private fun ExtensionsSubmenuPreview() {
),
),
showExtensionsOnboarding = true,
showDisabledExtensionsOnboarding = true,
showManageExtensions = true,
addonInstallationInProgress = Addon(
id = "id",
@@ -277,6 +290,7 @@ private fun ExtensionsSubmenuPrivatePreview() {
),
),
showExtensionsOnboarding = true,
showDisabledExtensionsOnboarding = false,
showManageExtensions = false,
addonInstallationInProgress = null,
onBackButtonClick = {},

View File

@@ -125,6 +125,7 @@ private fun ExtensionsSubmenuBannerText(
},
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.headline7,
textAlign = if (!isWideScreen) TextAlign.Center else null,
)
Text(

View File

@@ -253,6 +253,7 @@ class MenuTelemetryMiddleware(
is MenuAction.InstallAddonSuccess,
is MenuAction.UpdateInstallAddonInProgress,
is MenuAction.UpdateShowExtensionsOnboarding,
is MenuAction.UpdateShowDisabledExtensionsOnboarding,
is MenuAction.UpdateManageExtensionsMenuItemVisibility,
-> Unit
}

View File

@@ -174,6 +174,16 @@ sealed class MenuAction : Action {
val showExtensionsOnboarding: Boolean,
) : MenuAction()
/**
* [MenuAction] dispatched when disabled extensions promotion banner onboarding should be visible or not.
*
* @property showDisabledExtensionsOnboarding Show extensions promotion banner onboarding when
* all installed extensions have been disabled.
*/
data class UpdateShowDisabledExtensionsOnboarding(
val showDisabledExtensionsOnboarding: Boolean,
) : MenuAction()
/**
* [MenuAction] dispatched when a custom item is tapped in the custom tab menu.
*

View File

@@ -46,6 +46,8 @@ data class BrowserMenuState(
*
* @property recommendedAddons A list of recommended [Addon]s to suggest.
* @property showExtensionsOnboarding Show extensions promotion banner onboarding.
* @property showDisabledExtensionsOnboarding Show extensions promotion banner onboarding when
* all installed extensions have been disabled.
* @property addonInstallationInProgress The [Addon] that is currently being installed.
* @property shouldShowManageExtensionsMenuItem Indicates if manage extensions menu item
* should be displayed to the user.
@@ -55,6 +57,7 @@ data class BrowserMenuState(
data class ExtensionMenuState(
val recommendedAddons: List<Addon> = emptyList(),
val showExtensionsOnboarding: Boolean = false,
val showDisabledExtensionsOnboarding: Boolean = false,
val addonInstallationInProgress: Addon? = null,
val shouldShowManageExtensionsMenuItem: Boolean = false,
val browserWebExtensionMenuItem: List<WebExtensionMenuItem.WebExtensionBrowserMenuItem> = emptyList(),

View File

@@ -88,6 +88,10 @@ private fun reducer(state: MenuState, action: MenuAction): MenuState {
extensionState.copy(showExtensionsOnboarding = action.showExtensionsOnboarding)
}
is MenuAction.UpdateShowDisabledExtensionsOnboarding -> state.copyWithExtensionMenuState { extensionState ->
extensionState.copy(showDisabledExtensionsOnboarding = action.showDisabledExtensionsOnboarding)
}
is MenuAction.UpdateManageExtensionsMenuItemVisibility -> state.copyWithExtensionMenuState {
it.copy(shouldShowManageExtensionsMenuItem = action.isVisible)
}

View File

@@ -196,9 +196,14 @@
<!-- Browser menu banner header text for extensions onboarding.
The first parameter is the name of the app defined in app_name (for example: Fenix). -->
<string name="browser_menu_extensions_banner_onboarding_header">Make %s your own</string>
<!-- Browser menu banner header text for extensions onboarding when all installed extensions have been disabled. -->
<string name="browser_menu_disabled_extensions_banner_onboarding_header">You have extensions installed, but not enabled</string>
<!-- Browser menu banner body text for extensions onboarding.
The first parameter is the name of the app defined in app_name (for example: Fenix). -->
<string name="browser_menu_extensions_banner_onboarding_body">Extensions level up your browsing, from changing how %s looks and performs to boosting privacy and safety.</string>
<!-- Browser menu banner body text for extensions onboarding when all installed extensions have been disabled.
The first parameter is the name of the button that opens extension manager (for example "Manage extensions"). -->
<string name="browser_menu_disabled_extensions_banner_onboarding_body">To use extensions, enable them in settings or by selecting “%s” below.</string>
<!-- Browser menu banner link text for learning more about extensions -->
<string name="browser_menu_extensions_banner_learn_more">Learn more</string>
<!-- Browser menu button that opens the extensions manager -->

View File

@@ -404,4 +404,15 @@ class MenuStoreTest {
assertTrue(store.state.extensionMenuState.shouldShowManageExtensionsMenuItem)
}
@Test
fun `WHEN update show disabled extensions onboarding dispatched THEN extension state is updated`() =
runTest {
val initialState = MenuState()
val store = MenuStore(initialState = initialState)
store.dispatch(MenuAction.UpdateShowDisabledExtensionsOnboarding(true)).join()
assertTrue(store.state.extensionMenuState.showDisabledExtensionsOnboarding)
}
}

View File

@@ -18,6 +18,7 @@ import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.test.rule.MainCoroutineRule
import mozilla.components.support.test.rule.runTestOnMain
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -218,6 +219,96 @@ class WebExtensionsMenuBindingTest {
assertTrue(pageItemsUpdateCaptor.value.webExtensionPageMenuItem.isEmpty())
}
@Test
fun `WHEN all web extensions are disabled THEN show disabled extensions onboarding`() =
runTestOnMain {
val extensions: Map<String, WebExtensionState> = mapOf(
"id" to WebExtensionState(
id = "id",
url = "url",
name = "name",
enabled = false,
),
)
menuStore = spy(MenuStore(MenuState()))
browserStore = BrowserStore(
BrowserState(
tabs = listOf(
createTab(
url = "https://www.example.org",
id = "tab1",
extensions = extensions,
),
),
selectedTabId = "tab1",
extensions = extensions,
),
)
val binding = WebExtensionsMenuBinding(
browserStore = browserStore,
menuStore = menuStore,
iconSize = 24.dpToPx(testContext.resources.displayMetrics),
onDismiss = {},
)
binding.start()
val showDisabledExtensionsOnboardingCaptor = argumentCaptor<MenuAction.UpdateShowDisabledExtensionsOnboarding>()
verify(menuStore).dispatch(showDisabledExtensionsOnboardingCaptor.capture())
assertTrue(showDisabledExtensionsOnboardingCaptor.value.showDisabledExtensionsOnboarding)
}
@Test
fun `WHEN only one web extension is disabled THEN not show disabled extensions onboarding`() =
runTestOnMain {
val extensions: Map<String, WebExtensionState> = mapOf(
"id" to WebExtensionState(
id = "id",
url = "url",
name = "name",
enabled = false,
),
"id2" to WebExtensionState(
id = "id2",
url = "url2",
name = "name2",
enabled = true,
),
)
menuStore = spy(MenuStore(MenuState()))
browserStore = BrowserStore(
BrowserState(
tabs = listOf(
createTab(
url = "https://www.example.org",
id = "tab1",
extensions = extensions,
),
),
selectedTabId = "tab1",
extensions = extensions,
),
)
val binding = WebExtensionsMenuBinding(
browserStore = browserStore,
menuStore = menuStore,
iconSize = 24.dpToPx(testContext.resources.displayMetrics),
onDismiss = {},
)
binding.start()
val showDisabledExtensionsOnboardingCaptor = argumentCaptor<MenuAction.UpdateShowDisabledExtensionsOnboarding>()
verify(menuStore).dispatch(showDisabledExtensionsOnboardingCaptor.capture())
assertFalse(showDisabledExtensionsOnboardingCaptor.value.showDisabledExtensionsOnboarding)
}
private fun createWebExtensionPageAction(title: String) = WebExtensionPageAction(
title = title,
enabled = true,