diff --git a/app/src/main/java/eu/kanade/presentation/browse/RelatedMangasScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/RelatedMangasScreen.kt index 501cd5cd5..d278d278e 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/RelatedMangasScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/RelatedMangasScreen.kt @@ -80,10 +80,11 @@ fun RelatedMangasContent( selection = selection, ) } - LibraryDisplayMode.List -> { - RelatedMangasList( + LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> { + RelatedMangasCompactGrid( relatedMangas = relatedMangas, getManga = getMangaState, + columns = columns, contentPadding = contentPadding, onMangaClick = onMangaClick, onMangaLongClick = onMangaLongClick, @@ -92,11 +93,10 @@ fun RelatedMangasContent( selection = selection, ) } - LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> { - RelatedMangasCompactGrid( + LibraryDisplayMode.List -> { + RelatedMangasList( relatedMangas = relatedMangas, getManga = getMangaState, - columns = columns, contentPadding = contentPadding, onMangaClick = onMangaClick, onMangaLongClick = onMangaLongClick, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/RelatedMangasScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/RelatedMangasScreen.kt deleted file mode 100644 index 1e4e85916..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/RelatedMangasScreen.kt +++ /dev/null @@ -1,160 +0,0 @@ -package eu.kanade.tachiyomi.ui.browse - -import android.content.res.Configuration -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.unit.dp -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.core.preference.asState -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.presentation.browse.RelatedMangasContent -import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar -import eu.kanade.presentation.components.SelectionToolbar -import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.ui.manga.MangaScreen -import eu.kanade.tachiyomi.ui.manga.MangaScreenModel -import eu.kanade.tachiyomi.ui.manga.RelatedManga -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.filterNotNull -import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.manga.interactor.GetManga -import tachiyomi.domain.manga.model.Manga -import tachiyomi.presentation.core.components.material.Scaffold -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class RelatedMangasScreen( - val mangaScreenModel: MangaScreenModel, - val onKeywordClick: (String) -> Unit, - val onKeywordLongClick: (String) -> Unit, - private val getManga: GetManga = Injekt.get(), - private val sourcePreferences: SourcePreferences = Injekt.get(), - private val libraryPreferences: LibraryPreferences = Injekt.get(), -) : Screen() { - @Composable - override fun Content() { - val scope = rememberCoroutineScope() - var displayMode by sourcePreferences.sourceDisplayMode().asState(scope) - - val screenState by mangaScreenModel.state.collectAsState() - val successState = screenState as? MangaScreenModel.State.Success ?: return - - val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } - val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() - - val navigator = LocalNavigator.currentOrThrow - val haptic = LocalHapticFeedback.current - - BackHandler(enabled = bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelectionMode() - } - - val snackbarHostState = remember { SnackbarHostState() } - - Scaffold( - topBar = { scrollBehavior -> - if (bulkFavoriteState.selectionMode) { - SelectionToolbar( - selectedCount = bulkFavoriteState.selection.size, - onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, - onChangeCategoryClicked = bulkFavoriteScreenModel::addFavorite, - onSelectAll = { - successState.relatedMangasSorted?.forEach { - val relatedManga = it as RelatedManga.Success - relatedManga.mangaList.forEach { manga -> - if (!bulkFavoriteState.selection.contains(manga)) { - bulkFavoriteScreenModel.select(manga) - } - } - } - }, - ) - } else { - BrowseSourceSimpleToolbar( - navigateUp = navigator::pop, - title = successState.manga.title, - displayMode = displayMode, - onDisplayModeChange = { displayMode = it }, - scrollBehavior = scrollBehavior, - toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, - ) - } - }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - ) { paddingValues -> - RelatedMangasContent( - relatedMangas = successState.relatedMangasSorted, - getMangaState = { manga -> getMangaState(initialManga = manga) }, - columns = getColumnsPreference(LocalConfiguration.current.orientation), - displayMode = displayMode, - contentPadding = paddingValues, - onMangaClick = { manga -> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - navigator.push(MangaScreen(manga.id, true)) - } - }, - onMangaLongClick = { manga -> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - navigator.push(MangaScreen(manga.id, true)) - } - }, - onKeywordClick = onKeywordClick, - onKeywordLongClick = onKeywordLongClick, - selection = bulkFavoriteState.selection, - ) - } - - when (bulkFavoriteState.dialog) { - is BulkFavoriteScreenModel.Dialog.AddDuplicateManga -> - AddDuplicateMangaDialog(bulkFavoriteScreenModel) - is BulkFavoriteScreenModel.Dialog.RemoveManga -> - RemoveMangaDialog(bulkFavoriteScreenModel) - is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory -> - ChangeMangaCategoryDialog(bulkFavoriteScreenModel) - is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> - ChangeMangasCategoryDialog(bulkFavoriteScreenModel) - is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> - AllowDuplicateDialog(bulkFavoriteScreenModel) - else -> {} - } - } - - private fun getColumnsPreference(orientation: Int): GridCells { - val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE - val columns = if (isLandscape) { - libraryPreferences.landscapeColumns() - } else { - libraryPreferences.portraitColumns() - }.get() - return if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns) - } - - @Composable - fun getMangaState(initialManga: Manga): State { - return produceState(initialValue = initialManga) { - getManga.subscribe(initialManga.url, initialManga.source) - .filterNotNull() - .collectLatest { manga -> - value = manga - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 8e1713cec..614705177 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.ui.manga import android.content.Context import android.content.Intent +import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.MaterialTheme @@ -15,15 +17,18 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.core.net.toUri import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.screenModelScope +import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -54,7 +59,6 @@ import eu.kanade.tachiyomi.ui.browse.AllowDuplicateDialog import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.ChangeMangaCategoryDialog import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog -import eu.kanade.tachiyomi.ui.browse.RelatedMangasScreen import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreen import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen @@ -83,12 +87,15 @@ import exh.ui.ifSourcesLoaded import exh.ui.metadata.MetadataViewScreen import exh.ui.smartsearch.SmartSearchScreen import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import logcat.LogPriority +import soup.compose.material.motion.animation.materialSharedAxisX +import soup.compose.material.motion.animation.rememberSlideDistance import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchUI @@ -124,7 +131,9 @@ class MangaScreen( return } + val navigator = LocalNavigator.currentOrThrow val context = LocalContext.current + val scope = rememberCoroutineScope() val screenModel = rememberScreenModel { MangaScreenModel(context, mangaId, fromSource, smartSearchConfig != null) } @@ -136,9 +145,57 @@ class MangaScreen( } val successState = state as MangaScreenModel.State.Success + // KMK --> - val seedColorState = rememberUpdatedState(newValue = successState.seedColor) + val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } + val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + + val showingRelatedMangasScreen = rememberSaveable { mutableStateOf(false) } + + BackHandler(enabled = bulkFavoriteState.selectionMode || showingRelatedMangasScreen.value) { + when { + bulkFavoriteState.selectionMode -> bulkFavoriteScreenModel.toggleSelectionMode() + showingRelatedMangasScreen.value -> showingRelatedMangasScreen.value = false + } + } + val uiPreferences = remember { Injekt.get() } + val seedColorState = rememberUpdatedState(newValue = successState.seedColor) + + val content = @Composable { + val slideDistance = rememberSlideDistance() + AnimatedContent( + targetState = showingRelatedMangasScreen.value, + transitionSpec = { + materialSharedAxisX( + forward = navigator.lastEvent != StackEvent.Pop, + slideDistance = slideDistance, + ) + }, + label = "manga_related_transition", + contentKey = { "showingRelatedMangasScreen#$it" } + ) { showRelatedMangasScreen -> + when (showRelatedMangasScreen) { + true -> RelatedMangasScreen( + screenModel = screenModel, + successState = successState, + bulkFavoriteScreenModel = bulkFavoriteScreenModel, + navigateUp = { showingRelatedMangasScreen.value = false }, + navigator = navigator, + scope = scope, + ) + false -> MangaDetailContent( + context = context, + screenModel = screenModel, + successState = successState, + bulkFavoriteScreenModel = bulkFavoriteScreenModel, + showRelatedMangasScreen = { showingRelatedMangasScreen.value = true }, + navigator = navigator, + scope = scope, + ) + } + } + } if (uiPreferences.themeCoverBased().get()) { DynamicMaterialTheme( @@ -147,31 +204,48 @@ class MangaScreen( withAmoled = uiPreferences.themeDarkAmoled().get(), style = uiPreferences.themeCoverBasedStyle().get(), animate = true, - content = { MaterialThemeContent(context, screenModel, successState) }, + content = { content() }, ) } else { - MaterialThemeContent(context, screenModel, successState) + content() } + + // KMK --> + when (bulkFavoriteState.dialog) { + is BulkFavoriteScreenModel.Dialog.AddDuplicateManga -> + AddDuplicateMangaDialog(bulkFavoriteScreenModel) + + is BulkFavoriteScreenModel.Dialog.RemoveManga -> + RemoveMangaDialog(bulkFavoriteScreenModel) + + is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory -> + ChangeMangaCategoryDialog(bulkFavoriteScreenModel) + + is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> + ChangeMangasCategoryDialog(bulkFavoriteScreenModel) + + is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> + AllowDuplicateDialog(bulkFavoriteScreenModel) + + else -> {} + } + // KMK <-- } @Composable - fun MaterialThemeContent( + fun MangaDetailContent( context: Context, screenModel: MangaScreenModel, successState: MangaScreenModel.State.Success, + bulkFavoriteScreenModel: BulkFavoriteScreenModel, + showRelatedMangasScreen: () -> Unit, + navigator: Navigator, + scope: CoroutineScope, ) { // KMK <-- - val navigator = LocalNavigator.currentOrThrow val haptic = LocalHapticFeedback.current - val scope = rememberCoroutineScope() - val isHttpSource = remember { successState.source is HttpSource } - // KMK --> - val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } - val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() - // KMK <-- - LaunchedEffect(successState.manga, screenModel.source) { if (isHttpSource) { try { @@ -196,6 +270,7 @@ class MangaScreen( .launchIn(this) } // SY <-- + MangaScreen( state = successState, snackbarHostState = screenModel.snackbarHostState, @@ -289,17 +364,7 @@ class MangaScreen( if (successState.isRelatedMangasFetched == null) { screenModel.screenModelScope.launchIO { screenModel.fetchRelatedMangasFromSource(onDemand = true) } } - navigator.push( - RelatedMangasScreen( - mangaScreenModel = screenModel, - onKeywordClick = { query -> - navigator.push(BrowseSourceScreen(successState.source.id, query)) - }, - onKeywordLongClick = { query -> - navigator.push(GlobalSearchScreen(query)) - }, - ) - ) + showRelatedMangasScreen() }, onRelatedMangaClick = { navigator.push(MangaScreen(it.id, true)) }, onRelatedMangaLongClick = { bulkFavoriteScreenModel.addRemoveManga(it, haptic) }, @@ -449,27 +514,6 @@ class MangaScreen( onConfirm = screenModel::setExcludedScanlators, ) } - - // KMK --> - when (bulkFavoriteState.dialog) { - is BulkFavoriteScreenModel.Dialog.AddDuplicateManga -> - AddDuplicateMangaDialog(bulkFavoriteScreenModel) - - is BulkFavoriteScreenModel.Dialog.RemoveManga -> - RemoveMangaDialog(bulkFavoriteScreenModel) - - is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory -> - ChangeMangaCategoryDialog(bulkFavoriteScreenModel) - - is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory -> - ChangeMangasCategoryDialog(bulkFavoriteScreenModel) - - is BulkFavoriteScreenModel.Dialog.AllowDuplicate -> - AllowDuplicateDialog(bulkFavoriteScreenModel) - - else -> {} - } - // KMK <-- } private fun continueReading(context: Context, unreadChapter: Chapter?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt new file mode 100644 index 000000000..74bf36906 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt @@ -0,0 +1,120 @@ +package eu.kanade.tachiyomi.ui.manga + +import android.content.res.Configuration +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.navigator.Navigator +import eu.kanade.core.preference.asState +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.browse.RelatedMangasContent +import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar +import eu.kanade.presentation.components.SelectionToolbar +import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import kotlinx.coroutines.CoroutineScope +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.presentation.core.components.material.Scaffold +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +@Composable +fun RelatedMangasScreen( + screenModel: MangaScreenModel, + bulkFavoriteScreenModel: BulkFavoriteScreenModel, + navigateUp: () -> Unit, + navigator: Navigator, + scope: CoroutineScope, + successState: MangaScreenModel.State.Success, +) { + val sourcePreferences: SourcePreferences = Injekt.get() + var displayMode by sourcePreferences.sourceDisplayMode().asState(scope) + + val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + + val haptic = LocalHapticFeedback.current + + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold( + topBar = { scrollBehavior -> + if (bulkFavoriteState.selectionMode) { + SelectionToolbar( + selectedCount = bulkFavoriteState.selection.size, + onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, + onChangeCategoryClicked = bulkFavoriteScreenModel::addFavorite, + onSelectAll = { + successState.relatedMangasSorted?.forEach { + val relatedManga = it as RelatedManga.Success + relatedManga.mangaList.forEach { manga -> + if (!bulkFavoriteState.selection.contains(manga)) { + bulkFavoriteScreenModel.select(manga) + } + } + } + }, + ) + } else { + BrowseSourceSimpleToolbar( + navigateUp = navigateUp, + title = successState.manga.title, + displayMode = displayMode, + onDisplayModeChange = { displayMode = it }, + scrollBehavior = scrollBehavior, + toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, + ) + } + }, + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + ) { paddingValues -> + RelatedMangasContent( + relatedMangas = successState.relatedMangasSorted, + getMangaState = { manga -> screenModel.getManga(initialManga = manga) }, + columns = getColumnsPreference(LocalConfiguration.current.orientation), + displayMode = displayMode, + contentPadding = paddingValues, + onMangaClick = { manga -> + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + navigator.push(MangaScreen(manga.id, true)) + } + }, + onMangaLongClick = { manga -> + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + navigator.push(MangaScreen(manga.id, true)) + } + }, + onKeywordClick = { query -> + navigator.push(BrowseSourceScreen(successState.source.id, query)) + }, + onKeywordLongClick = { query -> + navigator.push(GlobalSearchScreen(query)) + }, + selection = bulkFavoriteState.selection, + ) + } +} + +private fun getColumnsPreference(orientation: Int): GridCells { + val libraryPreferences: LibraryPreferences = Injekt.get() + + val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE + val columns = if (isLandscape) { + libraryPreferences.landscapeColumns() + } else { + libraryPreferences.portraitColumns() + }.get() + return if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns) +}