mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Use Voyager on Migrate Manga screen (#8611)
This commit is contained in:
		| @@ -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, | ||||
|   | ||||
| @@ -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<Manga> | ||||
|     val isEmpty: Boolean | ||||
| } | ||||
|  | ||||
| fun MigrationMangaState(): MigrateMangaState { | ||||
|     return MigrateMangaStateImpl() | ||||
| } | ||||
|  | ||||
| class MigrateMangaStateImpl : MigrateMangaState { | ||||
|     override var isLoading: Boolean by mutableStateOf(true) | ||||
|     override var items: List<Manga> by mutableStateOf(emptyList()) | ||||
|     override val isEmpty: Boolean by derivedStateOf { items.isEmpty() } | ||||
| } | ||||
| @@ -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<MigrationMangaController>(), MigrateMangaState by state { | ||||
|  | ||||
|     private val _events = Channel<Event>(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() | ||||
|     } | ||||
| } | ||||
| @@ -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<MigrateMangaPresenter> { | ||||
|  | ||||
|     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" | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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>(MigrateMangaState()) { | ||||
|  | ||||
|     private val _events: Channel<MigrationMangaEvent> = Channel() | ||||
|     val events: Flow<MigrationMangaEvent> = _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<Manga>? = null, | ||||
| ) { | ||||
|  | ||||
|     val titles: List<Manga> | ||||
|         get() = titleList ?: emptyList() | ||||
|  | ||||
|     val isLoading: Boolean | ||||
|         get() = source == null || titleList == null | ||||
|  | ||||
|     val isEmpty: Boolean | ||||
|         get() = titles.isEmpty() | ||||
| } | ||||
| @@ -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, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user