mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Dedupe SearchScreenModels
This commit is contained in:
		@@ -10,8 +10,8 @@ import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
 | 
			
		||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
 | 
			
		||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
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.SearchScreenModel
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
@@ -19,7 +19,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun GlobalSearchScreen(
 | 
			
		||||
    state: GlobalSearchScreenModel.State,
 | 
			
		||||
    state: SearchScreenModel.State,
 | 
			
		||||
    navigateUp: () -> Unit,
 | 
			
		||||
    onChangeSearchQuery: (String?) -> Unit,
 | 
			
		||||
    onSearch: (String) -> Unit,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,15 @@ import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.State
 | 
			
		||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreenModel
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.presentation.core.components.material.Scaffold
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun MigrateSearchScreen(
 | 
			
		||||
    state: MigrateSearchScreenModel.State,
 | 
			
		||||
    state: SearchScreenModel.State,
 | 
			
		||||
    fromSourceId: Long?,
 | 
			
		||||
    navigateUp: () -> Unit,
 | 
			
		||||
    onChangeSearchQuery: (String?) -> Unit,
 | 
			
		||||
    onSearch: (String) -> Unit,
 | 
			
		||||
@@ -40,7 +41,7 @@ fun MigrateSearchScreen(
 | 
			
		||||
        },
 | 
			
		||||
    ) { paddingValues ->
 | 
			
		||||
        GlobalSearchContent(
 | 
			
		||||
            fromSourceId = state.manga?.source,
 | 
			
		||||
            fromSourceId = fromSourceId,
 | 
			
		||||
            items = state.filteredItems,
 | 
			
		||||
            contentPadding = paddingValues,
 | 
			
		||||
            getManga = getManga,
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ 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
 | 
			
		||||
@@ -20,8 +19,12 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
 | 
			
		||||
        val screenModel = rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) }
 | 
			
		||||
        val state by screenModel.state.collectAsState()
 | 
			
		||||
 | 
			
		||||
        val dialogScreenModel = rememberScreenModel { MigrateSearchScreenDialogScreenModel(mangaId = mangaId) }
 | 
			
		||||
        val dialogState by dialogScreenModel.state.collectAsState()
 | 
			
		||||
 | 
			
		||||
        MigrateSearchScreen(
 | 
			
		||||
            state = state,
 | 
			
		||||
            fromSourceId = dialogState.manga?.source,
 | 
			
		||||
            navigateUp = navigator::pop,
 | 
			
		||||
            onChangeSearchQuery = screenModel::updateSearchQuery,
 | 
			
		||||
            onSearch = screenModel::search,
 | 
			
		||||
@@ -29,19 +32,19 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
 | 
			
		||||
            onChangeSearchFilter = screenModel::setSourceFilter,
 | 
			
		||||
            onToggleResults = screenModel::toggleFilterResults,
 | 
			
		||||
            onClickSource = {
 | 
			
		||||
                navigator.push(SourceSearchScreen(state.manga!!, it.id, state.searchQuery))
 | 
			
		||||
                navigator.push(SourceSearchScreen(dialogState.manga!!, it.id, state.searchQuery))
 | 
			
		||||
            },
 | 
			
		||||
            onClickItem = { screenModel.setDialog(MigrateSearchScreenModel.Dialog.Migrate(it)) },
 | 
			
		||||
            onClickItem = { dialogScreenModel.setDialog(MigrateSearchScreenDialogScreenModel.Dialog.Migrate(it)) },
 | 
			
		||||
            onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        when (val dialog = state.dialog) {
 | 
			
		||||
            is MigrateSearchScreenModel.Dialog.Migrate -> {
 | 
			
		||||
        when (val dialog = dialogState.dialog) {
 | 
			
		||||
            is MigrateSearchScreenDialogScreenModel.Dialog.Migrate -> {
 | 
			
		||||
                MigrateDialog(
 | 
			
		||||
                    oldManga = state.manga!!,
 | 
			
		||||
                    oldManga = dialogState.manga!!,
 | 
			
		||||
                    newManga = dialog.manga,
 | 
			
		||||
                    screenModel = rememberScreenModel { MigrateDialogScreenModel() },
 | 
			
		||||
                    onDismissRequest = { screenModel.setDialog(null) },
 | 
			
		||||
                    onDismissRequest = { dialogScreenModel.setDialog(null) },
 | 
			
		||||
                    onClickTitle = {
 | 
			
		||||
                        navigator.push(MangaScreen(dialog.manga.id, true))
 | 
			
		||||
                    },
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.migration.search
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Immutable
 | 
			
		||||
import cafe.adriel.voyager.core.model.StateScreenModel
 | 
			
		||||
import cafe.adriel.voyager.core.model.coroutineScope
 | 
			
		||||
import kotlinx.coroutines.flow.update
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import tachiyomi.domain.manga.interactor.GetManga
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
class MigrateSearchScreenDialogScreenModel(
 | 
			
		||||
    val mangaId: Long,
 | 
			
		||||
    getManga: GetManga = Injekt.get(),
 | 
			
		||||
) : StateScreenModel<MigrateSearchScreenDialogScreenModel.State>(State()) {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        coroutineScope.launch {
 | 
			
		||||
            val manga = getManga.await(mangaId)!!
 | 
			
		||||
 | 
			
		||||
            mutableState.update {
 | 
			
		||||
                it.copy(manga = manga)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setDialog(dialog: Dialog?) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(dialog = dialog)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Immutable
 | 
			
		||||
    data class State(
 | 
			
		||||
        val manga: Manga? = null,
 | 
			
		||||
        val dialog: Dialog? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    sealed class Dialog {
 | 
			
		||||
        data class Migrate(val manga: Manga) : Dialog()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +1,26 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.migration.search
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Immutable
 | 
			
		||||
import cafe.adriel.voyager.core.model.coroutineScope
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
 | 
			
		||||
import kotlinx.coroutines.flow.update
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import tachiyomi.domain.manga.interactor.GetManga
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
class MigrateSearchScreenModel(
 | 
			
		||||
    val mangaId: Long,
 | 
			
		||||
    getManga: GetManga = Injekt.get(),
 | 
			
		||||
) : SearchScreenModel<MigrateSearchScreenModel.State>(State()) {
 | 
			
		||||
) : SearchScreenModel() {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        coroutineScope.launch {
 | 
			
		||||
            val manga = getManga.await(mangaId)!!
 | 
			
		||||
 | 
			
		||||
            mutableState.update {
 | 
			
		||||
                it.copy(manga = manga, searchQuery = manga.title)
 | 
			
		||||
                it.copy(fromSourceId = manga.source, searchQuery = manga.title)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            search(manga.title)
 | 
			
		||||
@@ -35,61 +32,10 @@ class MigrateSearchScreenModel(
 | 
			
		||||
            .filter { mutableState.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
 | 
			
		||||
            .sortedWith(
 | 
			
		||||
                compareBy(
 | 
			
		||||
                    { it.id != state.value.manga!!.source },
 | 
			
		||||
                    { it.id != state.value.fromSourceId },
 | 
			
		||||
                    { "${it.id}" !in pinnedSources },
 | 
			
		||||
                    { "${it.name.lowercase()} (${it.lang})" },
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun updateSearchQuery(query: String?) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(searchQuery = query)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun updateItems(items: Map<CatalogueSource, SearchItemResult>) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(items = items)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItems(): Map<CatalogueSource, SearchItemResult> {
 | 
			
		||||
        return mutableState.value.items
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setSourceFilter(filter: SourceFilter) {
 | 
			
		||||
        mutableState.update { it.copy(sourceFilter = filter) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toggleFilterResults() {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(onlyShowHasResults = !it.onlyShowHasResults)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setDialog(dialog: Dialog?) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(dialog = dialog)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Immutable
 | 
			
		||||
    data class State(
 | 
			
		||||
        val manga: Manga? = null,
 | 
			
		||||
        val dialog: Dialog? = null,
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
        val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sealed class Dialog {
 | 
			
		||||
        data class Migrate(val manga: Manga) : Dialog()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,11 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Immutable
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import kotlinx.coroutines.flow.update
 | 
			
		||||
 | 
			
		||||
class GlobalSearchScreenModel(
 | 
			
		||||
    initialQuery: String = "",
 | 
			
		||||
    initialExtensionFilter: String? = null,
 | 
			
		||||
) : SearchScreenModel<GlobalSearchScreenModel.State>(State(searchQuery = initialQuery)) {
 | 
			
		||||
) : SearchScreenModel(State(searchQuery = initialQuery)) {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        extensionFilter = initialExtensionFilter
 | 
			
		||||
@@ -20,42 +18,4 @@ class GlobalSearchScreenModel(
 | 
			
		||||
        return super.getEnabledSources()
 | 
			
		||||
            .filter { mutableState.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun updateSearchQuery(query: String?) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(searchQuery = query)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun updateItems(items: Map<CatalogueSource, SearchItemResult>) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(items = items)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItems(): Map<CatalogueSource, SearchItemResult> {
 | 
			
		||||
        return mutableState.value.items
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setSourceFilter(filter: SourceFilter) {
 | 
			
		||||
        mutableState.update { it.copy(sourceFilter = filter) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toggleFilterResults() {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(onlyShowHasResults = !it.onlyShowHasResults)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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
 | 
			
		||||
        val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.Immutable
 | 
			
		||||
import androidx.compose.runtime.produceState
 | 
			
		||||
import cafe.adriel.voyager.core.model.StateScreenModel
 | 
			
		||||
import eu.kanade.domain.manga.interactor.UpdateManga
 | 
			
		||||
import eu.kanade.domain.manga.model.toDomainManga
 | 
			
		||||
import eu.kanade.domain.source.service.SourcePreferences
 | 
			
		||||
import eu.kanade.presentation.util.ioCoroutineScope
 | 
			
		||||
@@ -16,6 +15,7 @@ import kotlinx.coroutines.async
 | 
			
		||||
import kotlinx.coroutines.awaitAll
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import kotlinx.coroutines.flow.filterNotNull
 | 
			
		||||
import kotlinx.coroutines.flow.update
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import tachiyomi.core.util.lang.awaitSingle
 | 
			
		||||
@@ -27,15 +27,14 @@ import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.util.concurrent.Executors
 | 
			
		||||
 | 
			
		||||
abstract class SearchScreenModel<T>(
 | 
			
		||||
    initialState: T,
 | 
			
		||||
abstract class SearchScreenModel(
 | 
			
		||||
    initialState: State = State(),
 | 
			
		||||
    private val sourcePreferences: SourcePreferences = Injekt.get(),
 | 
			
		||||
    private val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
    private val extensionManager: ExtensionManager = Injekt.get(),
 | 
			
		||||
    private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
 | 
			
		||||
    private val getManga: GetManga = Injekt.get(),
 | 
			
		||||
    private val updateManga: UpdateManga = Injekt.get(),
 | 
			
		||||
) : StateScreenModel<T>(initialState) {
 | 
			
		||||
) : StateScreenModel<SearchScreenModel.State>(initialState) {
 | 
			
		||||
 | 
			
		||||
    private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
 | 
			
		||||
    private var searchJob: Job? = null
 | 
			
		||||
@@ -55,7 +54,7 @@ abstract class SearchScreenModel<T>(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    fun getManga(initialManga: Manga): State<Manga> {
 | 
			
		||||
    fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> {
 | 
			
		||||
        return produceState(initialValue = initialManga) {
 | 
			
		||||
            getManga.subscribe(initialManga.url, initialManga.source)
 | 
			
		||||
                .filterNotNull()
 | 
			
		||||
@@ -95,19 +94,25 @@ abstract class SearchScreenModel<T>(
 | 
			
		||||
            .filter { it in enabledSources }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract fun updateSearchQuery(query: String?)
 | 
			
		||||
 | 
			
		||||
    abstract fun updateItems(items: Map<CatalogueSource, SearchItemResult>)
 | 
			
		||||
 | 
			
		||||
    abstract fun getItems(): Map<CatalogueSource, SearchItemResult>
 | 
			
		||||
 | 
			
		||||
    private fun getAndUpdateItems(function: (Map<CatalogueSource, SearchItemResult>) -> Map<CatalogueSource, SearchItemResult>) {
 | 
			
		||||
        updateItems(function(getItems()))
 | 
			
		||||
    fun updateSearchQuery(query: String?) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(searchQuery = query)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract fun setSourceFilter(filter: SourceFilter)
 | 
			
		||||
    fun getItems(): Map<CatalogueSource, SearchItemResult> {
 | 
			
		||||
        return mutableState.value.items
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract fun toggleFilterResults()
 | 
			
		||||
    fun setSourceFilter(filter: SourceFilter) {
 | 
			
		||||
        mutableState.update { it.copy(sourceFilter = filter) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toggleFilterResults() {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(onlyShowHasResults = !it.onlyShowHasResults)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun search(query: String) {
 | 
			
		||||
        if (this.query == query) return
 | 
			
		||||
@@ -147,6 +152,29 @@ abstract class SearchScreenModel<T>(
 | 
			
		||||
                .awaitAll()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateItems(items: Map<CatalogueSource, SearchItemResult>) {
 | 
			
		||||
        mutableState.update {
 | 
			
		||||
            it.copy(items = items)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getAndUpdateItems(function: (Map<CatalogueSource, SearchItemResult>) -> Map<CatalogueSource, SearchItemResult>) {
 | 
			
		||||
        updateItems(function(getItems()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Immutable
 | 
			
		||||
    data class State(
 | 
			
		||||
        val fromSourceId: Long? = null,
 | 
			
		||||
        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
 | 
			
		||||
        val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum class SourceFilter {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user