Tweak global search source filtering
Pinned only setting is removed in favor of the UI in the global search screen itself, which defaults to pinned only. This needs more UX improvements, but I'm not really sure what it should be like right now.
This commit is contained in:
parent
54733e6ceb
commit
12e7ee9d0c
8 changed files with 70 additions and 77 deletions
|
@ -30,7 +30,5 @@ class SourcePreferences(
|
|||
|
||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
||||
|
||||
fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
||||
|
||||
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||
}
|
||||
|
|
|
@ -30,23 +30,25 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.GlobalSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
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.VerticalDivider
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchScreen(
|
||||
state: GlobalSearchState,
|
||||
state: GlobalSearchScreenModel.State,
|
||||
items: Map<CatalogueSource, SearchItemResult>,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeFilter: (GlobalSearchFilter) -> Unit,
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
|
@ -71,13 +73,29 @@ fun GlobalSearchScreen(
|
|||
.padding(horizontal = MaterialTheme.padding.small),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
// TODO: make this UX better; it only applies when triggering a new search
|
||||
FilterChip(
|
||||
selected = state.searchFilter == GlobalSearchFilter.All,
|
||||
onClick = { onChangeFilter(GlobalSearchFilter.All) },
|
||||
selected = state.sourceFilter == SourceFilter.PinnedOnly,
|
||||
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.PushPin,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.pinned_sources))
|
||||
},
|
||||
)
|
||||
FilterChip(
|
||||
selected = state.sourceFilter == SourceFilter.All,
|
||||
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.DoneAll,
|
||||
contentDescription = "",
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
|
@ -87,29 +105,15 @@ fun GlobalSearchScreen(
|
|||
},
|
||||
)
|
||||
|
||||
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))
|
||||
},
|
||||
)
|
||||
VerticalDivider()
|
||||
|
||||
FilterChip(
|
||||
selected = state.searchFilter == GlobalSearchFilter.AvailableOnly,
|
||||
onClick = { onChangeFilter(GlobalSearchFilter.AvailableOnly) },
|
||||
selected = state.onlyShowHasResults,
|
||||
onClick = { onToggleResults() },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FilterList,
|
||||
contentDescription = "",
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
|
|
|
@ -29,10 +29,6 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||
Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.label_sources),
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = sourcePreferences.searchPinnedSourcesOnly(),
|
||||
title = stringResource(R.string.pref_search_pinned_sources_only),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = sourcePreferences.hideInLibraryItems(),
|
||||
title = stringResource(R.string.pref_hide_in_library_items),
|
||||
|
|
|
@ -10,6 +10,7 @@ import eu.kanade.presentation.browse.MigrateSearchScreen
|
|||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
|
||||
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
|
||||
class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -63,7 +63,8 @@ class GlobalSearchScreen(
|
|||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
getManga = { screenModel.getManga(it) },
|
||||
onChangeFilter = screenModel::setFilter,
|
||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
||||
onToggleResults = screenModel::toggleFilterResults,
|
||||
onClickSource = {
|
||||
if (!screenModel.incognitoMode.get()) {
|
||||
screenModel.lastUsedSourceId.set(it.id)
|
||||
|
|
|
@ -20,17 +20,17 @@ class GlobalSearchScreenModel(
|
|||
preferences: BasePreferences = Injekt.get(),
|
||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
) : SearchScreenModel<GlobalSearchState>(GlobalSearchState(searchQuery = initialQuery)) {
|
||||
) : SearchScreenModel<GlobalSearchScreenModel.State>(State(searchQuery = initialQuery)) {
|
||||
|
||||
val incognitoMode = preferences.incognitoMode()
|
||||
val lastUsedSourceId = sourcePreferences.lastUsedSource()
|
||||
|
||||
val searchPagerFlow = state.map { Pair(it.searchFilter, it.items) }
|
||||
val searchPagerFlow = state.map { Pair(it.onlyShowHasResults, it.items) }
|
||||
.distinctUntilChanged()
|
||||
.map { (filter, items) ->
|
||||
items
|
||||
.filter { (source, result) -> isSourceVisible(filter, source, result) }
|
||||
}.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items)
|
||||
.map { (onlyShowHasResults, items) ->
|
||||
items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
}
|
||||
.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items)
|
||||
|
||||
init {
|
||||
extensionFilter = initialExtensionFilter
|
||||
|
@ -45,19 +45,12 @@ class GlobalSearchScreenModel(
|
|||
val pinnedSources = sourcePreferences.pinnedSources().get()
|
||||
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { mutableState.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
.filter { it.lang in enabledLanguages }
|
||||
.filterNot { "${it.id}" in disabledSources }
|
||||
.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?) {
|
||||
mutableState.update {
|
||||
it.copy(searchQuery = query)
|
||||
|
@ -70,27 +63,32 @@ class GlobalSearchScreenModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun setFilter(filter: GlobalSearchFilter) {
|
||||
mutableState.update { it.copy(searchFilter = filter) }
|
||||
}
|
||||
|
||||
override fun getItems(): Map<CatalogueSource, SearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
}
|
||||
|
||||
enum class GlobalSearchFilter {
|
||||
All, PinnedOnly, AvailableOnly
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class GlobalSearchState(
|
||||
val searchQuery: String? = null,
|
||||
val searchFilter: GlobalSearchFilter = GlobalSearchFilter.All,
|
||||
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
||||
) {
|
||||
|
||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||
|
||||
val total: Int = items.size
|
||||
|
||||
fun setSourceFilter(filter: SourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
|
||||
return !onlyShowHasResults || (this is SearchItemResult.Success && !this.isEmpty)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,16 +68,7 @@ abstract class SearchScreenModel<T>(
|
|||
val enabledSources = getEnabledSources()
|
||||
|
||||
if (filter.isEmpty()) {
|
||||
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get()
|
||||
val pinnedSources = sourcePreferences.pinnedSources().get()
|
||||
|
||||
return enabledSources.filter {
|
||||
if (shouldSearchPinnedOnly) {
|
||||
"${it.id}" in pinnedSources
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
return enabledSources
|
||||
}
|
||||
|
||||
return extensionManager.installedExtensionsFlow.value
|
||||
|
@ -137,6 +128,11 @@ abstract class SearchScreenModel<T>(
|
|||
}
|
||||
}
|
||||
|
||||
enum class SourceFilter {
|
||||
All,
|
||||
PinnedOnly,
|
||||
}
|
||||
|
||||
sealed class SearchItemResult {
|
||||
object Loading : SearchItemResult()
|
||||
|
||||
|
|
|
@ -480,7 +480,6 @@
|
|||
<string name="action_track">Track</string>
|
||||
|
||||
<!-- Browse section -->
|
||||
<string name="pref_search_pinned_sources_only">Only search pinned sources in global search</string>
|
||||
<string name="pref_hide_in_library_items">Hide entries already in library</string>
|
||||
|
||||
<!-- Backup section -->
|
||||
|
|
Reference in a new issue