commit 852cb6e02c130d030c9b1c7f645f389e21a32b50
parent 11fd6a9e9ea76c5f000e187303ed6a8342fc1ad9
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date: Wed, 15 Oct 2025 08:08:41 +0000
Bug 1993482 - Migrate settings screen to Jetpack Compose. r=android-reviewers,rebecatudor273
This patch migrates the main settings screen from the legacy preference XML to a Jetpack Compose implementation.
The changes include:
- Creating a new `SettingsScreen.kt` composable to build the settings menu.
- Updating `SettingsFragment` to use Jetpack Compose for rendering its content, replacing the XML-based `addPreferencesFromResource`.
- Adding `preferenceTitle` and `preferenceSummary` text styles to `FocusTypography` for styling the new Compose screen.
- Removing obsolete preference keys from `preference_keys.xml` and settings.xml.
- Remove unnecessary PreferenceManager.setDefaultValues from FocusApplication.
Differential Revision: https://phabricator.services.mozilla.com/D268132
Diffstat:
8 files changed, 127 insertions(+), 102 deletions(-)
diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/SettingsRobot.kt
@@ -3,24 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.focus.activity.robots
-import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import org.junit.Assert.assertTrue
import org.mozilla.focus.R
import org.mozilla.focus.helpers.TestHelper.getStringResource
import org.mozilla.focus.helpers.TestHelper.mDevice
-import org.mozilla.focus.helpers.TestHelper.packageName
import org.mozilla.focus.helpers.TestHelper.waitingTime
class SettingsRobot {
fun verifySettingsMenuItems() {
- settingsMenuList.waitForExists(waitingTime)
- assertTrue(generalSettingsMenu().exists())
- assertTrue(searchSettingsMenu.exists())
- assertTrue(privacySettingsMenu.exists())
- assertTrue(advancedSettingsMenu.exists())
- assertTrue(mozillaSettingsMenu.exists())
+ assertTrue("General settings item not found", generalSettingsMenu().waitForExists(waitingTime))
+ assertTrue("Search settings item not found", searchSettingsMenu.waitForExists(waitingTime))
+ assertTrue("Privacy settings item not found", privacySettingsMenu.waitForExists(waitingTime))
+ assertTrue("Advanced settings item not found", advancedSettingsMenu.waitForExists(waitingTime))
+ assertTrue("Mozilla settings item not found", mozillaSettingsMenu.waitForExists(waitingTime))
}
class Transition {
@@ -84,31 +81,28 @@ class SettingsRobot {
}
}
-private val settingsMenuList =
- UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))
-
private fun generalSettingsMenu(localizedText: String = getStringResource(R.string.preference_category_general)) =
- settingsMenuList.getChild(
+ mDevice.findObject(
UiSelector()
.text(localizedText),
)
-private val searchSettingsMenu = settingsMenuList.getChild(
+private val searchSettingsMenu = mDevice.findObject(
UiSelector()
.text(getStringResource(R.string.preference_category_search)),
)
-private val privacySettingsMenu = settingsMenuList.getChild(
+private val privacySettingsMenu = mDevice.findObject(
UiSelector()
.text(getStringResource(R.string.preference_privacy_and_security_header)),
)
-private val advancedSettingsMenu = settingsMenuList.getChild(
+private val advancedSettingsMenu = mDevice.findObject(
UiSelector()
.text(getStringResource(R.string.preference_category_advanced)),
)
-private val mozillaSettingsMenu = settingsMenuList.getChild(
+private val mozillaSettingsMenu = mDevice.findObject(
UiSelector()
- .text(getStringResource(R.string.preference_mozilla_summary)),
+ .text(getStringResource(R.string.preference_category_mozilla)),
)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/FocusApplication.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/FocusApplication.kt
@@ -12,7 +12,6 @@ import androidx.annotation.OpenForTesting
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.ProcessLifecycleOwner
-import androidx.preference.PreferenceManager
import androidx.work.Configuration.Builder
import androidx.work.Configuration.Provider
import kotlinx.coroutines.CoroutineScope
@@ -65,8 +64,6 @@ open class FocusApplication : LocaleAwareApplication(), Provider, CoroutineScope
if (isMainProcess()) {
initializeNimbus()
- PreferenceManager.setDefaultValues(this, R.xml.settings, false)
-
setTheme(this)
components.engine.warmUp()
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsFragment.kt
@@ -4,41 +4,25 @@
package org.mozilla.focus.settings
-import android.os.Bundle
+import androidx.compose.runtime.Composable
import org.mozilla.focus.R
import org.mozilla.focus.ext.requireComponents
-import org.mozilla.focus.ext.showToolbar
import org.mozilla.focus.state.AppAction
-import org.mozilla.focus.state.Screen
-class SettingsFragment : BaseSettingsFragment() {
-
- override fun onCreatePreferences(bundle: Bundle?, s: String?) {
- addPreferencesFromResource(R.xml.settings)
- }
-
- override fun onResume() {
- super.onResume()
-
- showToolbar(getString(R.string.menu_settings))
- }
-
- override fun onPreferenceTreeClick(preference: androidx.preference.Preference): Boolean {
- val resources = resources
-
- val page = when (preference.key) {
- resources.getString(R.string.pref_key_general_screen) -> Screen.Settings.Page.General
- resources.getString(R.string.pref_key_privacy_security_screen) -> Screen.Settings.Page.Privacy
- resources.getString(R.string.pref_key_search_screen) -> Screen.Settings.Page.Search
- resources.getString(R.string.pref_key_advanced_screen) -> Screen.Settings.Page.Advanced
- resources.getString(R.string.pref_key_mozilla_screen) -> Screen.Settings.Page.Mozilla
- else -> throw IllegalStateException("Unknown preference: ${preference.key}")
+/**
+ * A fragment that displays the main settings screen.
+ * It uses Jetpack Compose to render its UI.
+ *
+ * When a user interacts with a setting that requires navigating to a sub-page (e.g., "Search"),
+ * this fragment dispatches an [AppAction.OpenSettings] action to handle the navigation logic.
+ */
+class SettingsFragment : BaseComposeFragment() {
+ override val titleRes: Int = R.string.menu_settings
+
+ @Composable
+ override fun Content() {
+ SettingsScreen { page ->
+ requireComponents.appStore.dispatch(AppAction.OpenSettings(page))
}
-
- requireComponents.appStore.dispatch(
- AppAction.OpenSettings(page),
- )
-
- return super.onPreferenceTreeClick(preference)
}
}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsScreen.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/SettingsScreen.kt
@@ -0,0 +1,89 @@
+/* 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.focus.settings
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import org.mozilla.focus.R
+import org.mozilla.focus.state.Screen
+import org.mozilla.focus.ui.theme.focusTypography
+
+/**
+ * The main settings screen, displaying a list of top-level setting categories.
+ * Each category, when clicked, navigates to its respective detailed settings page.
+ *
+ * @param onSettingClick A lambda function that is invoked when a setting category is clicked.
+ * It passes the corresponding [Screen.Settings.Page] to the caller, which is responsible
+ * for handling the navigation to the detailed settings screen.
+ */
+@Composable
+fun SettingsScreen(onSettingClick: (Screen.Settings.Page) -> Unit) {
+ Column(modifier = Modifier.fillMaxSize()) {
+ SettingItem(
+ title = R.string.preference_category_general,
+ summary = stringResource(id = R.string.preference_general_summary2),
+ onClick = { onSettingClick(Screen.Settings.Page.General) },
+ )
+ SettingItem(
+ title = R.string.preference_privacy_and_security_header,
+ summary = stringResource(id = R.string.preference_privacy_and_security_summary),
+ onClick = { onSettingClick(Screen.Settings.Page.Privacy) },
+ )
+ SettingItem(
+ title = R.string.preference_category_search,
+ summary = stringResource(id = R.string.preference_search_summary),
+ onClick = { onSettingClick(Screen.Settings.Page.Search) },
+ )
+ SettingItem(
+ title = R.string.preference_category_advanced,
+ summary = stringResource(id = R.string.preference_advanced_summary),
+ onClick = { onSettingClick(Screen.Settings.Page.Advanced) },
+ )
+ SettingItem(
+ title = R.string.preference_category_mozilla,
+ summary = stringResource(
+ id = R.string.preference_mozilla_summary,
+ stringResource(id = R.string.app_name),
+ ),
+ onClick = { onSettingClick(Screen.Settings.Page.Mozilla) },
+ )
+ }
+}
+
+@Composable
+private fun SettingItem(
+ @StringRes title: Int,
+ summary: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .clickable(onClick = onClick)
+ .padding(16.dp),
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = stringResource(id = title),
+ style = focusTypography.preferenceTitle,
+ )
+ Text(
+ text = summary,
+ style = focusTypography.preferenceSummary,
+ )
+ }
+ }
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTypography.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/theme/FocusTypography.kt
@@ -41,6 +41,8 @@ data class FocusTypography(
val onboardingButton: TextStyle,
val cfrTextStyle: TextStyle,
val cfrCookieBannerTextStyle: TextStyle,
+ val preferenceTitle: TextStyle,
+ val preferenceSummary: TextStyle,
) {
val displayLarge: TextStyle get() = materialTypography.displayLarge
@@ -136,4 +138,14 @@ val focusTypography: FocusTypography
letterSpacing = 0.25.sp,
lineHeight = 20.sp,
),
+ preferenceTitle = TextStyle(
+ fontSize = 16.sp,
+ lineHeight = 21.sp, // from 16sp textSize + 5sp lineSpacingExtra
+ color = focusColors.settingsTextColor,
+ ),
+ preferenceSummary = TextStyle(
+ fontSize = 14.sp,
+ letterSpacing = 0.42.sp, // from 14sp textSize * 0.03 letterSpacing
+ color = focusColors.settingsTextSummaryColor,
+ ),
)
diff --git a/mobile/android/focus-android/app/src/main/res/values/preference_keys.xml b/mobile/android/focus-android/app/src/main/res/values/preference_keys.xml
@@ -62,12 +62,6 @@
<string name="pref_key_autocomplete_custom" translatable="false"><xliff:g id="preference_key">pref_autocomplete_custom</xliff:g></string>
<string name="pref_key_screen_custom_domains" translatable="false"><xliff:g id="preference_key">pref_screen_custom_domains</xliff:g></string>
- <string name="pref_key_privacy_security_screen" translatable="false"><xliff:g id="preference_key">pref_screen_privacy_security</xliff:g></string>
- <string name="pref_key_advanced_screen" translatable="false"><xliff:g id="preference_key">pref_advanced_screen</xliff:g></string>
- <string name="pref_key_general_screen" translatable="false"><xliff:g id="preference_key">pref_general_screen</xliff:g></string>
-
- <string name="pref_key_mozilla_screen" translatable="false"><xliff:g id="preference_key">pref_screen_mozilla</xliff:g></string>
- <string name="pref_key_search_screen" translatable="false"><xliff:g id="preference_key">pref_screen_search</xliff:g></string>
<string name="pref_key_remote_debugging" translatable="false"><xliff:g id="preference_key">pref_remote_debugging</xliff:g></string>
<string name="pref_key_open_links_in_external_app" translatable="false"><xliff:g id="preference_key">pref_key_open_links_in_external_app</xliff:g></string>
<string name="pref_key_secret_settings" translatable="false"><xliff:g id="preference_key">pref_key_secret_settings</xliff:g></string>
diff --git a/mobile/android/focus-android/app/src/main/res/xml/settings.xml b/mobile/android/focus-android/app/src/main/res/xml/settings.xml
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
-<androidx.preference.PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <androidx.preference.Preference
- app:iconSpaceReserved="false"
- android:key="@string/pref_key_general_screen"
- android:layout="@layout/focus_preference"
- android:summary="@string/preference_general_summary2"
- android:title="@string/preference_category_general" />
-
- <androidx.preference.Preference
- app:iconSpaceReserved="false"
- android:key="@string/pref_key_privacy_security_screen"
- android:layout="@layout/focus_preference"
- android:summary="@string/preference_privacy_and_security_summary"
- android:title="@string/preference_privacy_and_security_header" />
-
- <androidx.preference.Preference
- app:iconSpaceReserved="false"
- android:key="@string/pref_key_search_screen"
- android:layout="@layout/focus_preference"
- android:summary="@string/preference_search_summary"
- android:title="@string/preference_category_search" />
-
- <androidx.preference.Preference
- app:iconSpaceReserved="false"
- android:key="@string/pref_key_advanced_screen"
- android:layout="@layout/focus_preference"
- android:summary="@string/preference_advanced_summary"
- android:title="@string/preference_category_advanced" />
-
- <org.mozilla.focus.widget.MozillaPreference
- app:iconSpaceReserved="false"
- android:key="@string/pref_key_mozilla_screen"
- android:layout="@layout/focus_preference"
- android:summary="@string/preference_mozilla_summary"
- android:title="@string/preference_category_mozilla"
- app:allowDividerAbove="false"/>
-
-</androidx.preference.PreferenceScreen>
diff --git a/mobile/android/focus-android/quality/detekt-baseline.xml b/mobile/android/focus-android/quality/detekt-baseline.xml
@@ -94,7 +94,6 @@
<ID>UndocumentedPublicClass:SearchWidgetProvider.kt$SearchWidgetProvider : AppSearchWidgetProvider</ID>
<ID>UndocumentedPublicClass:SearchWidgetUtils.kt$SearchWidgetUtils</ID>
<ID>UndocumentedPublicClass:SecretSettingsFragment.kt$SecretSettingsFragment : BaseSettingsFragmentOnSharedPreferenceChangeListener</ID>
- <ID>UndocumentedPublicClass:SettingsFragment.kt$SettingsFragment : BaseSettingsFragment</ID>
<ID>UndocumentedPublicFunction:SitePermissionOptionsStorage.kt$SitePermissionOptionsStorage$fun getSitePermissionLabel(sitePermission: SitePermission): String</ID>
<ID>UndocumentedPublicClass:SitePermission.kt$SitePermission : Parcelable</ID>
<ID>UndocumentedPublicClass:SitePermissionOption.kt$AutoplayOption</ID>