Add filters to Global search (#9691)

* add pinned and available filter chips to global search

* split filter predicate into seperate function

* change the global search available filter to has Results

* reordering of imports
This commit is contained in:
zaghdaneh 2023-07-15 04:49:14 +02:00 committed by GitHub
parent 2f05f7b91f
commit cbcec8c4d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 10 deletions

View file

@ -1,8 +1,22 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DoneAll
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.PushPin
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -16,19 +30,23 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchFilter
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
@Composable @Composable
fun GlobalSearchScreen( fun GlobalSearchScreen(
state: GlobalSearchState, state: GlobalSearchState,
items: Map<CatalogueSource, SearchItemResult>,
navigateUp: () -> Unit, navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
onChangeFilter: (GlobalSearchFilter) -> Unit,
getManga: @Composable (Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
@ -36,19 +54,78 @@ fun GlobalSearchScreen(
) { ) {
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
GlobalSearchToolbar( Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
searchQuery = state.searchQuery, GlobalSearchToolbar(
progress = state.progress, searchQuery = state.searchQuery,
total = state.total, progress = state.progress,
navigateUp = navigateUp, total = state.total,
onChangeSearchQuery = onChangeSearchQuery, navigateUp = navigateUp,
onSearch = onSearch, onChangeSearchQuery = onChangeSearchQuery,
scrollBehavior = scrollBehavior, onSearch = onSearch,
) scrollBehavior = scrollBehavior,
)
Row(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.padding(horizontal = MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
FilterChip(
selected = state.searchFilter == GlobalSearchFilter.All,
onClick = { onChangeFilter(GlobalSearchFilter.All) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.DoneAll,
contentDescription = "",
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.all))
},
)
FilterChip(
selected = state.searchFilter == GlobalSearchFilter.PinnedOnly,
onClick = { onChangeFilter(GlobalSearchFilter.PinnedOnly) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.PushPin,
contentDescription = "",
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.pinned_sources))
},
)
FilterChip(
selected = state.searchFilter == GlobalSearchFilter.AvailableOnly,
onClick = { onChangeFilter(GlobalSearchFilter.AvailableOnly) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = "",
modifier = Modifier
.size(FilterChipDefaults.IconSize),
)
},
label = {
Text(text = stringResource(id = R.string.has_results))
},
)
}
Divider()
}
}, },
) { paddingValues -> ) { paddingValues ->
GlobalSearchContent( GlobalSearchContent(
items = state.items, items = items,
contentPadding = paddingValues, contentPadding = paddingValues,
getManga = getManga, getManga = getManga,
onClickSource = onClickSource, onClickSource = onClickSource,

View file

@ -35,6 +35,7 @@ class GlobalSearchScreen(
var showSingleLoadingScreen by remember { var showSingleLoadingScreen by remember {
mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1) mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1)
} }
val filteredSources by screenModel.searchPagerFlow.collectAsState()
if (showSingleLoadingScreen) { if (showSingleLoadingScreen) {
LoadingScreen() LoadingScreen()
@ -57,10 +58,12 @@ class GlobalSearchScreen(
} else { } else {
GlobalSearchScreen( GlobalSearchScreen(
state = state, state = state,
items = filteredSources,
navigateUp = navigator::pop, navigateUp = navigator::pop,
onChangeSearchQuery = screenModel::updateSearchQuery, onChangeSearchQuery = screenModel::updateSearchQuery,
onSearch = screenModel::search, onSearch = screenModel::search,
getManga = { screenModel.getManga(it) }, getManga = { screenModel.getManga(it) },
onChangeFilter = screenModel::setFilter,
onClickSource = { onClickSource = {
if (!screenModel.incognitoMode.get()) { if (!screenModel.incognitoMode.get()) {
screenModel.lastUsedSourceId.set(it.id) screenModel.lastUsedSourceId.set(it.id)

View file

@ -3,7 +3,12 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -20,6 +25,13 @@ class GlobalSearchScreenModel(
val incognitoMode = preferences.incognitoMode() val incognitoMode = preferences.incognitoMode()
val lastUsedSourceId = sourcePreferences.lastUsedSource() val lastUsedSourceId = sourcePreferences.lastUsedSource()
val searchPagerFlow = state.map { Pair(it.searchFilter, it.items) }
.distinctUntilChanged()
.map { (filter, items) ->
items
.filter { (source, result) -> isSourceVisible(filter, source, result) }
}.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items)
init { init {
extensionFilter = initialExtensionFilter extensionFilter = initialExtensionFilter
if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) { if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) {
@ -38,6 +50,14 @@ class GlobalSearchScreenModel(
.sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" })) .sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" }))
} }
private fun isSourceVisible(filter: GlobalSearchFilter, source: CatalogueSource, result: SearchItemResult): Boolean {
return when (filter) {
GlobalSearchFilter.AvailableOnly -> result is SearchItemResult.Success && !result.isEmpty
GlobalSearchFilter.PinnedOnly -> "${source.id}" in sourcePreferences.pinnedSources().get()
GlobalSearchFilter.All -> true
}
}
override fun updateSearchQuery(query: String?) { override fun updateSearchQuery(query: String?) {
mutableState.update { mutableState.update {
it.copy(searchQuery = query) it.copy(searchQuery = query)
@ -50,14 +70,23 @@ class GlobalSearchScreenModel(
} }
} }
fun setFilter(filter: GlobalSearchFilter) {
mutableState.update { it.copy(searchFilter = filter) }
}
override fun getItems(): Map<CatalogueSource, SearchItemResult> { override fun getItems(): Map<CatalogueSource, SearchItemResult> {
return mutableState.value.items return mutableState.value.items
} }
} }
enum class GlobalSearchFilter {
All, PinnedOnly, AvailableOnly
}
@Immutable @Immutable
data class GlobalSearchState( data class GlobalSearchState(
val searchQuery: String? = null, val searchQuery: String? = null,
val searchFilter: GlobalSearchFilter = GlobalSearchFilter.All,
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(), val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
) { ) {

View file

@ -627,6 +627,7 @@
<string name="latest">Latest</string> <string name="latest">Latest</string>
<string name="popular">Popular</string> <string name="popular">Popular</string>
<string name="browse">Browse</string> <string name="browse">Browse</string>
<string name="has_results">Has results</string>
<string name="local_source_help_guide">Local source guide</string> <string name="local_source_help_guide">Local source guide</string>
<string name="no_pinned_sources">You have no pinned sources</string> <string name="no_pinned_sources">You have no pinned sources</string>
<string name="chapter_not_found">Chapter not found</string> <string name="chapter_not_found">Chapter not found</string>