commit 86e5433eba0668ba4dbe95fcaccc0a2b99bd8893 parent 01ad523e828a0be2340bfe8d2d8419caa557c97e Author: Sandor Molnar <smolnar@mozilla.com> Date: Fri, 19 Dec 2025 09:00:15 +0200 Revert "Bug 2005393 - Add a Firefox Labs screen in Settings r=android-reviewers,android-l10n-reviewers,flod,petru" for causing fenix build bustages This reverts commit ce9d380b6de880d141d0a3a7974606eb8a6ceaf2. Diffstat:
14 files changed, 0 insertions(+), 1108 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -487,10 +487,6 @@ class SettingsFragment : PreferenceFragmentCompat() { SettingsFragmentDirections.actionSettingsFragmentToOpenDownloadsSettingsFragment() } - resources.getString(R.string.pref_key_firefox_labs) -> { - SettingsFragmentDirections.actionSettingsFragmentToFirefoxLabsFragment() - } - resources.getString(R.string.pref_key_sync_debug) -> { SettingsFragmentDirections.actionSettingsFragmentToSyncDebugFragment() } @@ -580,9 +576,6 @@ class SettingsFragment : PreferenceFragmentCompat() { findPreference<Preference>( getPreferenceKey(R.string.pref_key_sync_debug), )?.isVisible = showSecretDebugMenuThisSession - findPreference<Preference>( - getPreferenceKey(R.string.pref_key_firefox_labs), - )?.isVisible = enableFirefoxLabs preferenceStartProfiler?.isVisible = showSecretDebugMenuThisSession && (components.core.engine.profiler?.isProfilerActive() != null) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/FirefoxLabsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/FirefoxLabsFragment.kt @@ -1,74 +0,0 @@ -/* 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.settings.labs - -import android.content.Intent -import android.os.Bundle -import android.os.Process -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.fragment.compose.content -import androidx.navigation.fragment.findNavController -import mozilla.components.lib.state.helpers.StoreProvider.Companion.fragmentStore -import org.mozilla.fenix.ext.hideToolbar -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.settings.labs.middleware.LabsMiddleware -import org.mozilla.fenix.settings.labs.store.LabsState -import org.mozilla.fenix.settings.labs.store.LabsStore -import org.mozilla.fenix.settings.labs.ui.FirefoxLabsScreen -import org.mozilla.fenix.theme.FirefoxTheme - -/** - * Fragment for displaying the Firefox Labs screen. - */ -class FirefoxLabsFragment : Fragment() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - hideToolbar() - } - - private val labsStore by fragmentStore( - initialState = LabsState.INITIAL, - ) { - LabsStore( - initialState = it, - middleware = listOf( - LabsMiddleware( - settings = requireContext().settings(), - onRestart = ::restartFenix, - ), - ), - ) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View = content { - FirefoxTheme { - FirefoxLabsScreen( - store = labsStore, - onNavigationIconClick = { - this@FirefoxLabsFragment.findNavController().popBackStack() - }, - ) - } - } - - private fun restartFenix() { - val context = activity?.applicationContext - context?.startActivity( - Intent.makeRestartActivityTask( - context.packageManager.getLaunchIntentForPackage(context.packageName)?.component, - ), - ) - // Kill the existing process to ensure we get a clean start of the application - Process.killProcess(Process.myPid()) - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/LabsFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/LabsFeature.kt @@ -1,29 +0,0 @@ -/* 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.settings.labs - -import androidx.annotation.StringRes - -/** - * Value type that represents a labs feature. - * - * @property key The [FeatureKey] of the feature. - * @property name The string resource ID of the feature name. - * @property description The string resource ID of the feature description. - * @property enabled Whether or not the feature is enabled. - */ -data class LabsFeature( - val key: FeatureKey, - @param:StringRes val name: Int, - @param:StringRes val description: Int, - val enabled: Boolean, -) - -/** - * Enum that represents a labs feature. - */ -enum class FeatureKey { - HOMEPAGE_AS_A_NEW_TAB, -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/middleware/LabsMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/middleware/LabsMiddleware.kt @@ -1,104 +0,0 @@ -/* 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.settings.labs.middleware - -import android.content.SharedPreferences -import androidx.core.content.edit -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import mozilla.components.lib.state.Middleware -import mozilla.components.lib.state.MiddlewareContext -import mozilla.components.lib.state.Store -import org.mozilla.fenix.R -import org.mozilla.fenix.settings.labs.FeatureKey -import org.mozilla.fenix.settings.labs.LabsFeature -import org.mozilla.fenix.settings.labs.store.LabsAction -import org.mozilla.fenix.settings.labs.store.LabsState -import org.mozilla.fenix.utils.Settings - -/** - * [Middleware] implementation for handling [LabsAction] and managing the [LabsState] for the - * Firefox Labs screen. - * - * @param settings An instance of [Settings] to read and write to the [SharedPreferences] - * properties. - * @param onRestart Callback invoked to restart the application. - * @param scope [CoroutineScope] used to launch coroutines. - */ -class LabsMiddleware( - private val settings: Settings, - private val onRestart: () -> Unit, - private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO), -) : Middleware<LabsState, LabsAction> { - - override fun invoke( - context: MiddlewareContext<LabsState, LabsAction>, - next: (LabsAction) -> Unit, - action: LabsAction, - ) { - when (action) { - is LabsAction.InitAction -> initialize(store = context.store) - is LabsAction.RestartApplication -> restartApplication() - is LabsAction.RestoreDefaults -> restoreDefaults(store = context.store) - is LabsAction.ToggleFeature -> toggleFeature( - store = context.store, - feature = action.feature, - ) - - else -> Unit - } - - next(action) - } - - private fun initialize( - store: Store<LabsState, LabsAction>, - ) = scope.launch { - val features = listOf( - LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = settings.enableHomepageAsNewTab, - ), - ) - - store.dispatch(LabsAction.UpdateFeatures(features)) - } - - private fun toggleFeature( - store: Store<LabsState, LabsAction>, - feature: LabsFeature, - ) = scope.launch { - setFeatureEnabled(key = feature.key, enabled = !feature.enabled) - store.dispatch(LabsAction.RestartApplication) - } - - private fun restoreDefaults( - store: Store<LabsState, LabsAction>, - ) = scope.launch { - for (key in FeatureKey.entries) { - setFeatureEnabled(key = key, enabled = false) - } - - store.dispatch(LabsAction.RestartApplication) - } - - private fun setFeatureEnabled(key: FeatureKey, enabled: Boolean) = scope.launch { - when (key) { - FeatureKey.HOMEPAGE_AS_A_NEW_TAB -> { - settings.enableHomepageAsNewTab = enabled - } - } - } - - private fun restartApplication() = scope.launch { - settings.preferences.edit { - commit() - } - onRestart() - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/store/LabsAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/store/LabsAction.kt @@ -1,63 +0,0 @@ -/* 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.settings.labs.store - -import mozilla.components.lib.state.Action -import org.mozilla.fenix.settings.labs.LabsFeature - -/** - * Actions to dispatch through the [LabsStore] to modify the [LabsState]. - */ -sealed class LabsAction : Action { - - /** - * [LabsAction] dispatched to indicate that the store is initialized and ready to use. - * This action is dispatched automatically before any other action is processed. - * Its main purpose is to trigger initialization logic in middlewares. - */ - data object InitAction : LabsAction() - - /** - * [LabsAction] dispatched when the list of features is updated. - * - * @property features The new list of [LabsFeature] to store. - */ - data class UpdateFeatures(val features: List<LabsFeature>) : LabsAction() - - /** - * [LabsAction] dispatched when a feature is toggled. - * - * @property feature The [LabsFeature] to toggle. - */ - data class ToggleFeature(val feature: LabsFeature) : LabsAction() - - /** - * [LabsAction] dispatched to restore the default settings without any lab features enabled. - */ - data object RestoreDefaults : LabsAction() - - /** - * [LabsAction] dispatched to restart the application. - */ - data object RestartApplication : LabsAction() - - /** - * [LabsAction] dispatched to show the dialog for toggling a [LabsFeature]. - * - * @property feature The [LabsFeature] that will be toggled. - */ - data class ShowToggleFeatureDialog(val feature: LabsFeature) : LabsAction() - - /** - * [LabsAction] dispatched to show the dialog for restoring all the [LabsFeature]s to their - * default disabled state. - */ - data object ShowRestoreDefaultsDialog : LabsAction() - - /** - * [LabsAction] dispatched to close the current dialog. - */ - data object CloseDialog : LabsAction() -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/store/LabsState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/store/LabsState.kt @@ -1,48 +0,0 @@ -/* 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.settings.labs.store - -import mozilla.components.lib.state.State -import org.mozilla.fenix.settings.labs.LabsFeature - -/** - * Value type that represents the state of the Labs screen. - * - * @property labsFeatures A list of [LabsFeature]s to display. - * @property dialogState The current dialog being displayed. - */ -data class LabsState( - val labsFeatures: List<LabsFeature>, - val dialogState: DialogState, -) : State { - companion object { - val INITIAL = LabsState( - labsFeatures = emptyList(), - dialogState = DialogState.Closed, - ) - } -} - -/** - * Represents the dialog state of the Firefox Labs screen. - */ -sealed interface DialogState { - /** - * The dialog for toggling a [LabsFeature] on or off. - * - * @property feature The [LabsFeature] being toggled. - */ - data class ToggleFeature(val feature: LabsFeature) : DialogState - - /** - * The dialog for restoring all [LabsFeature]s to their default disabled state. - */ - object RestoreDefaults : DialogState - - /** - * No dialog is being shown. - */ - object Closed : DialogState -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/store/LabsStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/store/LabsStore.kt @@ -1,66 +0,0 @@ -/* 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.settings.labs.store - -import mozilla.components.lib.state.Middleware -import mozilla.components.lib.state.Store - -/** - * The [Store] for holding the [LabsState] and applying [LabsAction]s. - */ -class LabsStore( - initialState: LabsState, - middleware: List<Middleware<LabsState, LabsAction>> = listOf(), -) : Store<LabsState, LabsAction>( - initialState = initialState, - reducer = ::reducer, - middleware = middleware, -) { - init { - dispatch(LabsAction.InitAction) - } -} - -private fun reducer(state: LabsState, action: LabsAction): LabsState { - return when (action) { - is LabsAction.InitAction, - is LabsAction.RestartApplication, - -> state - - is LabsAction.UpdateFeatures -> state.copy( - labsFeatures = action.features, - ) - - is LabsAction.RestoreDefaults -> state.copy( - labsFeatures = state.labsFeatures.map { - it.copy(enabled = false) - }, - dialogState = DialogState.Closed, - ) - - is LabsAction.ToggleFeature -> state.copy( - labsFeatures = state.labsFeatures.map { - if (it.key == action.feature.key) { - it.copy(enabled = !it.enabled) - } else { - it - } - }, - dialogState = DialogState.Closed, - ) - - is LabsAction.ShowToggleFeatureDialog -> state.copy( - dialogState = DialogState.ToggleFeature(action.feature), - ) - - is LabsAction.ShowRestoreDefaultsDialog -> state.copy( - dialogState = DialogState.RestoreDefaults, - ) - - is LabsAction.CloseDialog -> state.copy( - dialogState = DialogState.Closed, - ) - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/ui/FirefoxLabsScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/labs/ui/FirefoxLabsScreen.kt @@ -1,382 +0,0 @@ -/* 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.settings.labs.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview -import mozilla.components.compose.base.button.FilledButton -import mozilla.components.compose.base.button.IconButton -import mozilla.components.compose.base.button.TextButton -import mozilla.components.compose.base.utils.BackInvokedHandler -import mozilla.components.lib.state.ext.observeAsState -import org.mozilla.fenix.R -import org.mozilla.fenix.compose.list.SwitchListItem -import org.mozilla.fenix.settings.labs.FeatureKey -import org.mozilla.fenix.settings.labs.LabsFeature -import org.mozilla.fenix.settings.labs.store.DialogState -import org.mozilla.fenix.settings.labs.store.LabsAction -import org.mozilla.fenix.settings.labs.store.LabsState -import org.mozilla.fenix.settings.labs.store.LabsStore -import org.mozilla.fenix.theme.FirefoxTheme -import org.mozilla.fenix.theme.Theme -import mozilla.components.ui.icons.R as iconsR - -/** - * Firefox Labs screen that displays a list of experimental features that can be opted into. - * - * @param store The [LabsStore] used to observe the screen state and dispatch actions. - * @param onNavigationIconClick Callback invoked when the navigation icon is clicked. - */ -@Composable -fun FirefoxLabsScreen( - store: LabsStore, - onNavigationIconClick: () -> Unit, -) { - val labsFeatures by store.observeAsState(initialValue = store.state.labsFeatures) { state -> - state.labsFeatures - } - - BackInvokedHandler { - onNavigationIconClick() - } - - Scaffold( - topBar = { - FirefoxLabsTopAppBar( - onNavigationIconClick = onNavigationIconClick, - ) - }, - ) { paddingValues -> - if (labsFeatures.isEmpty()) { - EmptyState(modifier = Modifier.padding(paddingValues)) - } else { - FirefoxLabsScreenContent( - labsFeatures = labsFeatures, - paddingValues = paddingValues, - onToggleFeature = { feature -> store.dispatch(LabsAction.ShowToggleFeatureDialog(feature)) }, - onRestoreDefaultsButtonClick = { store.dispatch(LabsAction.ShowRestoreDefaultsDialog) }, - ) - } - } - - FirefoxLabsDialog(store = store) -} - -@Composable -private fun FirefoxLabsScreenContent( - labsFeatures: List<LabsFeature>, - paddingValues: PaddingValues, - onToggleFeature: (LabsFeature) -> Unit, - onRestoreDefaultsButtonClick: () -> Unit, -) { - LazyColumn( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - ) { - item { - Text( - text = String.format( - stringResource(R.string.firefox_labs_experimental_description), - stringResource(R.string.app_name), - ), - modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 16.dp), - style = FirefoxTheme.typography.body1, - ) - } - - items(labsFeatures) { feature -> - SwitchListItem( - label = stringResource(id = feature.name), - checked = feature.enabled, - description = stringResource(id = feature.description), - maxDescriptionLines = Int.MAX_VALUE, - showSwitchAfter = true, - onClick = { onToggleFeature(feature) }, - ) - } - - item { - FilledButton( - text = stringResource(R.string.firefox_labs_restore_default_button_text), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 24.dp), - onClick = onRestoreDefaultsButtonClick, - ) - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun FirefoxLabsTopAppBar(onNavigationIconClick: () -> Unit) { - TopAppBar( - title = { - Text( - text = stringResource(R.string.firefox_labs_title), - style = FirefoxTheme.typography.headline5, - ) - }, - navigationIcon = { - IconButton( - onClick = onNavigationIconClick, - contentDescription = null, - ) { - Icon( - painter = painterResource(iconsR.drawable.mozac_ic_back_24), - contentDescription = null, - ) - } - }, - windowInsets = WindowInsets( - top = 0.dp, - bottom = 0.dp, - ), - ) -} - -@Composable -private fun EmptyState(modifier: Modifier = Modifier) { - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image( - painter = painterResource(R.drawable.ic_onboarding_marketing_redesign), - contentDescription = null, - ) - - Spacer(modifier = Modifier.height(FirefoxTheme.layout.space.static200)) - - Text( - text = stringResource(id = R.string.firefox_labs_no_labs_available_description), - color = MaterialTheme.colorScheme.onSurface, - style = FirefoxTheme.typography.headline6, - ) - - Spacer(modifier = Modifier.height(FirefoxTheme.layout.space.static600)) - } -} - -@Composable -private fun FirefoxLabsDialog(store: LabsStore) { - val dialogState by store.observeAsState(initialValue = store.state.dialogState) { state -> - state.dialogState - } - - when (val currentDialog = dialogState) { - is DialogState.ToggleFeature -> { - ToggleFeatureDialog( - featureEnabled = currentDialog.feature.enabled, - onConfirm = { - store.dispatch(LabsAction.ToggleFeature(feature = currentDialog.feature)) - }, - onDismiss = { - store.dispatch(LabsAction.CloseDialog) - }, - ) - } - - is DialogState.RestoreDefaults -> { - RestoreDefaultsDialog( - onConfirm = { - store.dispatch(LabsAction.RestoreDefaults) - }, - onDismiss = { - store.dispatch(LabsAction.CloseDialog) - }, - ) - } - - DialogState.Closed -> {} - } -} - -@Composable -private fun ToggleFeatureDialog( - featureEnabled: Boolean, - onConfirm: () -> Unit, - onDismiss: () -> Unit, -) { - AlertDialog( - onDismissRequest = onDismiss, - confirmButton = { - TextButton( - text = stringResource(R.string.firefox_labs_dialog_restart_button), - onClick = onConfirm, - ) - }, - dismissButton = { - TextButton( - text = stringResource(R.string.firefox_labs_dialog_cancel_button), - onClick = onDismiss, - ) - }, - title = { - Text( - text = if (featureEnabled) { - stringResource(R.string.firefox_labs_disable_feature_dialog_title) - } else { - stringResource(R.string.firefox_labs_enable_feature_dialog_title) - }, - style = FirefoxTheme.typography.headline5, - ) - }, - text = { - Text( - text = String.format( - stringResource(R.string.firefox_labs_enable_feature_dialog_message), - stringResource(R.string.app_name), - ), - style = FirefoxTheme.typography.body2, - ) - }, - ) -} - -@Composable -private fun RestoreDefaultsDialog( - onConfirm: () -> Unit, - onDismiss: () -> Unit, -) { - AlertDialog( - onDismissRequest = onDismiss, - confirmButton = { - TextButton( - text = stringResource(R.string.firefox_labs_dialog_restart_button), - onClick = onConfirm, - ) - }, - dismissButton = { - TextButton( - text = stringResource(R.string.firefox_labs_dialog_cancel_button), - onClick = onDismiss, - ) - }, - title = { - Text( - text = stringResource(R.string.firefox_labs_restore_defaults_dialog_title), - style = FirefoxTheme.typography.headline5, - ) - }, - text = { - Text( - text = String.format( - stringResource(R.string.firefox_labs_restore_defaults_dialog_message), - stringResource(R.string.app_name), - ), - style = FirefoxTheme.typography.body2, - ) - }, - ) -} - -private class FirefoxLabsScreenPreviewProvider : PreviewParameterProvider<List<LabsFeature>> { - override val values: Sequence<List<LabsFeature>> - get() { - val sequenceOf = sequenceOf( - listOf( - LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = true, - ), - ), - emptyList(), - ) - return sequenceOf - } -} - -@Composable -@FlexibleWindowLightDarkPreview -private fun FirefoxLabsScreenPreview( - @PreviewParameter(FirefoxLabsScreenPreviewProvider::class) labsFeatures: List<LabsFeature>, -) { - FirefoxTheme { - FirefoxLabsScreen( - store = LabsStore( - initialState = LabsState( - labsFeatures = labsFeatures, - dialogState = DialogState.Closed, - ), - ), - onNavigationIconClick = {}, - ) - } -} - -@Composable -@Preview -private fun FirefoxLabsScreenPrivatePreview( - @PreviewParameter(FirefoxLabsScreenPreviewProvider::class) labsFeatures: List<LabsFeature>, -) { - FirefoxTheme(theme = Theme.Private) { - FirefoxLabsScreen( - store = LabsStore( - initialState = LabsState( - labsFeatures = labsFeatures, - dialogState = DialogState.Closed, - ), - ), - onNavigationIconClick = {}, - ) - } -} - -@Composable -@PreviewLightDark -private fun ToggleFeatureDialogPreview() { - FirefoxTheme { - ToggleFeatureDialog( - featureEnabled = true, - onConfirm = {}, - onDismiss = {}, - ) - } -} - -@Composable -@PreviewLightDark -private fun RestoreDefaultsDialogPreview() { - FirefoxTheme { - RestoreDefaultsDialog( - onConfirm = {}, - onDismiss = {}, - ) - } -} diff --git a/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml b/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml @@ -756,13 +756,6 @@ app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> <action - android:id="@+id/action_settingsFragment_to_firefoxLabsFragment" - app:destination="@id/firefoxLabsFragment" - app:enterAnim="@anim/slide_in_right" - app:exitAnim="@anim/slide_out_left" - app:popEnterAnim="@anim/slide_in_left" - app:popExitAnim="@anim/slide_out_right" /> - <action android:id="@+id/action_settingsFragment_to_linkSharingFragment" app:destination="@id/linkSharingFragment" app:enterAnim="@anim/slide_in_right" @@ -1069,9 +1062,6 @@ app:nullable="true" /> </fragment> <fragment - android:id="@+id/firefoxLabsFragment" - android:name="org.mozilla.fenix.settings.labs.FirefoxLabsFragment" /> - <fragment android:id="@+id/linkSharingFragment" android:name="org.mozilla.fenix.settings.LinkSharingFragment" /> <fragment diff --git a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml @@ -16,7 +16,6 @@ <string name="pref_key_accessibility_font_scale" translatable="false">pref_key_accessibility_font_scale</string> <string name="pref_key_accessibility_force_enable_zoom" translatable="false">pref_key_accessibility_force_enable_zoom</string> <string name="pref_key_advanced" translatable="false">pref_key_advanced</string> - <string name="pref_key_firefox_labs" translatable="false">pref_key_firefox_labs</string> <string name="pref_key_language" translatable="false">pref_key_language</string> <string name="pref_key_translation" translatable="false">pref_key_translation</string> <string name="pref_key_data_choices" translatable="false">pref_key_data_choices</string> diff --git a/mobile/android/fenix/app/src/main/res/values/static_strings.xml b/mobile/android/fenix/app/src/main/res/values/static_strings.xml @@ -269,35 +269,4 @@ <!-- Label for enabling Relay email masks --> <string name="preferences_enable_relay_email_masks">Enable Relay email masks</string> - - <!-- Firefox Labs --> - <!-- Firefox Labs is the name of a screen in Settings to allow users to learn about - experimental and in-development features, and turn those features on and off. - The "Labs" portion can be localized, "Firefox" must be treated as a brand and kept - in English. --> - <string name="firefox_labs_title" tools:ignore="BrandUsage">Firefox Labs</string> - <!-- Description text displayed in the Firefox Labs screen. %s is the name of the app (for example "Firefox"). --> - <string name="firefox_labs_experimental_description">Give our experimental features a try! They’re in development and evolving, which could impact how %s works.</string> - <!-- Button text for restoring the default settings by turning off any experimental features in the Firefox Labs screen. --> - <string name="firefox_labs_restore_default_button_text">Restore Defaults</string> - <!-- Text displayed when no experimental features are available in the Firefox Labs screen. --> - <string name="firefox_labs_no_labs_available_description">No experimental features available</string> - <!-- The name of an experimental feature displayed in theFirefox Labs screen. This features allows the user to open the homepage as a new tab. --> - <string name="firefox_labs_homepage_as_a_new_tab">Homepage as a New Tab</string> - <!-- The description of the "Homepage as a New Tab" feature in the Firefox Labs screen. --> - <string name="firefox_labs_homepage_as_a_new_tab_description">With this feature enabled, Homepage will behave as a tab.</string> - <!-- The title used in a confirmation dialog when toggling on an experimental feature. --> - <string name="firefox_labs_enable_feature_dialog_title">Enable experimental feature?</string> - <!-- The title used in a confirmation dialog when toggling off an experimental feature. --> - <string name="firefox_labs_disable_feature_dialog_title">Disable experimental feature?</string> - <!-- The description that is displayed in the confirmation dialog for toggling on and off experimental features. --> - <string name="firefox_labs_enable_feature_dialog_message">Toggling this feature will restart %s.</string> - <!-- The text for the positive button in the dialog for toggling on and off experimental features. This prompts the user to confirm that they want to restart the app. --> - <string name="firefox_labs_dialog_restart_button">Restart</string> - <!-- The text for the negative button in the dialog for toggling on and off experimental features. --> - <string name="firefox_labs_dialog_cancel_button">Cancel</string> - <!-- The title used in a confirmation dialog when restoring the application to its defaults by turning off any experimental features. --> - <string name="firefox_labs_restore_defaults_dialog_title">Restore defaults?</string> - <!-- The description used in a confirmation dialog when restoring the application to its defaults by turning off any experimental features. --> - <string name="firefox_labs_restore_defaults_dialog_message">Resetting experimental features will restart %s.</string> </resources> diff --git a/mobile/android/fenix/app/src/main/res/xml/preferences.xml b/mobile/android/fenix/app/src/main/res/xml/preferences.xml @@ -183,12 +183,6 @@ app:iconSpaceReserved="false" android:title="@string/preferences_downloads" /> - <androidx.preference.Preference - android:key="@string/pref_key_firefox_labs" - app:iconSpaceReserved="false" - app:isPreferenceVisible="false" - android:title="@string/firefox_labs_title" /> - <androidx.preference.SwitchPreference android:key="@string/pref_key_leakcanary" android:title="@string/preference_leakcanary" diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/labs/middleware/LabsMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/labs/middleware/LabsMiddlewareTest.kt @@ -1,127 +0,0 @@ -/* 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.settings.labs.middleware - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import mozilla.components.support.test.middleware.CaptureActionsMiddleware -import mozilla.components.support.test.mock -import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.verify -import org.mozilla.fenix.R -import org.mozilla.fenix.settings.labs.FeatureKey -import org.mozilla.fenix.settings.labs.LabsFeature -import org.mozilla.fenix.settings.labs.store.DialogState -import org.mozilla.fenix.settings.labs.store.LabsAction -import org.mozilla.fenix.settings.labs.store.LabsState -import org.mozilla.fenix.settings.labs.store.LabsStore -import org.mozilla.fenix.utils.Settings -import org.robolectric.RobolectricTestRunner - -@OptIn(ExperimentalCoroutinesApi::class) -@RunWith(RobolectricTestRunner::class) -class LabsMiddlewareTest { - - private lateinit var settings: Settings - private val onRestart: () -> Unit = mock() - - @Before - fun setup() { - settings = Settings(testContext) - settings.enableHomepageAsNewTab = false - } - - @Test - fun `WHEN InitAction is dispatched THEN features are initialized from settings`() = runTest(UnconfinedTestDispatcher()) { - val captureMiddleware = CaptureActionsMiddleware<LabsState, LabsAction>() - createStore( - captureMiddleware = captureMiddleware, - scope = backgroundScope, - ) - - // InitAction is dispatched on store creation. - // The middleware then dispatches UpdateFeatures. - captureMiddleware.assertLastAction(LabsAction.UpdateFeatures::class) { action -> - assertEquals(1, action.features.size) - val feature = action.features.first() - assertEquals(FeatureKey.HOMEPAGE_AS_A_NEW_TAB, feature.key) - assertEquals(settings.enableHomepageAsNewTab, feature.enabled) - } - } - - @Test - fun `WHEN RestartApplication action is dispatched THEN onRestart is called`() = runTest(UnconfinedTestDispatcher()) { - val store = createStore(scope = backgroundScope) - - store.dispatch(LabsAction.RestartApplication) - - verify(onRestart).invoke() - } - - @Test - fun `WHEN RestoreDefaults action is dispatched THEN all features are disabled and app restart is requested`() = runTest(UnconfinedTestDispatcher()) { - settings.enableHomepageAsNewTab = true - val captureMiddleware = CaptureActionsMiddleware<LabsState, LabsAction>() - val store = createStore( - captureMiddleware = captureMiddleware, - scope = backgroundScope, - ) - - store.dispatch(LabsAction.RestoreDefaults) - - assertFalse(settings.enableHomepageAsNewTab) - captureMiddleware.assertLastAction(LabsAction.RestartApplication::class) - } - - @Test - fun `WHEN ToggleFeature action is dispatched THEN feature is toggled and app restart is requested`() = runTest(UnconfinedTestDispatcher()) { - val feature = LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = false, - ) - val captureMiddleware = CaptureActionsMiddleware<LabsState, LabsAction>() - val store = createStore( - initialState = LabsState( - labsFeatures = listOf(feature), - dialogState = DialogState.Closed, - ), - captureMiddleware = captureMiddleware, - scope = backgroundScope, - ) - - assertFalse(settings.enableHomepageAsNewTab) - - store.dispatch(LabsAction.ToggleFeature(feature)) - - assertTrue(settings.enableHomepageAsNewTab) - captureMiddleware.assertLastAction(LabsAction.RestartApplication::class) - } - - private fun createStore( - initialState: LabsState = LabsState.INITIAL, - captureMiddleware: CaptureActionsMiddleware<LabsState, LabsAction> = CaptureActionsMiddleware(), - scope: CoroutineScope, - ): LabsStore { - val middleware = LabsMiddleware( - settings = settings, - onRestart = onRestart, - scope = scope, - ) - return LabsStore( - initialState = initialState, - middleware = listOf(captureMiddleware, middleware), - ) - } -} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/labs/store/LabsStoreTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/labs/store/LabsStoreTest.kt @@ -1,160 +0,0 @@ -/* 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.settings.labs.store - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.test.runTest -import mozilla.components.lib.state.Middleware -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.R -import org.mozilla.fenix.settings.labs.FeatureKey -import org.mozilla.fenix.settings.labs.LabsFeature - -@RunWith(AndroidJUnit4::class) -class LabsStoreTest { - - @Test - fun `WHEN store is created THEN init action is dispatched`() { - var initActionObserved = false - val testMiddleware: Middleware<LabsState, LabsAction> = { _, next, action -> - if (action == LabsAction.InitAction) { - initActionObserved = true - } - - next(action) - } - - LabsStore( - initialState = LabsState.INITIAL, - middleware = listOf(testMiddleware), - ) - - assertTrue(initActionObserved) - } - - @Test - fun `WHEN UpdateFeatures action is dispatched THEN labsFeatures are updated`() = runTest { - val store = LabsStore(initialState = LabsState.INITIAL) - - assertTrue(store.state.labsFeatures.isEmpty()) - - val features = listOf( - LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = false, - ), - ) - store.dispatch(LabsAction.UpdateFeatures(features)) - - assertEquals(features, store.state.labsFeatures) - } - - @Test - fun `WHEN RestoreDefaults action is dispatched THEN all features are disabled`() = runTest { - val features = listOf( - LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = true, - ), - ) - val store = LabsStore( - initialState = LabsState( - labsFeatures = features, - dialogState = DialogState.RestoreDefaults, - ), - ) - - store.dispatch(LabsAction.RestoreDefaults) - - store.state.labsFeatures.forEach { - assertFalse(it.enabled) - } - assertEquals(DialogState.Closed, store.state.dialogState) - } - - @Test - fun `WHEN ToggleFeature action is dispatched THEN feature is toggled`() = runTest { - val feature = LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = false, - ) - val store = LabsStore( - initialState = LabsState( - labsFeatures = listOf(feature), - dialogState = DialogState.ToggleFeature(feature), - ), - ) - - assertFalse(store.state.labsFeatures.first().enabled) - - store.dispatch(LabsAction.ToggleFeature(feature)) - - assertTrue(store.state.labsFeatures.first().enabled) - assertEquals(DialogState.Closed, store.state.dialogState) - - store.dispatch(LabsAction.ToggleFeature(feature)) - - assertFalse(store.state.labsFeatures.first().enabled) - assertEquals(DialogState.Closed, store.state.dialogState) - } - - @Test - fun `WHEN ShowToggleFeatureDialog action is dispatched THEN dialogState is updated`() = runTest { - val store = LabsStore(initialState = LabsState.INITIAL) - val feature = LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = false, - ) - - assertEquals(DialogState.Closed, store.state.dialogState) - - store.dispatch(LabsAction.ShowToggleFeatureDialog(feature)) - - assertEquals(DialogState.ToggleFeature(feature), store.state.dialogState) - } - - @Test - fun `WHEN ShowRestoreDefaultsDialog action is dispatched THEN dialogState is updated`() = runTest { - val store = LabsStore(initialState = LabsState.INITIAL) - assertEquals(DialogState.Closed, store.state.dialogState) - - store.dispatch(LabsAction.ShowRestoreDefaultsDialog) - - assertEquals(DialogState.RestoreDefaults, store.state.dialogState) - } - - @Test - fun `WHEN CloseDialog action is dispatched THEN dialogState is updated to Closed`() = runTest { - val feature = LabsFeature( - key = FeatureKey.HOMEPAGE_AS_A_NEW_TAB, - name = R.string.firefox_labs_homepage_as_a_new_tab, - description = R.string.firefox_labs_homepage_as_a_new_tab_description, - enabled = false, - ) - val store = LabsStore( - initialState = LabsState( - labsFeatures = listOf(feature), - dialogState = DialogState.RestoreDefaults, - ), - ) - assertEquals(DialogState.RestoreDefaults, store.state.dialogState) - - store.dispatch(LabsAction.CloseDialog) - - assertEquals(DialogState.Closed, store.state.dialogState) - } -}