commit 46590a0b226f2badfb4386890e968856388bcb49 parent 703472ecce89fe0e32127c0407414fdab741d379 Author: Harrison Oglesby <oglesby.harrison@gmail.com> Date: Mon, 17 Nov 2025 22:38:28 +0000 Bug 1998028 - Part 1: Category headers and new breadcrumbs in SettingsSearchResultItem r=android-reviewers,petru,android-l10n-reviewers,flod Differential Revision: https://phabricator.services.mozilla.com/D271152 Diffstat:
10 files changed, 219 insertions(+), 61 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/DefaultFenixSettingsIndexer.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/DefaultFenixSettingsIndexer.kt @@ -50,8 +50,7 @@ class DefaultFenixSettingsIndexer(private val context: Context) : SettingsIndexe return withContext(Dispatchers.Default) { settings.distinctBy { it.preferenceKey }.filter { item -> - item.title.contains(trimmedQuery, ignoreCase = true) || - item.summary.contains(trimmedQuery, ignoreCase = true) + item.title.contains(trimmedQuery, ignoreCase = true) } } } @@ -201,6 +200,8 @@ class DefaultFenixSettingsIndexer(private val context: Context) : SettingsIndexe } } + val categoryHeader = context.getString(preferenceFileInformation.categoryHeaderResourceId) + if (key == null || title == null) return null return SettingsSearchItem( @@ -208,6 +209,7 @@ class DefaultFenixSettingsIndexer(private val context: Context) : SettingsIndexe title = title, summary = summary, breadcrumbs = breadcrumbs.toList(), + categoryHeader = categoryHeader, preferenceFileInformation = preferenceFileInformation, ) } @@ -245,11 +247,14 @@ class DefaultFenixSettingsIndexer(private val context: Context) : SettingsIndexe } } + val categoryHeader = context.getString(preferenceFileInformation.categoryHeaderResourceId) + return SettingsSearchItem( preferenceKey = key ?: "", title = title ?: "", summary = summary, breadcrumbs = breadcrumbs.toList(), + categoryHeader = categoryHeader, preferenceFileInformation = preferenceFileInformation, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/FenixRecentSettingsSearchesRepository.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/FenixRecentSettingsSearchesRepository.kt @@ -39,6 +39,7 @@ class FenixRecentSettingsSearchesRepository( title = protoItem.title, summary = protoItem.summary, breadcrumbs = protoItem.breadcrumbsList, + categoryHeader = "", preferenceFileInformation = prefInfo, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/PreferenceFileInformation.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/PreferenceFileInformation.kt @@ -10,12 +10,16 @@ import org.mozilla.fenix.R * Data class for a settings search item navigation information based on the xml file it comes from. * * @property xmlResourceId The resource ID of the xml file that the item comes from. - * @property topBreadcrumbResourceId The top breadcrumb of the item as a string resource. + * @property topBreadcrumbResourceId The top breadcrumb of the item as a resource id. + * @property secondaryBreadcrumbResourceId The secondary breadcrumb of the item as a resource id. + * @property categoryHeaderResourceId The category header of the item as a resource id. * @property fragmentId The fragment ID of the fragment where the item is displayed. */ sealed class PreferenceFileInformation( val xmlResourceId: Int, val topBreadcrumbResourceId: Int, + val secondaryBreadcrumbResourceId: Int = 0, + val categoryHeaderResourceId: Int, val fragmentId: Int, ) { @@ -25,6 +29,7 @@ sealed class PreferenceFileInformation( object GeneralPreferences : PreferenceFileInformation( xmlResourceId = R.xml.preferences, topBreadcrumbResourceId = R.string.settings_title, + categoryHeaderResourceId = R.string.settings_title, fragmentId = R.id.settingsFragment, ) @@ -34,6 +39,7 @@ sealed class PreferenceFileInformation( object AccessibilityPreferences : PreferenceFileInformation( xmlResourceId = R.xml.accessibility_preferences, topBreadcrumbResourceId = R.string.preferences_accessibility, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.accessibilityFragment, ) @@ -43,6 +49,7 @@ sealed class PreferenceFileInformation( object AutofillPreferences : PreferenceFileInformation( xmlResourceId = R.xml.autofill_preferences, topBreadcrumbResourceId = R.string.preferences_autofill, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.autofill_graph, ) @@ -52,6 +59,7 @@ sealed class PreferenceFileInformation( object CustomizationPreferences : PreferenceFileInformation( xmlResourceId = R.xml.customization_preferences, topBreadcrumbResourceId = R.string.preferences_customize, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.customizationFragment, ) @@ -61,6 +69,7 @@ sealed class PreferenceFileInformation( object DefaultSearchEnginePreferences : PreferenceFileInformation( xmlResourceId = R.xml.default_search_engine_preferences, topBreadcrumbResourceId = R.string.preferences_default_search_engine, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.search_engine_graph, ) @@ -70,6 +79,7 @@ sealed class PreferenceFileInformation( object DownloadsSettingsPreferences : PreferenceFileInformation( xmlResourceId = R.xml.downloads_settings_preferences, topBreadcrumbResourceId = R.string.preferences_downloads, + categoryHeaderResourceId = R.string.preferences_category_advanced, fragmentId = R.id.openDownloadsSettingsFragment, ) @@ -79,6 +89,7 @@ sealed class PreferenceFileInformation( object HomePreferences : PreferenceFileInformation( xmlResourceId = R.xml.home_preferences, topBreadcrumbResourceId = R.string.preferences_home_2, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.homeSettingsFragment, ) @@ -88,6 +99,7 @@ sealed class PreferenceFileInformation( object OpenLinksInAppsPreferences : PreferenceFileInformation( xmlResourceId = R.xml.open_links_in_apps_preferences, topBreadcrumbResourceId = R.string.preferences_open_links_in_apps, + categoryHeaderResourceId = R.string.preferences_category_advanced, fragmentId = R.id.openLinksInAppsFragment, ) @@ -97,6 +109,7 @@ sealed class PreferenceFileInformation( object PrivateBrowsingPreferences : PreferenceFileInformation( xmlResourceId = R.xml.private_browsing_preferences, topBreadcrumbResourceId = R.string.preferences_private_browsing_options, + categoryHeaderResourceId = R.string.preferences_category_privacy_security, fragmentId = R.id.privateBrowsingFragment, ) @@ -106,6 +119,7 @@ sealed class PreferenceFileInformation( object SaveLoginsPreferences : PreferenceFileInformation( xmlResourceId = R.xml.save_logins_preferences, topBreadcrumbResourceId = R.string.preferences_passwords_save_logins_2, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.savedLogins, ) @@ -115,6 +129,7 @@ sealed class PreferenceFileInformation( object SearchSettingsPreferences : PreferenceFileInformation( xmlResourceId = R.xml.search_settings_preferences, topBreadcrumbResourceId = R.string.preferences_search, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.search_engine_graph, ) @@ -124,6 +139,7 @@ sealed class PreferenceFileInformation( object SiteSettingsPreferences : PreferenceFileInformation( xmlResourceId = R.xml.site_permissions_preferences, topBreadcrumbResourceId = R.string.preferences_site_settings, + categoryHeaderResourceId = R.string.preferences_category_privacy_security, fragmentId = R.id.sitePermissionsFragment, ) @@ -133,6 +149,7 @@ sealed class PreferenceFileInformation( object TabsPreferences : PreferenceFileInformation( xmlResourceId = R.xml.tabs_preferences, topBreadcrumbResourceId = R.string.preferences_tabs, + categoryHeaderResourceId = R.string.preferences_category_general, fragmentId = R.id.tabsSettingsFragment, ) @@ -142,6 +159,7 @@ sealed class PreferenceFileInformation( object TrackingProtectionPreferences : PreferenceFileInformation( xmlResourceId = R.xml.tracking_protection_preferences, topBreadcrumbResourceId = R.string.preference_enhanced_tracking_protection, + categoryHeaderResourceId = R.string.preferences_category_privacy_security, fragmentId = R.id.trackingProtectionFragment, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchItem.kt @@ -14,6 +14,7 @@ package org.mozilla.fenix.settings.settingssearch * [0] is the section on the main Settings page * [1] is the subPage title, if any * [2] is the subPage section, if any + * @property categoryHeader Category header [String] of the settings item. * @property preferenceFileInformation Preference file information [PreferenceFileInformation] of the settings item. */ data class SettingsSearchItem( @@ -21,6 +22,7 @@ data class SettingsSearchItem( val summary: String, val preferenceKey: String, val breadcrumbs: List<String>, + val categoryHeader: String, val preferenceFileInformation: PreferenceFileInformation, ) { @@ -31,6 +33,7 @@ data class SettingsSearchItem( * @param summary New summary [String]. * @param preferenceKey New preference key [String]. * @param breadcrumbs New breadcrumbs [List] of [String]s. + * @param categoryHeader New category header [String]. * @param preferenceFileInformation New preference file information [PreferenceFileInformation]. * @return New [SettingsSearchItem] with the given parameters replaced. */ @@ -39,6 +42,7 @@ data class SettingsSearchItem( summary: String? = null, preferenceKey: String? = null, breadcrumbs: List<String>? = null, + categoryHeader: String? = null, preferenceFileInformation: PreferenceFileInformation? = null, ): SettingsSearchItem { return SettingsSearchItem( @@ -46,6 +50,7 @@ data class SettingsSearchItem( summary = summary ?: this.summary, preferenceKey = preferenceKey ?: this.preferenceKey, breadcrumbs = breadcrumbs ?: this.breadcrumbs, + categoryHeader = categoryHeader ?: this.categoryHeader, preferenceFileInformation = preferenceFileInformation ?: this.preferenceFileInformation, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchResultItem.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchResultItem.kt @@ -14,6 +14,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -49,40 +50,37 @@ fun SettingsSearchResultItem( highlight = defaultSpanStyle, ) } - val displaySummary = remember(item.title, query) { - highlightQueryMatchingText( - text = item.summary, - query = query, - highlight = defaultSpanStyle, - ) + val displaySubtitle = if (shouldShowSummary(item)) { + AnnotatedString(item.summary) + } else { + val breadcrumbString = buildString { + append(stringResource(item.preferenceFileInformation.topBreadcrumbResourceId)) + if (item.preferenceFileInformation.secondaryBreadcrumbResourceId != 0) { + append(" > ") + append(stringResource(item.preferenceFileInformation.secondaryBreadcrumbResourceId)) + } + } + AnnotatedString(breadcrumbString) } Column( modifier = Modifier .fillMaxWidth() + .height(64.dp) .clickable(onClick = onClick) - .padding(16.dp), + .padding(start = 16.dp, end = 16.dp, top = 8.dp), ) { - if (item.breadcrumbs.isNotEmpty()) { - Text( - text = item.breadcrumbs.joinToString(" > "), - style = FirefoxTheme.typography.caption, - color = FirefoxTheme.colors.textSecondary, - ) - Spacer(modifier = Modifier.height(8.dp)) - } - Text( text = displayTitle, style = FirefoxTheme.typography.subtitle1, + maxLines = 1, color = FirefoxTheme.colors.textPrimary, ) - if (displaySummary.isNotBlank()) { + if (displaySubtitle.isNotBlank()) { Text( - text = displaySummary, + text = displaySubtitle, style = FirefoxTheme.typography.caption, color = FirefoxTheme.colors.textSecondary, - modifier = Modifier.padding(top = 4.dp), ) } else { Spacer(modifier = Modifier.height(8.dp)) @@ -91,6 +89,20 @@ fun SettingsSearchResultItem( } /** + * Whether the summary should be shown. + * + * @param item [SettingsSearchItem] to check. + */ +internal fun shouldShowSummary( + item: SettingsSearchItem, +): Boolean { + return ( + item.preferenceFileInformation == PreferenceFileInformation.GeneralPreferences && + item.summary.isNotBlank() + ) +} + +/** * Highlights the query matching text. * * @param text Text to highlight. @@ -145,6 +157,7 @@ private class SettingsSearchResultItemParameterProvider : PreviewParameterProvid summary = "Set your preferred search engine for browsing.", preferenceKey = "search_engine_main", breadcrumbs = listOf("Search", "Default Search Engine"), + categoryHeader = "General", preferenceFileInformation = PreferenceFileInformation.SearchSettingsPreferences, ), SettingsSearchItem( @@ -152,6 +165,7 @@ private class SettingsSearchResultItemParameterProvider : PreviewParameterProvid summary = "", // Empty or blank summary preferenceKey = "advanced_stuff", breadcrumbs = listOf("Developer", "Experiments"), + categoryHeader = "Advanced", preferenceFileInformation = PreferenceFileInformation.GeneralPreferences, ), ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchScreen.kt @@ -15,23 +15,27 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import mozilla.components.compose.base.button.TextButton +import mozilla.components.compose.base.theme.AcornTheme import mozilla.components.lib.state.ext.observeAsComposableState import org.mozilla.fenix.GleanMetrics.SettingsSearch import org.mozilla.fenix.R +import org.mozilla.fenix.settings.settingssearch.ui.SettingsSearchSectionHeader import org.mozilla.fenix.theme.FirefoxTheme /** @@ -66,7 +70,6 @@ fun SettingsSearchScreen( if (state.recentSearches.isNotEmpty()) { RecentSearchesContent( store = store, - recentSearches = state.recentSearches, modifier = Modifier .padding(top = topPadding) .fillMaxSize(), @@ -89,8 +92,6 @@ fun SettingsSearchScreen( is SettingsSearchState.SearchInProgress -> { SearchResults( store = store, - searchItems = state.searchResults, - isRecentSearch = false, modifier = Modifier .padding(top = topPadding) .fillMaxSize(), @@ -121,8 +122,6 @@ private fun SettingsSearchMessageContent( @Composable private fun SearchResults( store: SettingsSearchStore, - searchItems: List<SettingsSearchItem>, - isRecentSearch: Boolean, modifier: Modifier = Modifier, ) { val state by store.observeAsComposableState { it } @@ -130,28 +129,35 @@ private fun SearchResults( LazyColumn( modifier = modifier, ) { - items(searchItems.size) { index -> - val searchItem = searchItems[index] - if (index > 0) { - HorizontalDivider() + state.groupedResults.forEach { (header, items) -> + item { + SettingsSearchSectionHeader(title = header) + } + + items(items.size) { index -> + val settingsSearchItem = items[index] + SettingsSearchResultItem( + item = settingsSearchItem, + query = state.searchQuery, + onClick = { + SettingsSearch.searchResultClicked.record( + SettingsSearch.SearchResultClickedExtra( + itemPreferenceKey = settingsSearchItem.preferenceKey, + isRecentSearch = false, + ), + ) + store.dispatch( + SettingsSearchAction.ResultItemClicked( + settingsSearchItem, + ), + ) + }, + ) + } + + item { + HorizontalDivider(modifier = Modifier.padding(top = 8.dp, bottom = 12.dp)) } - SettingsSearchResultItem( - item = searchItem, - query = state.searchQuery, - onClick = { - SettingsSearch.searchResultClicked.record( - SettingsSearch.SearchResultClickedExtra( - itemPreferenceKey = searchItem.preferenceKey, - isRecentSearch = isRecentSearch, - ), - ) - store.dispatch( - SettingsSearchAction.ResultItemClicked( - searchItem, - ), - ) - }, - ) } } } @@ -159,37 +165,65 @@ private fun SearchResults( @Composable private fun RecentSearchesContent( store: SettingsSearchStore, - recentSearches: List<SettingsSearchItem>, modifier: Modifier = Modifier, ) { + val state by store.observeAsComposableState { it } + Column( modifier = modifier, ) { Row( modifier = Modifier .fillMaxWidth() - .height(34.dp) - .padding(start = 16.dp, end = 16.dp), + .height(50.dp) + .padding(start = 16.dp, top = 12.dp, bottom = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Text( text = stringResource(R.string.settings_search_recent_searches_section_header), style = FirefoxTheme.typography.headline8, + color = colorResource(RECENT_SEARCHES_HEADER_TEXT_COLOR), ) TextButton( - text = stringResource(R.string.settings_search_clear_recent_searches_message), onClick = { store.dispatch(SettingsSearchAction.ClearRecentSearchesClicked) }, - ) + colors = ButtonDefaults.textButtonColors(), + modifier = Modifier, + enabled = true, + ) { + Text( + text = stringResource(R.string.settings_search_clear_recent_searches_message), + color = colorResource(RECENT_SEARCHES_CLEAR_RECENTS_TEXT_COLOR), + style = AcornTheme.typography.button, + maxLines = 1, + ) + } + } + LazyColumn { + items(state.recentSearches.size) { index -> + val searchItem = state.recentSearches[index] + + SettingsSearchResultItem( + item = searchItem, + query = state.searchQuery, + onClick = { + SettingsSearch.searchResultClicked.record( + SettingsSearch.SearchResultClickedExtra( + itemPreferenceKey = searchItem.preferenceKey, + isRecentSearch = true, + ), + ) + store.dispatch( + SettingsSearchAction.ResultItemClicked( + searchItem, + ), + ) + }, + ) + } } - SearchResults( - store = store, - searchItems = recentSearches, - isRecentSearch = true, - modifier = Modifier, - ) } } @@ -253,6 +287,7 @@ private fun SettingsSearchScreenWithRecentsPreview() { "Choose your default", "search_engine", listOf("General"), + categoryHeader = "General", PreferenceFileInformation.SearchSettingsPreferences, ), SettingsSearchItem( @@ -260,6 +295,7 @@ private fun SettingsSearchScreenWithRecentsPreview() { "Clear history, cookies, and more", "delete_browsing_data", listOf("Privacy"), + categoryHeader = "Privacy", PreferenceFileInformation.PrivateBrowsingPreferences, ), ), @@ -284,10 +320,27 @@ private fun SettingsSearchScreenWithResultsPreview() { searchQuery = "privacy", searchResults = listOf( SettingsSearchItem( + "Search engine", + "Choose your default", + "search_engine", + listOf("General"), + categoryHeader = "General", + PreferenceFileInformation.SearchSettingsPreferences, + ), + SettingsSearchItem( + "Homepage", + "Choose your homepage", + "home_page", + listOf("General"), + categoryHeader = "General", + PreferenceFileInformation.SearchSettingsPreferences, + ), + SettingsSearchItem( "Tracking Protection", "Strict, Standard, or Custom", "tracking_protection", listOf("Privacy"), + categoryHeader = "Privacy", PreferenceFileInformation.GeneralPreferences, ), SettingsSearchItem( @@ -295,6 +348,7 @@ private fun SettingsSearchScreenWithResultsPreview() { "Clear history, cookies, and more", "delete_browsing_data", listOf("Privacy"), + categoryHeader = "Privacy", PreferenceFileInformation.GeneralPreferences, ), SettingsSearchItem( @@ -302,6 +356,7 @@ private fun SettingsSearchScreenWithResultsPreview() { "Enable in all tabs", "https_only_mode", listOf("Privacy", "Advanced"), + categoryHeader = "Privacy", PreferenceFileInformation.GeneralPreferences, ), ), @@ -335,3 +390,6 @@ private fun SettingsSearchScreenNoResultsPreview() { ) } } + +private val RECENT_SEARCHES_HEADER_TEXT_COLOR = mozilla.components.ui.colors.R.color.photonDarkGrey05 +private val RECENT_SEARCHES_CLEAR_RECENTS_TEXT_COLOR = mozilla.components.ui.colors.R.color.photonViolet70 diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchStore.kt @@ -81,11 +81,13 @@ private fun reduce(state: SettingsSearchState, action: SettingsSearchAction): Se * @property searchQuery Current search query [String]. * @property searchResults List of [SettingsSearchItem]s that match the current search query, if any. * @property recentSearches List of recently searched [SettingsSearchItem]s. + * @property groupedResults Map of category names to lists of [SettingsSearchItem]s that match the current search query. */ sealed class SettingsSearchState( open val searchQuery: String = "", open val searchResults: List<SettingsSearchItem> = emptyList(), open val recentSearches: List<SettingsSearchItem> = emptyList(), + open val groupedResults: Map<String, List<SettingsSearchItem>> = emptyMap(), ) : State { /** @@ -96,6 +98,7 @@ sealed class SettingsSearchState( searchQuery: String = this.searchQuery, searchResults: List<SettingsSearchItem> = this.searchResults, recentSearches: List<SettingsSearchItem> = this.recentSearches, + groupedResults: Map<String, List<SettingsSearchItem>> = this.groupedResults, ): SettingsSearchState /** @@ -113,6 +116,7 @@ sealed class SettingsSearchState( searchQuery: String, searchResults: List<SettingsSearchItem>, recentSearches: List<SettingsSearchItem>, + groupedResults: Map<String, List<SettingsSearchItem>>, ): SettingsSearchState { // A Default state can't have a query or search results, so we ignore those parameters // and return a new Default state, only considering the recentSearches. @@ -127,25 +131,31 @@ sealed class SettingsSearchState( * @property searchQuery Current search query [String]. * @property searchResults List of [SettingsSearchItem]s that match the current search query. * @property recentSearches List of recently searched [SettingsSearchItem]s. + * @property groupedResults searchResults grouped by category header. */ data class SearchInProgress( override val searchQuery: String, override val searchResults: List<SettingsSearchItem>, override val recentSearches: List<SettingsSearchItem>, + override val groupedResults: Map<String, List<SettingsSearchItem>> = + searchResults.groupBy { it.categoryHeader }.toSortedMap(), ) : SettingsSearchState( searchQuery, searchResults, recentSearches, + groupedResults, ) { override fun copyWith( searchQuery: String, searchResults: List<SettingsSearchItem>, recentSearches: List<SettingsSearchItem>, + groupedResults: Map<String, List<SettingsSearchItem>>, ): SettingsSearchState { return this.copy( searchQuery = searchQuery, searchResults = searchResults, recentSearches = recentSearches, + groupedResults = groupedResults, ) } } @@ -168,6 +178,7 @@ sealed class SettingsSearchState( searchQuery: String, searchResults: List<SettingsSearchItem>, recentSearches: List<SettingsSearchItem>, + groupedResults: Map<String, List<SettingsSearchItem>>, ): SettingsSearchState { return this.copy( searchQuery = searchQuery, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/ui/SettingsSearchSectionHeader.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/ui/SettingsSearchSectionHeader.kt @@ -0,0 +1,43 @@ +/* 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.settingssearch.ui + +import androidx.compose.foundation.layout.Box +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.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * Header for the Settings Search result list. + * + * @param title The title to display. + */ +@Composable +fun SettingsSearchSectionHeader(title: String) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + ) { + Text( + text = title, + style = FirefoxTheme.typography.headline8, + color = FirefoxTheme.colors.textSecondary, + ) + } +} + +@PreviewLightDark +@Composable +private fun SettingsSearchSectionHeaderPreview() { + FirefoxTheme { + SettingsSearchSectionHeader("General") + } +} diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml @@ -3520,7 +3520,7 @@ <!-- Settings Search Strings --> <!-- Title of Settings Search. "Search" is a verb here--> - <string name="settings_search_title">Search Settings</string> + <string name="settings_search_title">Search settings</string> <!-- Content description (not visible, for screen readers etc.) for the button to clear the search field for settings search --> <string name="content_description_settings_search_clear_search">Clear Search</string> <!-- Content description (not visible, for screen readers etc.) for the button to navigate back to the Settings page from the Settings Search screen. --> diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchMiddlewareTest.kt @@ -132,6 +132,7 @@ val testList = listOf( summary = "Set your preferred search engine for browsing.", preferenceKey = "search_engine_main", breadcrumbs = listOf("Search", "Default Search Engine"), + categoryHeader = "General", preferenceFileInformation = PreferenceFileInformation.SearchSettingsPreferences, ), SettingsSearchItem( @@ -139,6 +140,7 @@ val testList = listOf( summary = "", // Empty or blank summary preferenceKey = "advanced_stuff", breadcrumbs = listOf("Developer", "Experiments"), + categoryHeader = "Advanced", preferenceFileInformation = PreferenceFileInformation.GeneralPreferences, ), SettingsSearchItem( @@ -146,6 +148,7 @@ val testList = listOf( summary = "", // Empty or blank summary preferenceKey = "do_not_collect_data", breadcrumbs = listOf("Privacy", "Usage Data"), + categoryHeader = "Privacy", preferenceFileInformation = PreferenceFileInformation.GeneralPreferences, ), )