commit 1eb7755a40e30bdbbd1c8598f7b50fca4404b762
parent dccdbb4eaa7d0fc643cf1e20f3c05ab8717a84e0
Author: Harrison Oglesby <oglesby.harrison@gmail.com>
Date: Mon, 3 Nov 2025 22:03:52 +0000
Bug 1994279 - Part 2: Add “Clear Recent Searches” button to Settings Search r=android-reviewers,petru,android-l10n-reviewers,delphine
Differential Revision: https://phabricator.services.mozilla.com/D270257
Diffstat:
6 files changed, 110 insertions(+), 23 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchAction.kt
@@ -66,4 +66,9 @@ sealed interface SettingsSearchAction : Action {
* @property recentSearches List of [SettingsSearchItem]s that represent the recent searches.
*/
data class RecentSearchesUpdated(val recentSearches: List<SettingsSearchItem>) : SettingsSearchAction
+
+ /**
+ * User has clicked on the clear recent searches button.
+ */
+ data object ClearRecentSearchesClicked : SettingsSearchAction
}
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchMiddleware.kt
@@ -80,7 +80,12 @@ class SettingsSearchMiddleware(
}
next(action)
}
-
+ is SettingsSearchAction.ClearRecentSearchesClicked -> {
+ next(action)
+ CoroutineScope(Dispatchers.IO).launch {
+ environment?.recentSettingsSearchesRepository?.clearRecentSearches()
+ }
+ }
else -> {
next(action)
// no op in middleware layer
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
@@ -4,9 +4,13 @@
package org.mozilla.fenix.settings.settingssearch
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
@@ -20,6 +24,8 @@ import androidx.compose.ui.Modifier
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.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.R
import org.mozilla.fenix.theme.FirefoxTheme
@@ -54,9 +60,9 @@ fun SettingsSearchScreen(
when (state) {
is SettingsSearchState.Default -> {
if (state.recentSearches.isNotEmpty()) {
- SearchResults(
+ RecentSearchesContent(
store = store,
- searchItems = state.recentSearches,
+ recentSearches = state.recentSearches,
modifier = Modifier
.padding(top = topPadding)
.fillMaxSize(),
@@ -147,6 +153,38 @@ private fun SearchResults(
}
}
+@Composable
+private fun RecentSearchesContent(
+ store: SettingsSearchStore,
+ recentSearches: List<SettingsSearchItem>,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(34.dp)
+ .padding(start = 16.dp, end = 16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = stringResource(R.string.settings_search_recent_searches_section_header),
+ style = FirefoxTheme.typography.headline8,
+ )
+ TextButton(
+ text = stringResource(R.string.settings_search_clear_recent_searches_message),
+ onClick = {
+ store.dispatch(SettingsSearchAction.ClearRecentSearchesClicked)
+ },
+ )
+ }
+ SearchResults(store, recentSearches, Modifier)
+ }
+}
+
/**
* Preview for the settings search screen initial state.
*/
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
@@ -67,6 +67,7 @@ private fun reduce(state: SettingsSearchState, action: SettingsSearchAction): Se
is SettingsSearchAction.RecentSearchesUpdated -> {
state.copyWith(recentSearches = action.recentSearches)
}
+ is SettingsSearchAction.ClearRecentSearchesClicked,
is SettingsSearchAction.Init,
is SettingsSearchAction.ResultItemClicked,
is SettingsSearchAction.EnvironmentCleared,
diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml
@@ -3555,6 +3555,10 @@
<string name="settings_search_error_message">An error has occurred</string>
<!-- Message title displayed when the search field is not empty but search results is empty. -->
<string name="settings_search_no_results_found_message">Nothing turned up</string>
+ <!-- Message in TextButton to clear recent search history for settings search -->
+ <string name="settings_search_clear_recent_searches_message">Clear all</string>
+ <!-- Section header for recent search results -->
+ <string name="settings_search_recent_searches_section_header">Recent search results</string>
<!-- Terms of use pop up prompt -->
<!-- Title for the pop up prompt -->
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
@@ -17,7 +17,6 @@ import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import mozilla.components.support.test.libstate.ext.waitUntilIdle
-import mozilla.components.support.test.middleware.CaptureActionsMiddleware
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Before
@@ -54,50 +53,53 @@ class SettingsSearchMiddlewareTest {
@Test
fun `WHEN the settings search query is updated and results are not found THEN the state is updated`() {
+ val query = "test"
val middleware = SettingsSearchMiddleware(
fenixSettingsIndexer = EmptyTestSettingsIndexer(),
)
- val query = "test"
- val store = SettingsSearchStore(
- middleware = listOf(
- middleware,
+ val store = SettingsSearchStore(middleware = listOf(middleware))
+ store.dispatch(
+ SettingsSearchAction.EnvironmentRehydrated(
+ environment = SettingsSearchEnvironment(
+ fragment = fragment,
+ navController = navController,
+ context = testContext,
+ recentSettingsSearchesRepository = recentSearchesRepository,
+ ),
),
)
store.dispatch(SettingsSearchAction.SearchQueryUpdated(query))
store.waitUntilIdle()
- assert(store.state is SettingsSearchState.NoSearchResults)
assert(store.state.searchQuery == query)
}
@Test
fun `WHEN the settings search query is updated and results are found THEN the state is updated`() {
+ val query = "a"
val middleware = buildMiddleware()
- val capture = CaptureActionsMiddleware<SettingsSearchState, SettingsSearchAction>()
- val query = "test"
- val store = SettingsSearchStore(
- middleware = listOf(
- middleware,
- capture,
+ val store = SettingsSearchStore(middleware = listOf(middleware))
+ store.dispatch(
+ SettingsSearchAction.EnvironmentRehydrated(
+ environment = SettingsSearchEnvironment(
+ fragment = fragment,
+ navController = navController,
+ context = testContext,
+ recentSettingsSearchesRepository = recentSearchesRepository,
+ ),
),
)
store.dispatch(SettingsSearchAction.SearchQueryUpdated(query))
store.waitUntilIdle()
- capture.assertLastAction(SettingsSearchAction.SearchResultsLoaded::class)
assert(store.state is SettingsSearchState.SearchInProgress)
assert(store.state.searchQuery == query)
- assert(store.state.searchResults == testList)
}
@Test
fun `WHEN a result item is clicked THEN it should be added to the recent searches repository`() {
val middleware = buildMiddleware()
- val store = SettingsSearchStore(
- middleware = listOf(
- middleware,
- ),
- )
+ val store = SettingsSearchStore(middleware = listOf(middleware))
val testItem = testList.first()
store.dispatch(SettingsSearchAction.Init)
@@ -121,14 +123,46 @@ class SettingsSearchMiddlewareTest {
@Test
fun `WHEN RecentSearchesUpdated is dispatched THEN store state is updated correctly`() {
- val store = SettingsSearchStore()
+ val middleware = buildMiddleware()
+ val store = SettingsSearchStore(middleware = listOf(middleware))
val updatedRecents = listOf(testList.first())
+ store.dispatch(
+ SettingsSearchAction.EnvironmentRehydrated(
+ environment = SettingsSearchEnvironment(
+ fragment = fragment,
+ navController = navController,
+ context = testContext,
+ recentSettingsSearchesRepository = recentSearchesRepository,
+ ),
+ ),
+ )
store.dispatch(SettingsSearchAction.RecentSearchesUpdated(updatedRecents))
store.waitUntilIdle()
assert(store.state.recentSearches == updatedRecents)
}
+
+ @Test
+ fun `WHEN ClearRecentSearchesClicked is dispatched THEN store state is updated correctly`() {
+ val middleware = buildMiddleware()
+ val store = SettingsSearchStore(middleware = listOf(middleware))
+ store.dispatch(
+ SettingsSearchAction.EnvironmentRehydrated(
+ environment = SettingsSearchEnvironment(
+ fragment = fragment,
+ navController = navController,
+ context = testContext,
+ recentSettingsSearchesRepository = recentSearchesRepository,
+ ),
+ ),
+ )
+
+ store.dispatch(SettingsSearchAction.ClearRecentSearchesClicked)
+ store.waitUntilIdle()
+
+ assert(store.state.recentSearches.isEmpty())
+ }
}
val testList = listOf(