Fix RelatedMangasScreen crash

This commit is contained in:
Tran M. Cuong 2024-06-18 17:36:44 +07:00
parent 742126e4eb
commit c91cb19979
No known key found for this signature in database
GPG key ID: 733AA7624B9315C2
4 changed files with 214 additions and 210 deletions

View file

@ -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,

View file

@ -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<Manga> {
return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source)
.filterNotNull()
.collectLatest { manga ->
value = manga
}
}
}
}

View file

@ -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<UiPreferences>() }
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?) {

View file

@ -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)
}