implement search

This commit is contained in:
2023-07-14 13:00:12 +03:00
parent 2178a0c9a4
commit bcd2591ba7
2 changed files with 77 additions and 16 deletions
@@ -189,15 +189,17 @@ fun HomePage(
navigateToDetailsScreen: (item: PackageItem) -> Unit, navigateToDetailsScreen: (item: PackageItem) -> Unit,
settingsPrefs: SettingsPreferences, settingsPrefs: SettingsPreferences,
settingsViewModel: SettingsViewModel, settingsViewModel: SettingsViewModel,
packageListViewModel: PackageListViewModel = viewModel(
factory = PackageListViewModel.Factory
),
) { ) {
var isSearchOpen by rememberSaveable { mutableStateOf(false)}
var searchQuery by rememberSaveable { mutableStateOf("")}
val searchBar = remember { FocusRequester() } val searchBar = remember { FocusRequester() }
var layoutDialogOpen by remember { mutableStateOf(false) } var layoutDialogOpen by remember { mutableStateOf(false) }
val context = LocalContext.current val context = LocalContext.current
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val navbarScrimColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp) val navbarScrimColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
val uiState = packageListViewModel.uiState.collectAsStateWithLifecycle().value
val tsvs = settingsViewModel.availableTsvs() val tsvs = settingsViewModel.availableTsvs()
var selectedBarItem by rememberSaveable { mutableStateOf(ConsoleType.PSVITA) } var selectedBarItem by rememberSaveable { mutableStateOf(ConsoleType.PSVITA) }
@@ -215,22 +217,26 @@ fun HomePage(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
if (!isSearchOpen) { if (!uiState.isSearchOpen) {
Text(text = stringResource(id = R.string.app_name)) Text(text = stringResource(id = R.string.app_name))
} }
}, },
actions = { actions = {
if (isSearchOpen) { if (uiState.isSearchOpen) {
NPSIconButton( NPSIconButton(
tooltip = "Close Search", tooltip = "Close Search",
onClick = { isSearchOpen = false } onClick = {
packageListViewModel.closeSearch()
}
){ ){
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search") Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search")
} }
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
TextField( TextField(
value = searchQuery, value = uiState.searchQuery,
onValueChange = {str -> searchQuery = str}, onValueChange = {
packageListViewModel.search(it)
},
Modifier Modifier
.weight(1.0f) .weight(1.0f)
.focusRequester(searchBar), .focusRequester(searchBar),
@@ -248,10 +254,12 @@ fun HomePage(
), ),
) )
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
if (searchQuery.isNotEmpty()) { if (uiState.searchQuery.isNotEmpty()) {
NPSIconButton( NPSIconButton(
tooltip = "Clear Search", tooltip = "Clear Search",
onClick = { searchQuery = "" } onClick = {
packageListViewModel.clearSearch()
}
){ ){
Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search") Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search")
} }
@@ -261,10 +269,10 @@ fun HomePage(
searchBar.requestFocus() searchBar.requestFocus()
} }
} }
if (!isSearchOpen) { if (!uiState.isSearchOpen) {
NPSIconButton( NPSIconButton(
tooltip = stringResource(id = R.string.search_tooltip), tooltip = stringResource(id = R.string.search_tooltip),
onClick = { isSearchOpen = true } onClick = { packageListViewModel.openSearch() }
){ ){
Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource( Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource(
id = R.string.search_tooltip id = R.string.search_tooltip
@@ -306,7 +314,8 @@ fun HomePage(
PackageList( PackageList(
navigateToDetailsScreen = navigateToDetailsScreen, navigateToDetailsScreen = navigateToDetailsScreen,
type = selectedBarItem, type = selectedBarItem,
settingsPrefs = settingsPrefs settingsPrefs = settingsPrefs,
viewModel = packageListViewModel,
) )
} }
} }
@@ -321,6 +330,18 @@ fun HomePage(
} }
} }
@Composable
fun NoResults() {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(text = "No results found", color = MaterialTheme.colorScheme.outline)
}
}
@Composable @Composable
fun NavBar(selectedTab: ConsoleType, onTabSelected: (ConsoleType) -> Unit) { fun NavBar(selectedTab: ConsoleType, onTabSelected: (ConsoleType) -> Unit) {
NavigationBar { NavigationBar {
@@ -411,9 +432,7 @@ fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: ()
fun PackageList( fun PackageList(
type: ConsoleType = ConsoleType.PSVITA, type: ConsoleType = ConsoleType.PSVITA,
navigateToDetailsScreen: (item: PackageItem) -> Unit, navigateToDetailsScreen: (item: PackageItem) -> Unit,
viewModel: PackageListViewModel = viewModel( viewModel: PackageListViewModel,
factory = PackageListViewModel.Factory
),
settingsPrefs: SettingsPreferences, settingsPrefs: SettingsPreferences,
) { ) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
@@ -447,7 +466,7 @@ fun PackageList(
CircularProgressIndicator() CircularProgressIndicator()
} }
} else { } else {
if (packages.isEmpty()) { if (packages.isEmpty() && uiState.searchQuery.isEmpty()) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -461,6 +480,8 @@ fun PackageList(
color = MaterialTheme.colorScheme.outline color = MaterialTheme.colorScheme.outline
) )
} }
} else if (packages.isEmpty() && uiState.searchQuery.isNotEmpty()) {
NoResults()
} else { } else {
if (settingsPrefs.layout == ItemLayout.LIST.ordinal) { if (settingsPrefs.layout == ItemLayout.LIST.ordinal) {
LazyColumn { LazyColumn {
@@ -19,6 +19,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.internal.toLongOrDefault import okhttp3.internal.toLongOrDefault
import java.io.InputStream import java.io.InputStream
@@ -27,10 +28,13 @@ data class PackageListUiState(
val packages: ArrayList<PackageItem> = ArrayList(), val packages: ArrayList<PackageItem> = ArrayList(),
val error: String = "", val error: String = "",
val isLoading: Boolean = true, val isLoading: Boolean = true,
val isSearchOpen: Boolean = false,
val searchQuery: String = "",
) )
class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesRepository): ViewModel() { class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesRepository): ViewModel() {
private val _uiState = MutableStateFlow(PackageListUiState()) private val _uiState = MutableStateFlow(PackageListUiState())
private val immutablePackages = ArrayList<PackageItem>()
val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow() val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow()
private val tsvReader = csvReader { private val tsvReader = csvReader {
@@ -52,6 +56,7 @@ class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesReposit
list.sortBy { it.name } list.sortBy { it.name }
_uiState.emit(PackageListUiState(isLoading = false, packages = list)) _uiState.emit(PackageListUiState(isLoading = false, packages = list))
immutablePackages.addAll(list)
settingsRepo.tsvFiles().collect { tsv -> settingsRepo.tsvFiles().collect { tsv ->
_uiState.emit(PackageListUiState()) _uiState.emit(PackageListUiState())
@@ -70,10 +75,45 @@ class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesReposit
list.sortBy { it.name } list.sortBy { it.name }
_uiState.emit(PackageListUiState(isLoading = false, packages = list)) _uiState.emit(PackageListUiState(isLoading = false, packages = list))
immutablePackages.addAll(list)
} }
} }
} }
fun search(query: String) {
_uiState.update {
it.copy(
packages = immutablePackages.filter { item ->
item.name.contains(query, true)
} as ArrayList<PackageItem>,
searchQuery = query
)
}
}
fun openSearch() {
_uiState.update {
it.copy(isSearchOpen = true)
}
}
fun closeSearch() {
_uiState.update {
it.copy(
packages = immutablePackages,
searchQuery = "",
isSearchOpen = false
)
}
}
fun clearSearch() {
_uiState.update {
it.copy(packages = immutablePackages, searchQuery = "")
}
}
private fun parsePackageFromTSV( private fun parsePackageFromTSV(
it: Triple<String, ConsoleType, PackageItemType>, app: NPSApp it: Triple<String, ConsoleType, PackageItemType>, app: NPSApp
): ArrayList<PackageItem> { ): ArrayList<PackageItem> {