mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-10-01 16:46:26 -04:00
Show all categories in empty state of search screen
This commit is contained in:
8 files changed
+167
-166
No files matched your search
@@ -52,7 +52,9 @@ constructor(
|
||||
private val collator = Collator.getInstance(Locale.getDefault())
|
||||
private val _searchResults = MutableStateFlow<SearchResults?>(null)
|
||||
private val _savedSearches = MutableStateFlow<List<SavedSearch>?>(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()
|
||||
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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<SavedSearch>? = null,
|
||||
categories: List<CategoryItem>? = 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 = {},
|
||||
|
||||
@@ -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<SavedSearch>,
|
||||
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 = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ fun SearchResults(
|
||||
searchResults: SearchResults?,
|
||||
textFieldState: TextFieldState,
|
||||
savedSearches: List<SavedSearch>?,
|
||||
categories: List<CategoryItem>?,
|
||||
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<CategoryItem>, onNav: (NavigationKey) -> Unit) {
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||
private fun CategoriesFlowRow(
|
||||
categories: List<CategoryItem>,
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6eb1c82199e7d0ace698bdc95b523bef51a761eaa22698dfefae3ff5f2419ca3
|
||||
size 56180
|
||||
oid sha256:e926d11d01b2d8786e9af5077f60d47b51d23605e016559c94b998c85edd44f0
|
||||
size 119038
|
||||
Reference in new issue
Block a user