tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 67cb4781e9840e07ae9104754394b1a12ea1bbf0
parent 3b5c7dc57eb533cbc766727df31998be67ae9808
Author: Harrison Oglesby <oglesby.harrison@gmail.com>
Date:   Tue,  4 Nov 2025 23:44:42 +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:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchAction.kt | 5+++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchMiddleware.kt | 7++++++-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchScreen.kt | 42++++++++++++++++++++++++++++++++++++++++--
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchStore.kt | 1+
Mmobile/android/fenix/app/src/main/res/values/strings.xml | 4++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/settingssearch/SettingsSearchMiddlewareTest.kt | 57++++++++++++++++++++++++++++++++++++---------------------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/studies/StudiesViewTest.kt | 4++++
7 files changed, 96 insertions(+), 24 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> + <!-- Text of button allowing to clear history of recent searches done in the settings screen. --> + <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 @@ -18,7 +18,6 @@ import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest 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.After @@ -63,11 +62,7 @@ class SettingsSearchMiddlewareTest { dispatcher = coroutineRule.testDispatcher, ) val query = "longSample" - val store = SettingsSearchStore( - middleware = listOf( - middleware, - ), - ) + val store = SettingsSearchStore(middleware = listOf(middleware)) store.dispatch( SettingsSearchAction.EnvironmentRehydrated( environment = SettingsSearchEnvironment( @@ -81,7 +76,6 @@ class SettingsSearchMiddlewareTest { store.waitUntilIdle() store.dispatch(SettingsSearchAction.SearchQueryUpdated(query)) store.waitUntilIdle() - assert(store.state is SettingsSearchState.NoSearchResults) assert(store.state.searchQuery == query) assert(store.state.searchResults.isEmpty()) } @@ -89,14 +83,8 @@ class SettingsSearchMiddlewareTest { @Test fun `WHEN the settings search query is updated and results are found THEN the state is updated`() { val middleware = buildMiddleware() - val capture = CaptureActionsMiddleware<SettingsSearchState, SettingsSearchAction>() val query = "a" - val store = SettingsSearchStore( - middleware = listOf( - middleware, - capture, - ), - ) + val store = SettingsSearchStore(middleware = listOf(middleware)) store.dispatch(SettingsSearchAction.Init) store.dispatch( SettingsSearchAction.EnvironmentRehydrated( @@ -112,17 +100,12 @@ class SettingsSearchMiddlewareTest { store.waitUntilIdle() 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) @@ -146,8 +129,19 @@ 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() @@ -155,6 +149,27 @@ class SettingsSearchMiddlewareTest { 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()) + } + @After fun tearDown() = runTest { lifecycleOwner.destroy() diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/studies/StudiesViewTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/studies/StudiesViewTest.kt @@ -25,6 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mozilla.experiments.nimbus.internal.EnrolledExperiment import org.mozilla.fenix.databinding.SettingsStudiesBinding +import org.mozilla.fenix.helpers.MockkRetryTestRule import org.mozilla.fenix.utils.Settings import org.robolectric.RobolectricTestRunner @@ -46,6 +47,9 @@ class StudiesViewTest { private lateinit var view: StudiesView @get:Rule + val mockkRule = MockkRetryTestRule() + + @get:Rule val coroutinesTestRule = MainCoroutineRule() private val testCoroutineScope = coroutinesTestRule.scope