diff --git a/app/src/main/kotlin/org/fdroid/search/SearchManager.kt b/app/src/main/kotlin/org/fdroid/search/SearchManager.kt index 5e42da63a..123f90873 100644 --- a/app/src/main/kotlin/org/fdroid/search/SearchManager.kt +++ b/app/src/main/kotlin/org/fdroid/search/SearchManager.kt @@ -52,7 +52,9 @@ constructor( private val collator = Collator.getInstance(Locale.getDefault()) private val _searchResults = MutableStateFlow(null) private val _savedSearches = MutableStateFlow?>(null) - private val categories = + private var searchJob: SearchJob? = null + + val categories = db.getRepositoryDao().getLiveCategories().asFlow().map { categories -> categories .map { category -> @@ -60,8 +62,6 @@ constructor( } .sortedWith { c1, c2 -> collator.compare(c1.name, c2.name) } } - private var searchJob: SearchJob? = null - val searchResults = _searchResults.asStateFlow() val savedSearches = _savedSearches.asStateFlow() diff --git a/app/src/main/kotlin/org/fdroid/ui/Main.kt b/app/src/main/kotlin/org/fdroid/ui/Main.kt index 76bab88fa..e541a57f8 100644 --- a/app/src/main/kotlin/org/fdroid/ui/Main.kt +++ b/app/src/main/kotlin/org/fdroid/ui/Main.kt @@ -73,6 +73,7 @@ fun Main(onListeningForIntent: () -> Unit = {}) { GlobalSearch( searchResults = viewModel.searchResults.collectAsStateWithLifecycle().value, savedSearches = viewModel.savedSearchesFlow.collectAsStateWithLifecycle().value, + categories = viewModel.categories.collectAsStateWithLifecycle(null).value, onSearch = viewModel::search, onClearSavedSearches = viewModel::onClearSearchHistory, onNav = { navKey -> navigator.navigate(navKey) }, diff --git a/app/src/main/kotlin/org/fdroid/ui/search/GlobalSearch.kt b/app/src/main/kotlin/org/fdroid/ui/search/GlobalSearch.kt index 197c5c266..9b4d5aa73 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/GlobalSearch.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/GlobalSearch.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.tooling.preview.Preview import org.fdroid.search.SavedSearch import org.fdroid.ui.FDroidContent +import org.fdroid.ui.categories.CategoryItem import org.fdroid.ui.navigation.NavigationKey import org.fdroid.ui.utils.appListItems import org.fdroid.ui.utils.categoryItems @@ -27,6 +28,7 @@ fun GlobalSearch( textFieldState: TextFieldState = rememberTextFieldState(), searchResults: SearchResults?, savedSearches: List? = null, + categories: List? = null, onSearch: suspend (String) -> Unit, onClearSavedSearches: () -> Unit, onNav: (NavigationKey) -> Unit, @@ -60,6 +62,7 @@ fun GlobalSearch( searchResults = searchResults, textFieldState = textFieldState, savedSearches = savedSearches, + categories = categories, onClearSavedSearches = onClearSavedSearches, onNav = onNav, ) @@ -72,7 +75,15 @@ fun GlobalSearch( private fun AppsSearchLoadingPreview() { FDroidContent { val textFieldState = rememberTextFieldState("foo bar") - Box(Modifier.fillMaxSize()) { GlobalSearch(textFieldState, null, null, {}, {}, {}, {}, {}) } + Box(Modifier.fillMaxSize()) { GlobalSearch( + textFieldState, + null, + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) } } } @@ -89,6 +100,7 @@ private fun AppsSearchEmptyStatePreview() { textFieldState = textFieldState, searchResults = null, savedSearches = savedSearches, + categories = categoryItems, onSearch = {}, onClearSavedSearches = {}, onNav = {}, diff --git a/app/src/main/kotlin/org/fdroid/ui/search/PastSearches.kt b/app/src/main/kotlin/org/fdroid/ui/search/PastSearches.kt index 09271a0fd..f6d536017 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/PastSearches.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/PastSearches.kt @@ -1,17 +1,11 @@ package org.fdroid.ui.search import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons @@ -23,6 +17,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource @@ -32,51 +27,41 @@ import org.fdroid.R import org.fdroid.search.SavedSearch import org.fdroid.ui.FDroidContent -@Composable @OptIn(ExperimentalMaterial3ExpressiveApi::class) -fun PastSearches( +fun LazyListScope.pastSearches( savedSearches: List, onSearch: (String) -> Unit, onClearSavedSearches: () -> Unit, - modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues(), ) { - LazyColumn(modifier = modifier.fillMaxSize(), contentPadding = paddingValues) { - item { - Row { - Text( - text = stringResource(R.string.search_history), - style = MaterialTheme.typography.labelLarge, - modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp).weight(1f), + item { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = stringResource(R.string.search_history), + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.padding(horizontal = 16.dp).weight(1f), + ) + TextButton(onClick = onClearSavedSearches, modifier = Modifier.padding(horizontal = 8.dp)) { + Text(stringResource(R.string.clear)) + } + } + } + items(savedSearches) { item -> + ListItem( + leadingContent = { + Icon( + Icons.Default.History, + contentDescription = null, + modifier = + Modifier.clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(8.dp), ) - TextButton( - onClick = onClearSavedSearches, - modifier = Modifier.padding(horizontal = 8.dp), - ) { - Text(stringResource(R.string.clear)) - } - } + }, + onClick = { onSearch(item.query) }, + modifier = Modifier.fillMaxWidth().animateItem(), + ) { + Text(item.query) } - items(savedSearches) { item -> - ListItem( - leadingContent = { - Icon( - Icons.Default.History, - contentDescription = null, - modifier = - Modifier.clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceContainer) - .padding(8.dp), - ) - }, - onClick = { onSearch(item.query) }, - modifier = Modifier.fillMaxWidth().animateItem(), - ) { - Text(item.query) - } - } - item { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)) } - item { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) } } } @@ -86,10 +71,8 @@ private fun Preview() { FDroidContent { val savedSearches = listOf(SavedSearch(1, "foo"), SavedSearch(2, "foo bar"), SavedSearch(3, "foobar")) - PastSearches( - savedSearches = savedSearches, - onClearSavedSearches = {}, - onSearch = {}, - ) + LazyColumn { + pastSearches(savedSearches = savedSearches, onClearSavedSearches = {}, onSearch = {}) + } } } diff --git a/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt b/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt index 5470ca4ef..25de71bbb 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt @@ -47,6 +47,7 @@ fun SearchResults( searchResults: SearchResults?, textFieldState: TextFieldState, savedSearches: List?, + categories: List?, onClearSavedSearches: () -> Unit, onNav: (NavigationKey) -> Unit, modifier: Modifier = Modifier, @@ -58,14 +59,19 @@ fun SearchResults( if (textFieldState.text.isSearchable()) { BigLoadingIndicator(modifier.padding(paddingValues).imePadding()) } else { - if (!savedSearches.isNullOrEmpty()) { - PastSearches( - savedSearches = savedSearches, - onSearch = { textFieldState.edit { replace(0, length, it) } }, - onClearSavedSearches = onClearSavedSearches, - modifier = modifier, - paddingValues = paddingValues, - ) + LazyColumn(modifier = modifier.fillMaxSize(), contentPadding = paddingValues) { + if (!savedSearches.isNullOrEmpty()) { + pastSearches( + savedSearches = savedSearches, + onSearch = { textFieldState.edit { replace(0, length, it) } }, + onClearSavedSearches = onClearSavedSearches, + ) + } + if (!categories.isNullOrEmpty()) { + item { CategoriesFlowRow(categories, onNav, modifier = Modifier.padding(top = 16.dp)) } + } + item { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)) } + item { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) } } } } else if (searchResults.apps.isEmpty() && textFieldState.text.isSearchable()) { @@ -121,8 +127,12 @@ fun SearchResults( } @Composable -private fun CategoriesFlowRow(categories: List, onNav: (NavigationKey) -> Unit) { - Column(modifier = Modifier.padding(horizontal = 8.dp)) { +private fun CategoriesFlowRow( + categories: List, + onNav: (NavigationKey) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.padding(horizontal = 8.dp)) { Text( text = stringResource(R.string.main_menu__categories), style = MaterialTheme.typography.labelLarge, diff --git a/app/src/main/kotlin/org/fdroid/ui/search/SearchViewModel.kt b/app/src/main/kotlin/org/fdroid/ui/search/SearchViewModel.kt index 90c81ebc1..2b4238668 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/SearchViewModel.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/SearchViewModel.kt @@ -16,6 +16,7 @@ constructor(app: Application, private val searchManager: SearchManager) : Androi val searchResults = searchManager.searchResults val savedSearchesFlow = searchManager.savedSearches + val categories = searchManager.categories suspend fun search(term: String) = if (term.isSearchable()) searchManager.search(term) else Unit diff --git a/app/src/screenshotTest/kotlin/org/fdroid/ui/search/GlobalSearchTest.kt b/app/src/screenshotTest/kotlin/org/fdroid/ui/search/GlobalSearchTest.kt index 2d9fb24ca..3e8acaf62 100644 --- a/app/src/screenshotTest/kotlin/org/fdroid/ui/search/GlobalSearchTest.kt +++ b/app/src/screenshotTest/kotlin/org/fdroid/ui/search/GlobalSearchTest.kt @@ -13,112 +13,107 @@ import org.fdroid.ui.utils.categoryItems @Composable @PreviewTest @Preview(showBackground = true, showSystemUi = true) -fun GlobalSearchEmptyTest() = - ScreenshotTest { - val textFieldState = rememberTextFieldState() - GlobalSearch( - textFieldState = textFieldState, - searchResults = null, - savedSearches = emptyList(), - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchEmptyTest() = ScreenshotTest { + val textFieldState = rememberTextFieldState() + GlobalSearch( + textFieldState = textFieldState, + searchResults = null, + savedSearches = emptyList(), + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} @Composable @PreviewTest @Preview(showBackground = true, showSystemUi = true) -fun GlobalSearchEmptyStateTest() = - ScreenshotTest { - val savedSearches = - listOf(SavedSearch(1, "Browser"), SavedSearch(2, "media player"), SavedSearch(3, "email app")) - val textFieldState = rememberTextFieldState() - GlobalSearch( - textFieldState = textFieldState, - searchResults = null, - savedSearches = savedSearches, - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchEmptyStateTest() = ScreenshotTest { + val savedSearches = + listOf(SavedSearch(1, "Browser"), SavedSearch(2, "media player"), SavedSearch(3, "email app")) + val textFieldState = rememberTextFieldState() + GlobalSearch( + textFieldState = textFieldState, + searchResults = null, + savedSearches = savedSearches, + categories = categoryItems, + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} @Composable @PreviewTest @Preview(showBackground = true, showSystemUi = true) -fun GlobalSearchLoadingTest() = - ScreenshotTest { - val textFieldState = rememberTextFieldState("foo bar") - GlobalSearch( - textFieldState = textFieldState, - searchResults = null, - savedSearches = emptyList(), - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchLoadingTest() = ScreenshotTest { + val textFieldState = rememberTextFieldState("foo bar") + GlobalSearch( + textFieldState = textFieldState, + searchResults = null, + savedSearches = emptyList(), + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} @Composable @PreviewTest @Preview(showBackground = true, showSystemUi = true) -fun GlobalSearchNoResultsTest() = - ScreenshotTest { - val textFieldState = rememberTextFieldState("foo") - GlobalSearch( - textFieldState = textFieldState, - searchResults = SearchResults(emptyList(), emptyList()), - savedSearches = emptyList(), - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchNoResultsTest() = ScreenshotTest { + val textFieldState = rememberTextFieldState("foo") + GlobalSearch( + textFieldState = textFieldState, + searchResults = SearchResults(emptyList(), emptyList()), + savedSearches = emptyList(), + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} @Composable @PreviewTest @Preview(showBackground = true, showSystemUi = true) -fun GlobalSearchResultsTest() = - ScreenshotTest { - val textFieldState = rememberTextFieldState("foo bar") - GlobalSearch( - textFieldState = textFieldState, - searchResults = SearchResults(appListItems, categoryItems.subList(0, 4)), - savedSearches = emptyList(), - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchResultsTest() = ScreenshotTest { + val textFieldState = rememberTextFieldState("foo bar") + GlobalSearch( + textFieldState = textFieldState, + searchResults = SearchResults(appListItems, categoryItems.subList(0, 4)), + savedSearches = emptyList(), + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} @Composable @PreviewTest @Preview(showBackground = true, showSystemUi = true) -fun GlobalSearchOnlyCategoriesTest() = - ScreenshotTest { - val textFieldState = rememberTextFieldState("foo bar") - GlobalSearch( - textFieldState = textFieldState, - searchResults = SearchResults(emptyList(), categoryItems.subList(5, 9)), - savedSearches = emptyList(), - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchOnlyCategoriesTest() = ScreenshotTest { + val textFieldState = rememberTextFieldState("foo bar") + GlobalSearch( + textFieldState = textFieldState, + searchResults = SearchResults(emptyList(), categoryItems.subList(5, 9)), + savedSearches = emptyList(), + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} @Composable @PreviewTest @@ -127,17 +122,16 @@ fun GlobalSearchOnlyCategoriesTest() = showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL, ) -fun GlobalSearchResultsNightTest() = - ScreenshotTest { - val textFieldState = rememberTextFieldState("foo bar") - GlobalSearch( - textFieldState = textFieldState, - searchResults = SearchResults(appListItems, categoryItems.subList(0, 4)), - savedSearches = emptyList(), - onSearch = {}, - onClearSavedSearches = {}, - onNav = {}, - onBack = {}, - onSearchCleared = {}, - ) - } +fun GlobalSearchResultsNightTest() = ScreenshotTest { + val textFieldState = rememberTextFieldState("foo bar") + GlobalSearch( + textFieldState = textFieldState, + searchResults = SearchResults(appListItems, categoryItems.subList(0, 4)), + savedSearches = emptyList(), + onSearch = {}, + onClearSavedSearches = {}, + onNav = {}, + onBack = {}, + onSearchCleared = {}, + ) +} diff --git a/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/search/GlobalSearchTestKt/GlobalSearchEmptyStateTest_2ed8e27d_0.png b/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/search/GlobalSearchTestKt/GlobalSearchEmptyStateTest_2ed8e27d_0.png index 0737be26d..0d65142d8 100644 --- a/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/search/GlobalSearchTestKt/GlobalSearchEmptyStateTest_2ed8e27d_0.png +++ b/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/search/GlobalSearchTestKt/GlobalSearchEmptyStateTest_2ed8e27d_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6eb1c82199e7d0ace698bdc95b523bef51a761eaa22698dfefae3ff5f2419ca3 -size 56180 +oid sha256:e926d11d01b2d8786e9af5077f60d47b51d23605e016559c94b998c85edd44f0 +size 119038