Initial Compose ReaderScreen (#799)

* Initial Compose ReaderScreen

* Refine

* Naming

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
Vladimir DostoyevskiyandGG Bond authored and GitHub committed 2024-02-27 09:44:03 +00:00
1 parent 2ff9df4c07
commit ff4f76dfa5
13 files changed
+199 -28

No files matched your search

@@ -184,6 +184,7 @@ object Settings : DataStorePreferences(null) {
val readingMode = intPref("pref_default_reading_mode_key", ReadingModeType.LEFT_TO_RIGHT.flagValue)
val orientationMode = intPref("pref_default_orientation_type_key", OrientationType.FREE.flagValue)
val showReaderSeekbar = boolPref("pref_show_reader_seekbar", true)
var newReader by boolPref("new_compose_reader", false)
init {
if ("CN" == Locale.getDefault().country) {
@@ -94,7 +94,7 @@ class Image private constructor(image: CoilImage, private val src: AutoCloseable
size(targetWidth, targetHeight)
scale(Scale.FILL)
precision(Precision.INEXACT)
if (isAtLeastO) {
if (isAtLeastO && !Settings.newReader) {
colorSpace(colorSpace)
}
maybeCropBorder(Settings.cropBorder.value)
@@ -53,6 +53,7 @@ import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.DownloadService
import com.hippo.ehviewer.download.downloadDir
import com.hippo.ehviewer.download.downloadLocation
import com.hippo.ehviewer.ui.destinations.ReaderScreenDestination
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.LabeledCheckbox
import com.hippo.ehviewer.util.FavouriteStatusRouter
@@ -62,6 +63,7 @@ import com.hippo.ehviewer.util.mapToLongArray
import com.hippo.ehviewer.util.requestPermission
import com.hippo.ehviewer.util.toEpochMillis
import com.hippo.unifile.UniFile
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.lang.withUIContext
@@ -270,15 +272,24 @@ suspend fun removeFromFavorites(galleryInfo: BaseGalleryInfo) = doModifyFavorite
localFavorited = EhDB.containLocalFavorites(galleryInfo.gid),
)
fun Context.navToReader(info: BaseGalleryInfo, page: Int = -1) {
val intent = Intent(this, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_EH
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, info)
intent.putExtra(ReaderActivity.KEY_PAGE, page)
startActivity(intent)
context (Context, DestinationsNavigator)
fun navToReader(
info: BaseGalleryInfo,
page: Int = -1,
) {
if (Settings.newReader) {
navigate(ReaderScreenDestination(info, page))
} else {
val intent = Intent(this@Context, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_EH
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, info)
intent.putExtra(ReaderActivity.KEY_PAGE, page)
startActivity(intent)
}
}
suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Context) {
context(DialogState, Context, DestinationsNavigator)
suspend fun doGalleryInfoAction(info: BaseGalleryInfo) {
val downloaded = DownloadManager.getDownloadState(info.gid) != DownloadInfo.STATE_INVALID
val favorited = info.favoriteSlot != NOT_FAVORITED
val items = buildList {
@@ -300,14 +311,14 @@ suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Cont
}
}
val selected = showSelectItemWithIcon(items, EhUtils.getSuitableTitle(info))
with(context.findActivity<MainActivity>()) {
with(findActivity<MainActivity>()) {
when (selected) {
0 -> navToReader(info)
1 -> withUIContext {
if (downloaded) {
confirmRemoveDownload(info)
} else {
startDownload(context, false, info)
startDownload(this@with, false, info)
}
}
@@ -0,0 +1,33 @@
package com.hippo.ehviewer.ui
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
// Fuck U, Kotlin compiler!!!
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
inline fun <T1, T2, R> with(
t1: T1,
t2: T2,
block: context(T1, T2)
() -> R,
): R {
contract {
callsInPlace(block, EXACTLY_ONCE)
}
return block(t1, t2)
}
// Fuck U, Kotlin compiler!!!
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
inline fun <T1, T2, T3, R> with(
t1: T1,
t2: T2,
t3: T3,
block: context(T1, T2, T3)
() -> R,
): R {
contract {
callsInPlace(block, EXACTLY_ONCE)
}
return block(t1, t2, t3)
}
@@ -19,11 +19,11 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.annotation.MainThread
import androidx.browser.customtabs.CustomTabsIntent
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
private val intent = CustomTabsIntent.Builder().apply { setShowTitle(true) }.build()
@@ -36,8 +36,11 @@ fun Context.openBrowser(url: String) {
}
}
@MainThread
fun Context.jumpToReaderByPage(url: String, detail: GalleryDetail): Boolean {
context(Context, DestinationsNavigator)
fun Context.jumpToReaderByPage(
url: String,
detail: GalleryDetail,
): Boolean {
GalleryPageUrlParser.parse(url)?.let {
if (it.gid == detail.gid) {
navToReader(detail.galleryInfo, it.page)
@@ -0,0 +1,111 @@
package com.hippo.ehviewer.ui.reader
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import coil3.BitmapImage
import coil3.Image as CoilImage
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.archiveFile
import com.hippo.ehviewer.gallery.ArchivePageLoader
import com.hippo.ehviewer.gallery.EhPageLoader
import com.hippo.ehviewer.ui.LockDrawer
import com.hippo.ehviewer.ui.tools.Deferred
import com.ramcosta.composedestinations.annotation.Destination
import eu.kanade.tachiyomi.source.model.Page.State.DOWNLOAD_IMAGE
import eu.kanade.tachiyomi.source.model.Page.State.ERROR
import eu.kanade.tachiyomi.source.model.Page.State.LOAD_PAGE
import eu.kanade.tachiyomi.source.model.Page.State.QUEUE
import eu.kanade.tachiyomi.source.model.Page.State.READY
@Destination
@Composable
fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1) {
LockDrawer(true)
val pageLoader = remember {
val archive = DownloadManager.getDownloadInfo(info.gid)?.archiveFile
archive?.let { ArchivePageLoader(it, info.gid, page) } ?: EhPageLoader(info, page)
}
DisposableEffect(Unit) {
pageLoader.start()
onDispose {
pageLoader.stop()
}
}
Deferred({ pageLoader.awaitReady() }) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(pageLoader.mPages) { page ->
pageLoader.request(page.index)
val state by page.status.collectAsState()
when (state) {
QUEUE, LOAD_PAGE -> {
Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}
DOWNLOAD_IMAGE -> {
Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) {
val progress by page.progressFlow.collectAsState()
CircularProgressIndicator(
progress = { progress / 100f },
modifier = Modifier.align(Alignment.Center),
)
}
}
READY -> {
val image = page.image!!.innerImage!!
val painter = remember(image) { image.toPainter() }
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth,
)
}
ERROR -> {
Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) {
Column(
modifier = Modifier.align(Companion.Center),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = page.errorMsg.orEmpty())
Button(onClick = { }) {
Text(text = stringResource(id = R.string.action_retry))
}
}
}
}
}
}
}
}
}
private fun CoilImage.toPainter() = when (this) {
is BitmapImage -> BitmapPainter(image = bitmap.asImageBitmap())
else -> TODO()
}
private const val DEFAULT_ASPECT = 1 / 1.4125f
@@ -110,6 +110,7 @@ import com.hippo.ehviewer.ui.tools.SwipeToDismissBox2
import com.hippo.ehviewer.ui.tools.delegateSnapshotUpdate
import com.hippo.ehviewer.ui.tools.draggingHapticFeedback
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.with
import com.hippo.ehviewer.util.findActivity
import com.hippo.ehviewer.util.mapToLongArray
import com.ramcosta.composedestinations.annotation.Destination
@@ -466,7 +467,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
coroutineScope.launchIO {
EhDB.putHistoryInfo(info.galleryInfo)
}
context.navToReader(info.galleryInfo)
with(context, navigator) { navToReader(info.galleryInfo) }
}
Crossfade(targetState = gridView, label = "Downloads") { showGridView ->
if (showGridView) {
@@ -580,7 +581,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
) {
if (!selectMode) {
onClick(Icons.Default.Shuffle) {
context.navToReader(list.random().galleryInfo)
with(context, navigator) { navToReader(list.random().galleryInfo) }
}
onClick(Icons.AutoMirrored.Default.Sort) {
val selected = dialogState.showSingleChoice(
@@ -97,6 +97,7 @@ import com.hippo.ehviewer.ui.tools.snackBarPadding
import com.hippo.ehviewer.ui.tools.toAnnotatedString
import com.hippo.ehviewer.ui.tools.toBBCode
import com.hippo.ehviewer.ui.tools.updateSpan
import com.hippo.ehviewer.ui.with
import com.hippo.ehviewer.util.ReadableTime
import com.hippo.ehviewer.util.TextUrl
import com.hippo.ehviewer.util.addTextToClipboard
@@ -384,7 +385,7 @@ fun GalleryCommentsScreen(gid: Long, navigator: DestinationsNavigator) {
}
},
onUrlClick = {
if (!activity.jumpToReaderByPage(it, galleryDetail)) {
if (!with(activity, navigator) { jumpToReaderByPage(it, galleryDetail) }) {
if (!navigator.navWithUrl(it)) {
activity.openBrowser(it)
}
@@ -144,6 +144,7 @@ import com.hippo.ehviewer.ui.tools.GalleryRatingBar
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.tools.rememberLambda
import com.hippo.ehviewer.ui.with
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.AppHelper
import com.hippo.ehviewer.util.FavouriteStatusRouter
@@ -243,7 +244,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
true,
)
if (result == SnackbarResult.ActionPerformed) {
context.navToReader(gi.findBaseInfo(), page)
with(context, navigator) { navToReader(gi.findBaseInfo(), page) }
}
}
}
@@ -407,7 +408,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
EhPreviewItem(
galleryPreview = it,
position = it.position,
onClick = { context.navToReader(gd.galleryInfo, it.position) },
onClick = { with(context, navigator) { navToReader(gd.galleryInfo, it.position) } },
)
}
item(span = { GridItemSpan(maxLineSpan) }) {
@@ -460,9 +461,11 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
onCardClick = ::onNavigateToCommentScene,
onUserClick = ::onNavigateToCommentScene,
onUrlClick = {
if (!activity.jumpToReaderByPage(it, galleryDetail)) {
if (!navigator.navWithUrl(it)) {
activity.openBrowser(it)
with(activity, navigator) {
if (!jumpToReaderByPage(it, galleryDetail)) {
if (!navWithUrl(it)) {
openBrowser(it)
}
}
}
},
@@ -811,7 +814,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
DownloadInfo.STATE_FAILED -> stringResource(R.string.download_state_failed)
else -> error("Invalid DownloadState!!!")
}
fun onReadButtonClick() = context.navToReader(galleryInfo.findBaseInfo(), startPage)
fun onReadButtonClick() = with(context, navigator) { navToReader(galleryInfo.findBaseInfo(), startPage) }
fun onCategoryChipClick() {
val category = galleryInfo.category
if (category == EhUtils.NONE || category == EhUtils.PRIVATE || category == EhUtils.UNKNOWN) {
@@ -132,6 +132,7 @@ import com.hippo.ehviewer.ui.tools.draggingHapticFeedback
import com.hippo.ehviewer.ui.tools.foldToLoadResult
import com.hippo.ehviewer.ui.tools.observed
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.with
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.hippo.ehviewer.util.findActivity
import com.hippo.ehviewer.util.pickVisualMedia
@@ -668,7 +669,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
},
onLongClick = {
coroutineScope.launch {
dialogState.doGalleryInfoAction(info, context)
with(dialogState, context, navigator) { doGalleryInfoAction(info) }
}
},
info = info,
@@ -684,7 +685,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
},
onLongClick = {
coroutineScope.launch {
dialogState.doGalleryInfoAction(info, context)
with(dialogState, context, navigator) { doGalleryInfoAction(info) }
}
},
info = info,
@@ -21,7 +21,6 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
@@ -51,13 +50,15 @@ import com.hippo.ehviewer.ui.tools.getLimit
import com.hippo.ehviewer.ui.tools.getOffset
import com.hippo.ehviewer.ui.tools.launchInVM
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.with
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.util.lang.withIOContext
import moe.tarsin.coroutines.runSuspendCatching
@Destination
@Composable
fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPage: Boolean, navigator: NavController) {
fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPage: Boolean, navigator: DestinationsNavigator) {
LockDrawer(true)
val context = LocalContext.current
val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior()
@@ -139,7 +140,7 @@ fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPage: Boolean, navi
) { index ->
val item = data[index]
EhPreviewItem(item, index) {
context.navToReader(galleryDetail.galleryInfo, index)
with(context, navigator) { navToReader(galleryDetail.galleryInfo, index) }
}
}
}
@@ -50,6 +50,7 @@ import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.SwipeToDismissBox2
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.with
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@@ -134,7 +135,7 @@ fun HistoryScreen(navigator: DestinationsNavigator) {
},
onLongClick = {
coroutineScope.launchIO {
dialogState.doGalleryInfoAction(info, context)
with(dialogState, context, navigator) { doGalleryInfoAction(info) }
}
},
info = info,
@@ -84,6 +84,10 @@ fun AdvancedScreen(navigator: DestinationsNavigator) {
snackbarHost = { SnackbarHost(snackbarHostState) },
) { paddingValues ->
Column(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection).verticalScroll(rememberScrollState()).padding(paddingValues)) {
SwitchPreference(
title = "New Compose Reader [WIP!!!]",
value = Settings::newReader,
)
SwitchPreference(
title = stringResource(id = R.string.settings_advanced_save_parse_error_body),
summary = stringResource(id = R.string.settings_advanced_save_parse_error_body_summary),