Compare commits

...

4 Commits

Author SHA1 Message Date
AntsyLich
6c6ea84509 spotlessApply my beloved 2024-09-02 22:35:00 +06:00
AntsyLich
4ee31bfea5 Add stable marker to Manga data class
Co-authored-by: ivan <12537387+ivaniskandar@users.noreply.github.com>
2024-09-02 21:54:53 +06:00
AntsyLich
03eb756ecb Collect MangaScreen state with lifecycle
Co-authored-by: ivan <12537387+ivaniskandar@users.noreply.github.com>
2024-09-02 21:22:21 +06:00
AntsyLich
a45eb5e528 PagerPageHolder: lazy init loading indicator
Co-authored-by: ivan <12537387+ivaniskandar@users.noreply.github.com>
2024-09-02 21:13:52 +06:00
9 changed files with 84 additions and 73 deletions

View File

@@ -380,13 +380,9 @@ private fun MangaScreenSmallImpl(
MangaInfoBox(
isTabletUi = false,
appBarPadding = topPadding,
title = state.manga.title,
author = state.manga.author,
artist = state.manga.artist,
manga = state.manga,
sourceName = remember { state.source.getNameForMangaInfo() },
isStubSource = remember { state.source is StubSource },
coverDataProvider = { state.manga },
status = state.manga.status,
onCoverClick = onCoverClicked,
doSearch = onSearch,
)
@@ -622,13 +618,9 @@ fun MangaScreenLargeImpl(
MangaInfoBox(
isTabletUi = true,
appBarPadding = contentPadding.calculateTopPadding(),
title = state.manga.title,
author = state.manga.author,
artist = state.manga.artist,
manga = state.manga,
sourceName = remember { state.source.getNameForMangaInfo() },
isStubSource = remember { state.source is StubSource },
coverDataProvider = { state.manga },
status = state.manga.status,
onCoverClick = onCoverClicked,
doSearch = onSearch,
)

View File

@@ -56,7 +56,7 @@ import tachiyomi.presentation.core.util.clickableNoIndication
@Composable
fun MangaCoverDialog(
coverDataProvider: () -> Manga,
manga: Manga,
isCustomCover: Boolean,
snackbarHostState: SnackbarHostState,
onShareClick: () -> Unit,
@@ -166,7 +166,7 @@ fun MangaCoverDialog(
},
update = { view ->
val request = ImageRequest.Builder(view.context)
.data(coverDataProvider())
.data(manga)
.size(Size.ORIGINAL)
.memoryCachePolicy(CachePolicy.DISABLED)
.target { image ->

View File

@@ -75,6 +75,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
@@ -98,13 +100,9 @@ private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTIL
fun MangaInfoBox(
isTabletUi: Boolean,
appBarPadding: Dp,
title: String,
author: String?,
artist: String?,
manga: Manga,
sourceName: String,
isStubSource: Boolean,
coverDataProvider: () -> Manga,
status: Long,
onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit,
modifier: Modifier = Modifier,
@@ -116,7 +114,10 @@ fun MangaInfoBox(
MaterialTheme.colorScheme.background,
)
AsyncImage(
model = coverDataProvider(),
model = ImageRequest.Builder(LocalContext.current)
.data(manga)
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
@@ -136,28 +137,20 @@ fun MangaInfoBox(
if (!isTabletUi) {
MangaAndSourceTitlesSmall(
appBarPadding = appBarPadding,
coverDataProvider = coverDataProvider,
onCoverClick = onCoverClick,
title = title,
doSearch = doSearch,
author = author,
artist = artist,
status = status,
manga = manga,
sourceName = sourceName,
isStubSource = isStubSource,
onCoverClick = onCoverClick,
doSearch = doSearch,
)
} else {
MangaAndSourceTitlesLarge(
appBarPadding = appBarPadding,
coverDataProvider = coverDataProvider,
onCoverClick = onCoverClick,
title = title,
doSearch = doSearch,
author = author,
artist = artist,
status = status,
manga = manga,
sourceName = sourceName,
isStubSource = isStubSource,
onCoverClick = onCoverClick,
doSearch = doSearch,
)
}
}
@@ -338,15 +331,11 @@ fun ExpandableMangaDescription(
@Composable
private fun MangaAndSourceTitlesLarge(
appBarPadding: Dp,
coverDataProvider: () -> Manga,
onCoverClick: () -> Unit,
title: String,
doSearch: (query: String, global: Boolean) -> Unit,
author: String?,
artist: String?,
status: Long,
manga: Manga,
sourceName: String,
isStubSource: Boolean,
onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit,
) {
Column(
modifier = Modifier
@@ -356,19 +345,22 @@ private fun MangaAndSourceTitlesLarge(
) {
MangaCover.Book(
modifier = Modifier.fillMaxWidth(0.65f),
data = coverDataProvider(),
data = ImageRequest.Builder(LocalContext.current)
.data(manga)
.crossfade(true)
.build(),
contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick,
)
Spacer(modifier = Modifier.height(16.dp))
MangaContentInfo(
title = title,
doSearch = doSearch,
author = author,
artist = artist,
status = status,
title = manga.title,
author = manga.author,
artist = manga.artist,
status = manga.status,
sourceName = sourceName,
isStubSource = isStubSource,
doSearch = doSearch,
textAlign = TextAlign.Center,
)
}
@@ -377,15 +369,11 @@ private fun MangaAndSourceTitlesLarge(
@Composable
private fun MangaAndSourceTitlesSmall(
appBarPadding: Dp,
coverDataProvider: () -> Manga,
onCoverClick: () -> Unit,
title: String,
doSearch: (query: String, global: Boolean) -> Unit,
author: String?,
artist: String?,
status: Long,
manga: Manga,
sourceName: String,
isStubSource: Boolean,
onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit,
) {
Row(
modifier = Modifier
@@ -398,7 +386,10 @@ private fun MangaAndSourceTitlesSmall(
modifier = Modifier
.sizeIn(maxWidth = 100.dp)
.align(Alignment.Top),
data = coverDataProvider(),
data = ImageRequest.Builder(LocalContext.current)
.data(manga)
.crossfade(true)
.build(),
contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick,
)
@@ -406,13 +397,13 @@ private fun MangaAndSourceTitlesSmall(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
MangaContentInfo(
title = title,
doSearch = doSearch,
author = author,
artist = artist,
status = status,
title = manga.title,
author = manga.author,
artist = manga.artist,
status = manga.status,
sourceName = sourceName,
isStubSource = isStubSource,
doSearch = doSearch,
)
}
}
@@ -421,12 +412,12 @@ private fun MangaAndSourceTitlesSmall(
@Composable
private fun ColumnScope.MangaContentInfo(
title: String,
doSearch: (query: String, global: Boolean) -> Unit,
author: String?,
artist: String?,
status: Long,
sourceName: String,
isStubSource: Boolean,
doSearch: (query: String, global: Boolean) -> Unit,
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
) {
val context = LocalContext.current

View File

@@ -18,6 +18,8 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.core.net.toUri
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
@@ -85,9 +87,12 @@ class MangaScreen(
val context = LocalContext.current
val haptic = LocalHapticFeedback.current
val scope = rememberCoroutineScope()
val screenModel = rememberScreenModel { MangaScreenModel(context, mangaId, fromSource) }
val lifecycleOwner = LocalLifecycleOwner.current
val screenModel = rememberScreenModel {
MangaScreenModel(context, lifecycleOwner.lifecycle, mangaId, fromSource)
}
val state by screenModel.state.collectAsState()
val state by screenModel.state.collectAsStateWithLifecycle()
if (state is MangaScreenModel.State.Loading) {
LoadingScreen()
@@ -248,7 +253,7 @@ class MangaScreen(
sm.editCover(context, it)
}
MangaCoverDialog(
coverDataProvider = { manga!! },
manga = manga!!,
snackbarHostState = sm.snackbarHostState,
isCustomCover = remember(manga) { manga!!.hasCustomCover() },
onShareClick = { sm.shareCover(context) },

View File

@@ -6,6 +6,8 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.ui.util.fastAny
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState
@@ -83,8 +85,9 @@ import uy.kohesive.injekt.api.get
import kotlin.math.floor
class MangaScreenModel(
val context: Context,
val mangaId: Long,
private val context: Context,
private val lifecycle: Lifecycle,
private val mangaId: Long,
private val isFromSource: Boolean,
private val libraryPreferences: LibraryPreferences = Injekt.get(),
readerPreferences: ReaderPreferences = Injekt.get(),
@@ -159,6 +162,7 @@ class MangaScreenModel(
downloadCache.changes,
downloadManager.queueState,
) { mangaAndChapters, _, _ -> mangaAndChapters }
.flowWithLifecycle(lifecycle)
.collectLatest { (manga, chapters) ->
updateSuccessState {
it.copy(
@@ -171,6 +175,7 @@ class MangaScreenModel(
screenModelScope.launchIO {
getExcludedScanlators.subscribe(mangaId)
.flowWithLifecycle(lifecycle)
.distinctUntilChanged()
.collectLatest { excludedScanlators ->
updateSuccessState {
@@ -181,6 +186,7 @@ class MangaScreenModel(
screenModelScope.launchIO {
getAvailableScanlators.subscribe(mangaId)
.flowWithLifecycle(lifecycle)
.distinctUntilChanged()
.collectLatest { availableScanlators ->
updateSuccessState {
@@ -464,6 +470,7 @@ class MangaScreenModel(
downloadManager.statusFlow()
.filter { it.manga.id == successState?.manga?.id }
.catch { error -> logcat(LogPriority.ERROR, error) }
.flowWithLifecycle(lifecycle)
.collect {
withUIContext {
updateDownloadState(it)
@@ -475,6 +482,7 @@ class MangaScreenModel(
downloadManager.progressFlow()
.filter { it.manga.id == successState?.manga?.id }
.catch { error -> logcat(LogPriority.ERROR, error) }
.flowWithLifecycle(lifecycle)
.collect {
withUIContext {
updateDownloadState(it)
@@ -979,6 +987,7 @@ class MangaScreenModel(
val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds }
supportedTrackerTracks.size to supportedTrackers.isNotEmpty()
}
.flowWithLifecycle(lifecycle)
.distinctUntilChanged()
.collectLatest { (trackingCount, hasLoggedInTrackers) ->
updateSuccessState {

View File

@@ -45,7 +45,7 @@ class PagerPageHolder(
/**
* Loading progress bar to indicate the current progress.
*/
private val progressIndicator: ReaderProgressIndicator = ReaderProgressIndicator(readerThemedContext)
private var progressIndicator: ReaderProgressIndicator? = null // = ReaderProgressIndicator(readerThemedContext)
/**
* Error layout to show when the image fails to load.
@@ -60,7 +60,6 @@ class PagerPageHolder(
private var loadJob: Job? = null
init {
addView(progressIndicator)
loadJob = scope.launch { loadPageAndProcessStatus() }
}
@@ -74,6 +73,13 @@ class PagerPageHolder(
loadJob = null
}
private fun initProgressIndicator() {
if (progressIndicator == null) {
progressIndicator = ReaderProgressIndicator(context)
addView(progressIndicator)
}
}
/**
* Loads the page and processes changes to the page's status.
*
@@ -95,7 +101,7 @@ class PagerPageHolder(
Page.State.DOWNLOAD_IMAGE -> {
setDownloading()
page.progressFlow.collectLatest { value ->
progressIndicator.setProgress(value)
progressIndicator?.setProgress(value)
}
}
Page.State.READY -> setImage()
@@ -109,7 +115,8 @@ class PagerPageHolder(
* Called when the page is queued.
*/
private fun setQueued() {
progressIndicator.show()
initProgressIndicator()
progressIndicator?.show()
removeErrorLayout()
}
@@ -117,7 +124,8 @@ class PagerPageHolder(
* Called when the page is loading.
*/
private fun setLoading() {
progressIndicator.show()
initProgressIndicator()
progressIndicator?.show()
removeErrorLayout()
}
@@ -125,7 +133,8 @@ class PagerPageHolder(
* Called when the page is downloading.
*/
private fun setDownloading() {
progressIndicator.show()
initProgressIndicator()
progressIndicator?.show()
removeErrorLayout()
}
@@ -133,7 +142,7 @@ class PagerPageHolder(
* Called when the page is ready.
*/
private suspend fun setImage() {
progressIndicator.setProgress(0)
progressIndicator?.setProgress(0)
val streamFn = page.stream ?: return
@@ -234,13 +243,13 @@ class PagerPageHolder(
* Called when the page has an error.
*/
private fun setError() {
progressIndicator.hide()
progressIndicator?.hide()
showErrorLayout()
}
override fun onImageLoaded() {
super.onImageLoaded()
progressIndicator.hide()
progressIndicator?.hide()
}
/**

View File

@@ -25,6 +25,8 @@ dependencies {
api(libs.sqldelight.android.paging)
compileOnly(libs.compose.stablemarker)
testImplementation(libs.bundles.test)
testImplementation(kotlinx.coroutines.test)
}

View File

@@ -1,11 +1,13 @@
package tachiyomi.domain.manga.model
import androidx.compose.runtime.Immutable
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import tachiyomi.core.common.preference.TriState
import java.io.Serializable
import java.time.Instant
@Immutable
data class Manga(
val id: Long,
val source: Long,

View File

@@ -64,6 +64,7 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:2.0.1"
compose-webview = "io.github.kevinnzou:compose-webview:0.33.6"
compose-grid = "io.woong.compose.grid:grid:1.2.2"
compose-stablemarker = "com.github.skydoves:compose-stable-marker:1.0.5"
swipe = "me.saket.swipe:swipe:1.3.0"