commit fe454103682c79f627933965fce18f03f7c5a84a
parent 9bf87b6331a3b47aab64fccde05f4a7dd915f6ea
Author: Gabriel Luong <gabriel.luong@gmail.com>
Date: Fri, 21 Nov 2025 09:03:42 +0000
Bug 1998073 - Part 6: Migrate SavedLoginsScreen to use M3 Acorn color tokens r=android-reviewers,007
- Fixed previews in SavedLoginsScreen to show the LoginsList
- Renamed mozac_ic_filter to mozac_ic_sort_24 to align with the https://github.com/FirefoxUX/acorn-icons/blob/main/icons/mobile/24/xml/ic_sort_24.xml
Top App Bar: https://www.figma.com/design/MjufE1X5fvkxZ0YneX4kRd/Android-Library--2025-?node-id=65138-3594&m=dev
Saved passwords: https://www.figma.com/design/ctk1Pw1TBxUwVgTTOvjHb4/2025-Android-Fundamentals?node-id=972-25884&m=dev
Differential Revision: https://phabricator.services.mozilla.com/D271191
Diffstat:
4 files changed, 139 insertions(+), 173 deletions(-)
diff --git a/mobile/android/android-components/components/ui/icons/src/main/res/drawable/mozac_ic_filter.xml b/mobile/android/android-components/components/ui/icons/src/main/res/drawable/mozac_ic_filter.xml
@@ -1,18 +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/. -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="@color/mozac_ui_icons_fill"
- android:pathData="M3 4.5H21V6.75H3V4.5Z" />
- <path
- android:fillColor="@color/mozac_ui_icons_fill"
- android:pathData="M5.25 10.5H18.75V12.75H5.25V10.5Z" />
- <path
- android:fillColor="@color/mozac_ui_icons_fill"
- android:pathData="M14.25 16.5H9.75V18.75H14.25V16.5Z" />
-</vector>
diff --git a/mobile/android/android-components/components/ui/icons/src/main/res/drawable/mozac_ic_sort_24.xml b/mobile/android/android-components/components/ui/icons/src/main/res/drawable/mozac_ic_sort_24.xml
@@ -0,0 +1,12 @@
+<!-- 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/. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/mozac_ui_icons_fill"
+ android:pathData="M4,6H20V7.75H4V6ZM6,11.25H18V13H6V11.25ZM10,16.25H14V18H10V16.25Z" />
+</vector>
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/bookmarks/BookmarksScreen.kt
@@ -794,7 +794,7 @@ private fun BookmarksListTopBarActionsNoSelection(
},
) {
Icon(
- painter = painterResource(iconsR.drawable.mozac_ic_filter),
+ painter = painterResource(iconsR.drawable.mozac_ic_sort_24),
contentDescription = stringResource(
R.string.bookmark_sort_menu_content_desc,
),
@@ -1018,7 +1018,7 @@ private fun SelectFolderTopBar(store: BookmarksStore) {
store.dispatch(BookmarksListMenuAction.SortMenu.SortMenuButtonClicked)
}) {
Icon(
- painter = painterResource(iconsR.drawable.mozac_ic_filter),
+ painter = painterResource(iconsR.drawable.mozac_ic_sort_24),
contentDescription = stringResource(
R.string.bookmark_sort_menu_content_desc,
),
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/ui/SavedLoginsScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/logins/ui/SavedLoginsScreen.kt
@@ -5,8 +5,6 @@
package org.mozilla.fenix.settings.logins.ui
import androidx.activity.compose.BackHandler
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -22,10 +20,10 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
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.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -43,6 +41,7 @@ import androidx.compose.ui.semantics.CollectionInfo
import androidx.compose.ui.semantics.collectionInfo
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@@ -53,7 +52,6 @@ import mozilla.components.compose.base.button.IconButton
import mozilla.components.compose.base.menu.DropdownMenu
import mozilla.components.compose.base.menu.MenuItem
import mozilla.components.compose.base.textfield.TextField
-import mozilla.components.compose.base.textfield.TextFieldColors
import mozilla.components.lib.state.ext.observeAsState
import mozilla.components.support.ktx.kotlin.trimmed
import org.mozilla.fenix.R
@@ -64,6 +62,7 @@ import org.mozilla.fenix.compose.list.SelectableFaviconListItem
import org.mozilla.fenix.settings.biometric.ui.SecureScreen
import org.mozilla.fenix.settings.logins.ui.LoginsSortOrder.Alphabetical.isGuidToDelete
import org.mozilla.fenix.theme.FirefoxTheme
+import org.mozilla.fenix.theme.Theme
import mozilla.components.ui.icons.R as iconsR
/**
@@ -133,7 +132,6 @@ private fun LoginsList(store: LoginsStore) {
text = state.searchText ?: "",
)
},
- containerColor = FirefoxTheme.colors.layer1,
contentWindowInsets = WindowInsets(0.dp),
) { paddingValues ->
if (state.searchText.isNullOrEmpty() && state.loginItems.isEmpty()) {
@@ -156,7 +154,6 @@ private fun LoginsList(store: LoginsStore) {
},
) {
itemsIndexed(state.loginItems) { _, item ->
-
if (state.isGuidToDelete(item.guid)) {
return@itemsIndexed
}
@@ -217,8 +214,8 @@ private fun EmptyList(
stringResource(R.string.preferences_passwords_saved_logins_description_empty_text_2),
stringResource(R.string.app_name),
),
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
style = FirefoxTheme.typography.body2,
- color = FirefoxTheme.colors.textPrimary,
)
LinkText(
@@ -230,10 +227,6 @@ private fun EmptyList(
onClick = { dispatcher(LearnMoreAboutSync) },
),
),
- style = FirefoxTheme.typography.body2.copy(
- color = FirefoxTheme.colors.textPrimary,
- ),
- linkTextColor = FirefoxTheme.colors.textPrimary,
linkTextDecoration = TextDecoration.Underline,
)
@@ -254,82 +247,73 @@ private fun LoginsListTopBar(
var showMenu by remember { mutableStateOf(false) }
var searchActive by remember { mutableStateOf(false) }
- val iconColor = FirefoxTheme.colors.iconPrimary
+ TopAppBar(
+ windowInsets = WindowInsets(
+ top = 0.dp,
+ bottom = 0.dp,
+ ),
+ title = {
+ if (!searchActive) {
+ Text(
+ text = stringResource(R.string.preferences_passwords_saved_logins_2),
+ style = FirefoxTheme.typography.headline5,
+ )
+ } else {
+ SearchBar(text, store)
+ }
+ },
+ navigationIcon = {
+ IconButton(
+ onClick = {
+ if (!searchActive) {
+ store.dispatch(LoginsListBackClicked)
+ } else {
+ searchActive = false
+ }
+ },
+ contentDescription = stringResource(R.string.logins_navigate_back_button_content_description),
+ ) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_back_24),
+ contentDescription = null,
+ )
+ }
+ },
+ actions = {
+ if (searchActive) return@TopAppBar
- Box {
- TopAppBar(
- colors = TopAppBarDefaults.topAppBarColors(containerColor = FirefoxTheme.colors.layer1),
- windowInsets = WindowInsets(
- top = 0.dp,
- bottom = 0.dp,
- ),
- title = {
- if (!searchActive) {
- Text(
- color = FirefoxTheme.colors.textPrimary,
- style = FirefoxTheme.typography.headline6,
- text = stringResource(R.string.preferences_passwords_saved_logins_2),
- )
- } else {
- SearchBar(text, store)
- }
- },
- navigationIcon = {
+ Box {
IconButton(
onClick = {
- if (!searchActive) {
- store.dispatch(LoginsListBackClicked)
- } else {
- searchActive = false
- }
+ showMenu = true
},
- contentDescription = null,
+ contentDescription = stringResource(
+ R.string.saved_logins_menu_dropdown_chevron_icon_content_description_2,
+ ),
) {
Icon(
- painter = painterResource(iconsR.drawable.mozac_ic_back_24),
- contentDescription = stringResource(R.string.logins_navigate_back_button_content_description),
- tint = iconColor,
+ painter = painterResource(iconsR.drawable.mozac_ic_sort_24),
+ contentDescription = null,
)
}
- },
- actions = {
- if (!searchActive) {
- Box {
- Icon(
- modifier = Modifier
- .clickable {
- showMenu = true
- },
- painter = if (showMenu) {
- painterResource(R.drawable.ic_chevron_up)
- } else {
- painterResource(R.drawable.ic_chevron_down)
- },
- contentDescription = stringResource(
- R.string.saved_logins_menu_dropdown_chevron_icon_content_description_2,
- ),
- tint = iconColor,
- )
- LoginListSortMenu(
- showMenu = showMenu,
- onDismissRequest = {
- showMenu = false
- },
- store = store,
- )
- }
- IconButton(onClick = { searchActive = true }, contentDescription = null) {
- Icon(
- painter = painterResource(R.drawable.ic_search),
- contentDescription = stringResource(R.string.preferences_passwords_saved_logins_search_2),
- tint = iconColor,
- )
- }
- }
- },
- )
- }
+ LoginListSortMenu(
+ showMenu = showMenu,
+ onDismissRequest = {
+ showMenu = false
+ },
+ store = store,
+ )
+ }
+
+ IconButton(onClick = { searchActive = true }, contentDescription = null) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_search_24),
+ contentDescription = stringResource(R.string.preferences_passwords_saved_logins_search_2),
+ )
+ }
+ },
+ )
}
private val IconButtonHeight = 48.dp
@@ -346,48 +330,40 @@ private fun SearchBar(
focusRequester.requestFocus()
}
- Box {
- TextField(
- value = text,
- placeholder = stringResource(R.string.preferences_passwords_saved_logins_search_2),
- onValueChange = {
- store.dispatch(SearchLogins(searchText = it, loginItems = store.state.loginItems))
- },
- errorText = "",
- modifier = Modifier
- .background(color = FirefoxTheme.colors.layer1)
- .fillMaxWidth()
- .focusRequester(focusRequester),
- minHeight = IconButtonHeight,
- trailingIcons = {
- if (text.isNotBlank()) {
- IconButton(
- onClick = {
- store.dispatch(
- SearchLogins(
- searchText = "",
- loginItems = store.state.loginItems,
- ),
- )
- },
- contentDescription = null,
- ) {
- Icon(
- painter = painterResource(iconsR.drawable.mozac_ic_cross_24),
- contentDescription = null,
- tint = FirefoxTheme.colors.iconPrimary,
+ TextField(
+ value = text,
+ placeholder = stringResource(R.string.preferences_passwords_saved_logins_search_2),
+ onValueChange = {
+ store.dispatch(SearchLogins(searchText = it, loginItems = store.state.loginItems))
+ },
+ errorText = "",
+ modifier = Modifier
+ .fillMaxWidth()
+ .focusRequester(focusRequester),
+ minHeight = IconButtonHeight,
+ trailingIcons = {
+ if (text.isNotBlank()) {
+ IconButton(
+ onClick = {
+ store.dispatch(
+ SearchLogins(
+ searchText = "",
+ loginItems = store.state.loginItems,
+ ),
)
- }
+ },
+ contentDescription = null,
+ ) {
+ Icon(
+ painter = painterResource(iconsR.drawable.mozac_ic_cross_24),
+ contentDescription = null,
+ )
}
- },
- colors = TextFieldColors.default(
- placeholderColor = FirefoxTheme.colors.textPrimary,
- cursorColor = FirefoxTheme.colors.layerAccent,
- ),
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions,
- )
- }
+ }
+ },
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ )
}
@Composable
@@ -420,54 +396,50 @@ private fun LoginListSortMenu(
}
private const val LOGINS_LIST_SIZE = 15
+private val loginItems = List(LOGINS_LIST_SIZE) {
+ LoginItem(
+ guid = "$it",
+ url = "https://www.justanothersite$it.com",
+ username = "username $it",
+ password = "password $it",
+ )
+}
+
+private fun createStore() = LoginsStore(
+ initialState = LoginsState.default.copy(
+ loginItems = loginItems,
+ searchText = "",
+ ),
+)
@Composable
@FlexibleWindowLightDarkPreview
private fun LoginsListScreenPreview() {
- val loginItems = List(LOGINS_LIST_SIZE) {
- LoginItem(
- guid = "$it",
- url = "https://www.justanothersite$it.com",
- username = "username $it",
- password = "password $it",
- )
- }
-
- val store = { _: NavHostController ->
- LoginsStore(
- initialState = LoginsState.default.copy(
- loginItems = loginItems,
- searchText = "",
- ),
- )
+ FirefoxTheme {
+ LoginsList(store = createStore())
}
+}
- FirefoxTheme {
- Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
- SavedLoginsScreen(
- buildStore = store,
- )
- }
+@Composable
+@Preview
+private fun LoginsListScreenPrivatePreview() {
+ FirefoxTheme(theme = Theme.Private) {
+ LoginsList(store = createStore())
}
}
@Composable
@FlexibleWindowLightDarkPreview
private fun EmptyLoginsListScreenPreview() {
- val store = { _: NavHostController ->
- LoginsStore(
- initialState = LoginsState.default.copy(
- loginItems = listOf(),
- searchText = "",
- ),
- )
+ FirefoxTheme {
+ LoginsList(store = LoginsStore())
}
+}
- FirefoxTheme {
- Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
- SavedLoginsScreen(
- buildStore = store,
- )
- }
+@Composable
+@Preview
+private fun EmptyLoginsListScreenPrivatePreview() {
+ FirefoxTheme(theme = Theme.Private) {
+ LoginsList(store = LoginsStore())
}
}