commit 5268af90d48886646439fb7f46fe2374e4ac2561
parent a07e6bed44d2163b5fbd9c2fe21a45e0efc90dbc
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date: Mon, 10 Nov 2025 12:00:53 +0000
Bug 1998659 - Migrate AboutFragment to be fully composable r=android-reviewers,giorga
This patch refactors the `AboutFragment` to be a fully composable screen, removing the dependency on fragment lifecycle methods like `onCreate` and `onResume`.
Key changes include:
- Moving logic for initializing content, version info, and click handlers into the main `@Composable` `Content` function using `remember`.
- Simplifying the `SecretSettingsUnlocker` to accept a callback lambda, decoupling it from `AppAction` dispatching and making it more reusable.
- Changing the `openLearnMore` function to return `Unit` instead of `Job`, as it is a fire-and-forget action.
Differential Revision: https://phabricator.services.mozilla.com/D271615
Diffstat:
3 files changed, 113 insertions(+), 115 deletions(-)
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/AboutFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/AboutFragment.kt
@@ -4,7 +4,7 @@
package org.mozilla.focus.fragment.about
-import android.os.Bundle
+import android.content.Context
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -16,13 +16,14 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
import androidx.core.content.pm.PackageInfoCompat
-import kotlinx.coroutines.Job
import mozilla.components.browser.state.state.SessionState
import mozilla.components.support.utils.ext.getPackageInfoCompat
import org.mozilla.focus.BuildConfig
@@ -43,34 +44,20 @@ import org.mozilla.geckoview.BuildConfig as GeckoViewBuildConfig
*/
class AboutFragment : BaseComposeFragment() {
- private lateinit var secretSettingsUnlocker: SecretSettingsUnlocker
- private lateinit var appName: String
- private lateinit var aboutHeader: String
- private lateinit var content: String
- private lateinit var aboutContent: String
-
- private val openLearnMore = {
- val tabId = requireContext().components.tabsUseCases.addTab(
- url = manifestoURL,
- source = SessionState.Source.Internal.Menu,
- selectTab = true,
- private = true,
- )
- requireContext().components.appStore.dispatch(AppAction.OpenTab(tabId))
- }
-
override val titleRes: Int = R.string.menu_about
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- secretSettingsUnlocker = SecretSettingsUnlocker(requireContext())
- appName = requireContext().resources.getString(R.string.app_name)
- aboutContent =
- requireContext().getString(R.string.about_content, appName, "")
+ @Composable
+ override fun Content() {
+ val context = LocalContext.current
- aboutHeader = getAboutHeader()
+ val aboutHeader = remember {
+ getAboutHeader(context)
+ }
+
+ val content = remember {
+ val appName = context.getString(R.string.app_name)
+ val aboutContent = context.getString(R.string.about_content, appName, "")
- content =
aboutContent
.replace("<li>", "\u2022 \u0009 ")
.replace("</li>", "\n")
@@ -80,106 +67,117 @@ class AboutFragment : BaseComposeFragment() {
.replace("</p>", "")
.replaceAfter("<br/>", "")
.replace("<br/>", "")
- }
+ }
- override fun onResume() {
- super.onResume()
- secretSettingsUnlocker.resetCounter()
- }
+ val openLearnMore = remember {
+ {
+ val tabId = context.components.tabsUseCases.addTab(
+ url = manifestoURL,
+ source = SessionState.Source.Internal.Menu,
+ selectTab = true,
+ private = true,
+ )
+ context.components.appStore.dispatch(AppAction.OpenTab(tabId))
+ Unit
+ }
+ }
+
+ val secretSettingsUnlocker = remember {
+ SecretSettingsUnlocker(
+ context,
+ ) {
+ context.components.appStore.dispatch(
+ AppAction.SecretSettingsStateChange(
+ true,
+ ),
+ )
+ }
+ }
- @Composable
- override fun Content() {
AboutPageContent(
aboutVersion = aboutHeader,
content = content,
- secretSettingsUnlocker = secretSettingsUnlocker,
+ onLogoClick = secretSettingsUnlocker::increment,
openLearnMore = openLearnMore,
)
}
+}
- private fun getAboutHeader(): String {
- val gecko = " \uD83E\uDD8E "
- val engineIndicator =
- gecko + GeckoViewBuildConfig.MOZ_APP_VERSION + "-" + GeckoViewBuildConfig.MOZ_APP_BUILDID
- val servicesAbbreviation = getString(R.string.services_abbreviation)
- val servicesIndicator = mozilla.components.Build.APPLICATION_SERVICES_VERSION
- val packageInfo =
- requireContext().packageManager.getPackageInfoCompat(requireContext().packageName, 0)
- val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
- val vcsHash = if (BuildConfig.VCS_HASH.isNotBlank()) ", ${BuildConfig.VCS_HASH}" else ""
-
- @Suppress("ImplicitDefaultLocale") // We want LTR in all cases as the version is not translatable.
- return String.format(
- "%s (Build #%s)%s\n%s: %s",
- packageInfo.versionName,
- versionCode + engineIndicator,
- vcsHash,
- servicesAbbreviation,
- servicesIndicator,
- )
- }
-
- @Composable
- private fun AboutPageContent(
- aboutVersion: String,
- content: String,
- secretSettingsUnlocker: SecretSettingsUnlocker,
- openLearnMore: () -> Job,
- ) {
- FocusTheme {
- Column(
- modifier = Modifier
- .padding(8.dp)
- .fillMaxSize()
- .verticalScroll(rememberScrollState()),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- LogoIcon(secretSettingsUnlocker)
- VersionInfo(aboutVersion)
- AboutContent(content)
- LearnMoreLink(openLearnMore)
- }
- }
- }
-
- @Composable
- private fun LogoIcon(secretSettingsUnlocker: SecretSettingsUnlocker) {
- Image(
- painter = painterResource(R.drawable.wordmark2),
- contentDescription = null,
+@Composable
+private fun AboutPageContent(
+ aboutVersion: String,
+ content: String,
+ onLogoClick: () -> Unit,
+ openLearnMore: () -> Unit,
+) {
+ FocusTheme {
+ Column(
modifier = Modifier
- .padding(4.dp)
- .clickable {
- secretSettingsUnlocker.increment()
- },
- )
- }
-
- @Composable
- private fun VersionInfo(aboutVersion: String) {
- SelectionContainer {
- Text(
- text = aboutVersion,
- color = focusColors.aboutPageText,
- style = focusTypography.bodyLarge.copy(
- // Use LTR in all cases since the version is not translatable.
- textDirection = TextDirection.Ltr,
- ),
- modifier = Modifier
- .padding(10.dp),
- )
+ .padding(8.dp)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ LogoIcon(onLogoClick)
+ VersionInfo(aboutVersion)
+ AboutContent(content)
+ LearnMoreLink(openLearnMore)
}
}
+}
- @Composable
- private fun AboutContent(content: String) {
+@Composable
+private fun LogoIcon(onLogoClick: () -> Unit) {
+ Image(
+ painter = painterResource(R.drawable.wordmark2),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(4.dp)
+ .clickable(onClick = onLogoClick),
+ )
+}
+
+@Composable
+private fun VersionInfo(aboutVersion: String) {
+ SelectionContainer {
Text(
- text = content,
+ text = aboutVersion,
color = focusColors.aboutPageText,
- style = focusTypography.bodyLarge,
+ style = focusTypography.bodyLarge.copy(
+ // Use LTR in all cases since the version is not translatable.
+ textDirection = TextDirection.Ltr,
+ ),
modifier = Modifier
.padding(10.dp),
)
}
}
+
+@Composable
+private fun AboutContent(content: String) {
+ Text(
+ text = content,
+ color = focusColors.aboutPageText,
+ style = focusTypography.bodyLarge,
+ modifier = Modifier
+ .padding(10.dp),
+ )
+}
+
+private fun getAboutHeader(context: Context): String {
+ val gecko = " \uD83E\uDD8E "
+ val engineIndicator =
+ gecko + GeckoViewBuildConfig.MOZ_APP_VERSION + "-" + GeckoViewBuildConfig.MOZ_APP_BUILDID
+ val servicesAbbreviation = context.getString(R.string.services_abbreviation)
+ val servicesIndicator = mozilla.components.Build.APPLICATION_SERVICES_VERSION
+ val packageInfo =
+ context.packageManager.getPackageInfoCompat(context.packageName, 0)
+ val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
+ val vcsHash = if (BuildConfig.VCS_HASH.isNotBlank()) ", ${BuildConfig.VCS_HASH}" else ""
+
+ return """
+ ${packageInfo.versionName} (Build #${versionCode}$engineIndicator)$vcsHash
+ $servicesAbbreviation: $servicesIndicator
+ """.trimIndent()
+}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/SecretSettingsUnlocker.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/about/SecretSettingsUnlocker.kt
@@ -7,13 +7,14 @@ package org.mozilla.focus.fragment.about
import android.content.Context
import android.widget.Toast
import org.mozilla.focus.R
-import org.mozilla.focus.ext.components
-import org.mozilla.focus.state.AppAction
/**
* Triggers the "secret" debug menu when logoView is tapped 5 times.
*/
-class SecretSettingsUnlocker(private val context: Context) {
+class SecretSettingsUnlocker(
+ private val context: Context,
+ private val onLogoClicked: () -> Unit,
+) {
private var secretSettingsClicks = 0
private var lastDebugMenuToast: Toast? = null
@@ -47,7 +48,7 @@ class SecretSettingsUnlocker(private val context: Context) {
R.string.about_debug_menu_toast_done,
Toast.LENGTH_LONG,
).show()
- context.components.appStore.dispatch(AppAction.SecretSettingsStateChange(true))
+ onLogoClicked()
}
}
}
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/preferences/LearnMoreLink.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/ui/preferences/LearnMoreLink.kt
@@ -14,7 +14,6 @@ import androidx.compose.ui.Alignment.Companion.Start
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.Job
import org.mozilla.focus.R
import org.mozilla.focus.ui.theme.focusColors
import org.mozilla.focus.ui.theme.focusTypography
@@ -27,7 +26,7 @@ import org.mozilla.focus.ui.theme.focusTypography
*/
@Composable
fun ColumnScope.LearnMoreLink(
- openLearnMore: () -> Job,
+ openLearnMore: () -> Unit,
modifier: Modifier = Modifier,
) {
Text(