mirror of
https://github.com/mihonapp/mihon.git
synced 2025-08-19 21:11:31 +02:00
Compare commits
4 Commits
8f9a325895
...
6c6ea84509
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c6ea84509 | ||
|
|
4ee31bfea5 | ||
|
|
03eb756ecb | ||
|
|
a45eb5e528 |
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,8 @@ dependencies {
|
||||
|
||||
api(libs.sqldelight.android.paging)
|
||||
|
||||
compileOnly(libs.compose.stablemarker)
|
||||
|
||||
testImplementation(libs.bundles.test)
|
||||
testImplementation(kotlinx.coroutines.test)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user