diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt index 7e4697e68..4df4179fe 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt index 3b41222ff..31abb596c 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt index 524db9b08..8d5a86ee0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt @@ -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)) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt new file mode 100644 index 000000000..0bd9bda89 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt @@ -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(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() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt index 23733e947..10572cd60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt @@ -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(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) { - mutableState.update { - it.copy(items = items) - } - } - - override fun getItems(): Map { - 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 = 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() - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt index fe30da8f0..844755766 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreenModel.kt @@ -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(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) { - mutableState.update { - it.copy(items = items) - } - } - - override fun getItems(): Map { - 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 = emptyMap(), - ) { - val progress: Int = items.count { it.value !is SearchItemResult.Loading } - val total: Int = items.size - val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index a46502549..12e20251a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -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( - 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(initialState) { +) : StateScreenModel(initialState) { private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher() private var searchJob: Job? = null @@ -55,7 +54,7 @@ abstract class SearchScreenModel( } @Composable - fun getManga(initialManga: Manga): State { + fun getManga(initialManga: Manga): androidx.compose.runtime.State { return produceState(initialValue = initialManga) { getManga.subscribe(initialManga.url, initialManga.source) .filterNotNull() @@ -95,19 +94,25 @@ abstract class SearchScreenModel( .filter { it in enabledSources } } - abstract fun updateSearchQuery(query: String?) - - abstract fun updateItems(items: Map) - - abstract fun getItems(): Map - - private fun getAndUpdateItems(function: (Map) -> Map) { - updateItems(function(getItems())) + fun updateSearchQuery(query: String?) { + mutableState.update { + it.copy(searchQuery = query) + } } - abstract fun setSourceFilter(filter: SourceFilter) + fun getItems(): Map { + 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( .awaitAll() } } + + private fun updateItems(items: Map) { + mutableState.update { + it.copy(items = items) + } + } + + private fun getAndUpdateItems(function: (Map) -> Map) { + 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 = 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 {