diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt index f08c4b0eaf..ca06c6d462 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt @@ -4,31 +4,24 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import eu.kanade.domain.manga.model.Manga import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.FastScrollLazyColumn -import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.manga.components.BaseMangaListItem import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter -import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter.Event -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.collectLatest +import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState @Composable fun MigrateMangaScreen( navigateUp: () -> Unit, title: String?, - presenter: MigrateMangaPresenter, + state: MigrateMangaState, onClickItem: (Manga) -> Unit, onClickCover: (Manga) -> Unit, ) { - val context = LocalContext.current Scaffold( topBar = { scrollBehavior -> AppBar( @@ -38,30 +31,20 @@ fun MigrateMangaScreen( ) }, ) { contentPadding -> - when { - presenter.isLoading -> LoadingScreen() - presenter.isEmpty -> EmptyScreen( + if (state.isEmpty) { + EmptyScreen( textResource = R.string.empty_screen, modifier = Modifier.padding(contentPadding), ) - else -> { - MigrateMangaContent( - contentPadding = contentPadding, - state = presenter, - onClickItem = onClickItem, - onClickCover = onClickCover, - ) - } - } - } - LaunchedEffect(Unit) { - presenter.events.collectLatest { event -> - when (event) { - Event.FailedFetchingFavorites -> { - context.toast(R.string.internal_error) - } - } + return@Scaffold } + + MigrateMangaContent( + contentPadding = contentPadding, + state = state, + onClickItem = onClickItem, + onClickCover = onClickCover, + ) } } @@ -75,7 +58,7 @@ private fun MigrateMangaContent( FastScrollLazyColumn( contentPadding = contentPadding, ) { - items(state.items) { manga -> + items(state.titles) { manga -> MigrateMangaItem( manga = manga, onClickItem = onClickItem, diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaState.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaState.kt deleted file mode 100644 index b4cf8d0cef..0000000000 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateMangaState.kt +++ /dev/null @@ -1,23 +0,0 @@ -package eu.kanade.presentation.browse - -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import eu.kanade.domain.manga.model.Manga - -interface MigrateMangaState { - val isLoading: Boolean - val items: List - val isEmpty: Boolean -} - -fun MigrationMangaState(): MigrateMangaState { - return MigrateMangaStateImpl() -} - -class MigrateMangaStateImpl : MigrateMangaState { - override var isLoading: Boolean by mutableStateOf(true) - override var items: List by mutableStateOf(emptyList()) - override val isEmpty: Boolean by derivedStateOf { items.isEmpty() } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaPresenter.kt deleted file mode 100644 index 2960be6670..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaPresenter.kt +++ /dev/null @@ -1,51 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.manga - -import android.os.Bundle -import eu.kanade.domain.manga.interactor.GetFavorites -import eu.kanade.presentation.browse.MigrateMangaState -import eu.kanade.presentation.browse.MigrateMangaStateImpl -import eu.kanade.presentation.browse.MigrationMangaState -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.system.logcat -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.receiveAsFlow -import logcat.LogPriority -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class MigrateMangaPresenter( - private val sourceId: Long, - private val state: MigrateMangaStateImpl = MigrationMangaState() as MigrateMangaStateImpl, - private val getFavorites: GetFavorites = Injekt.get(), -) : BasePresenter(), MigrateMangaState by state { - - private val _events = Channel(Int.MAX_VALUE) - val events = _events.receiveAsFlow() - - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - presenterScope.launchIO { - getFavorites - .subscribe(sourceId) - .catch { - logcat(LogPriority.ERROR, it) - _events.send(Event.FailedFetchingFavorites) - } - .map { list -> - list.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title }) - } - .collectLatest { sortedList -> - state.isLoading = false - state.items = sortedList - } - } - } - - sealed class Event { - object FailedFetchingFavorites : Event() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt deleted file mode 100644 index d441c1ac0d..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt +++ /dev/null @@ -1,51 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse.migration.manga - -import android.os.Bundle -import androidx.compose.runtime.Composable -import androidx.core.os.bundleOf -import eu.kanade.presentation.browse.MigrateMangaScreen -import eu.kanade.tachiyomi.ui.base.controller.FullComposeController -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController -import eu.kanade.tachiyomi.ui.manga.MangaController - -class MigrationMangaController : FullComposeController { - - constructor(sourceId: Long, sourceName: String?) : super( - bundleOf( - SOURCE_ID_EXTRA to sourceId, - SOURCE_NAME_EXTRA to sourceName, - ), - ) - - @Suppress("unused") - constructor(bundle: Bundle) : this( - bundle.getLong(SOURCE_ID_EXTRA), - bundle.getString(SOURCE_NAME_EXTRA), - ) - - private val sourceId: Long = args.getLong(SOURCE_ID_EXTRA) - private val sourceName: String? = args.getString(SOURCE_NAME_EXTRA) - - override fun createPresenter() = MigrateMangaPresenter(sourceId) - - @Composable - override fun ComposeContent() { - MigrateMangaScreen( - navigateUp = router::popCurrentController, - title = sourceName, - presenter = presenter, - onClickItem = { - router.pushController(SearchController(it.id)) - }, - onClickCover = { - router.pushController(MangaController(it.id)) - }, - ) - } - - companion object { - const val SOURCE_ID_EXTRA = "source_id_extra" - const val SOURCE_NAME_EXTRA = "source_name_extra" - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaScreen.kt new file mode 100644 index 0000000000..4e3ebbdc40 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaScreen.kt @@ -0,0 +1,66 @@ +package eu.kanade.tachiyomi.ui.browse.migration.manga + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.browse.MigrateMangaScreen +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.util.LocalRouter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.base.controller.pushController +import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.flow.collectLatest + +data class MigrationMangaScreen( + private val sourceId: Long, +) : Screen { + + @Composable + override fun Content() { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val router = LocalRouter.currentOrThrow + val screenModel = rememberScreenModel { MigrationMangaScreenModel(sourceId) } + + val state by screenModel.state.collectAsState() + + if (state.isLoading) { + LoadingScreen() + return + } + + MigrateMangaScreen( + navigateUp = navigator::pop, + title = state.source!!.name, + state = state, + onClickItem = { + router.pushController(SearchController(it.id)) + }, + onClickCover = { + navigator.push(MangaScreen(it.id)) + }, + ) + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { event -> + when (event) { + MigrationMangaEvent.FailedFetchingFavorites -> { + context.toast(R.string.internal_error) + } + MigrationMangaEvent.FailedGettingSource -> { + context.toast(R.string.loader_not_implemented_error) + router.popCurrentController() + } + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaScreenModel.kt new file mode 100644 index 0000000000..1abba3d3a6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaScreenModel.kt @@ -0,0 +1,77 @@ +package eu.kanade.tachiyomi.ui.browse.migration.manga + +import androidx.compose.runtime.Immutable +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.domain.manga.interactor.GetFavorites +import eu.kanade.domain.manga.model.Manga +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.util.system.logcat +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import logcat.LogPriority +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class MigrationMangaScreenModel( + private val sourceId: Long, + private val sourceManager: SourceManager = Injekt.get(), + private val getFavorites: GetFavorites = Injekt.get(), +) : StateScreenModel(MigrateMangaState()) { + + private val _events: Channel = Channel() + val events: Flow = _events.receiveAsFlow() + + init { + coroutineScope.launch { + mutableState.update { state -> + val source = sourceManager.get(sourceId) + if (source == null) { + _events.send(MigrationMangaEvent.FailedGettingSource) + } + state.copy(source = source) + } + + getFavorites.subscribe(sourceId) + .catch { + logcat(LogPriority.ERROR, it) + _events.send(MigrationMangaEvent.FailedFetchingFavorites) + mutableState.update { it.copy(titleList = emptyList()) } + } + .map { + it.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title }) + } + .collectLatest { list -> + mutableState.update { it.copy(titleList = list) } + } + } + } +} + +sealed class MigrationMangaEvent { + object FailedGettingSource : MigrationMangaEvent() + object FailedFetchingFavorites : MigrationMangaEvent() +} + +@Immutable +data class MigrateMangaState( + val source: Source? = null, + private val titleList: List? = null, +) { + + val titles: List + get() = titleList ?: emptyList() + + val isLoading: Boolean + get() = source == null || titleList == null + + val isEmpty: Boolean + get() = titles.isEmpty() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt index eb13835516..6b3c7a9161 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt @@ -9,19 +9,18 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.browse.MigrateSourceScreen import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.TabContent -import eu.kanade.presentation.util.LocalRouter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController +import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaScreen @Composable fun Screen.migrateSourceTab(): TabContent { val uriHandler = LocalUriHandler.current - val router = LocalRouter.currentOrThrow + val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { MigrateSourceScreenModel() } val state by screenModel.state.collectAsState() @@ -41,12 +40,7 @@ fun Screen.migrateSourceTab(): TabContent { state = state, contentPadding = contentPadding, onClickItem = { source -> - router.pushController( - MigrationMangaController( - source.id, - source.name, - ), - ) + navigator.push(MigrationMangaScreen(source.id)) }, onToggleSortingDirection = screenModel::toggleSortingDirection, onToggleSortingMode = screenModel::toggleSortingMode,