[New Reader] Implement open archive intent

This commit is contained in:
FooIbar committed 2024-07-28 14:00:23 +08:00
1 parent 7f8e4aa003
commit 02e36e7021
15 files changed
+311 -215

No files matched your search

+19 -20
View File
@@ -70,26 +70,6 @@
<data android:host="exhentai.org" />
<data android:host="e-hentai.org" />
</intent-filter>
<intent-filter android:label="@string/keyword_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/image_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -123,6 +103,25 @@
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
<intent-filter android:label="@string/keyword_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/image_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:launchMode="singleTask">
</activity>
<activity
android:name=".ui.SecurityActivity"
@@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.util.system.logcat
import java.nio.ByteBuffer
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
@@ -54,21 +55,19 @@ class ArchivePageLoader(
passwdProvider: PasswdProvider? = null,
) : PageLoader2(gid, startPage) {
private lateinit var pfd: ParcelFileDescriptor
private val hostJob = launch(start = CoroutineStart.LAZY) {
private val hostJob = async(start = CoroutineStart.LAZY) {
logcat(DEBUG_TAG) { "Open archive ${file.toUri().displayPath}" }
pfd = file.openFileDescriptor("r")
size = openArchive(pfd.fd, pfd.statSize, gid == 0L || file.name.endsWith(".zip"))
if (size == 0) {
return@launch
}
if (passwdProvider != null && needPassword()) {
val size = openArchive(pfd.fd, pfd.statSize, gid == 0L || file.name.endsWith(".zip"))
if (size != 0 && passwdProvider != null && needPassword()) {
archivePasswds?.forEach {
it ?: return@forEach
if (providePassword(it)) return@launch
if (providePassword(it)) return@async size
}
val toAdd = passwdProvider { providePassword(it) }
archivePasswds = archivePasswds?.toMutableSet()?.apply { add(toAdd) } ?: setOf(toAdd)
}
size
}
override var size = 0
@@ -136,8 +135,8 @@ class ArchivePageLoader(
}
override suspend fun awaitReady(): Boolean {
hostJob.join()
return super.awaitReady() && size != 0
size = hostJob.await()
return super.awaitReady() && isReady
}
override val isReady: Boolean
@@ -15,7 +15,6 @@
*/
package com.hippo.ehviewer.gallery
import androidx.annotation.CallSuper
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.util.FileUtils
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader
@@ -23,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import okio.Path
@@ -31,21 +31,19 @@ abstract class PageLoader2(private val gid: Long, var startPage: Int) :
PageLoader(),
CoroutineScope {
override val coroutineContext = Dispatchers.IO + Job()
private val progressJob = launch(start = CoroutineStart.LAZY) {
private val progressJob = async(start = CoroutineStart.LAZY) {
if (startPage == -1) {
startPage = EhDB.getReadProgress(gid)
}
}
override fun start() {
super.start()
progressJob.start()
}
@CallSuper
override suspend fun awaitReady(): Boolean {
progressJob.join()
return startPage != -1
progressJob.await()
return super.awaitReady() && startPage != -1
}
override fun stop() {
@@ -50,6 +50,7 @@ import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
@@ -224,7 +225,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
check(!(mReadReference < 0 || mDownloadReference < 0)) { "Mode reference < 0" }
}
private val prepareJob = launch { doPrepare() }
private val prepareJob = async { doPrepare() }
private val archiveJob = launch(start = CoroutineStart.LAZY) { mSpiderDen.archive() }
private suspend fun doPrepare() {
@@ -235,7 +236,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
}
suspend fun awaitReady(): Boolean {
prepareJob.join()
prepareJob.await()
return isReady
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -54,6 +55,7 @@ import com.hippo.ehviewer.download.downloadDir
import com.hippo.ehviewer.download.downloadLocation
import com.hippo.ehviewer.download.tempDownloadDir
import com.hippo.ehviewer.ui.destinations.ReaderScreenDestination
import com.hippo.ehviewer.ui.reader.ReaderScreenArgs
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.LabeledCheckbox
import com.hippo.ehviewer.util.FavouriteStatusRouter
@@ -286,7 +288,7 @@ fun navToReader(
page: Int = -1,
) {
if (Settings.newReader) {
navigate(ReaderScreenDestination(info, page))
navigate(ReaderScreenDestination(ReaderScreenArgs.Gallery(info, page)))
} else {
val intent = Intent(this@Context, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_EH
@@ -296,6 +298,17 @@ fun navToReader(
}
}
fun DestinationsNavigator.navToReader(context: Context, uri: Uri) {
if (Settings.newReader) {
navigate(ReaderScreenDestination(ReaderScreenArgs.Archive(uri)))
} else {
val intent = Intent(context, ReaderActivity::class.java)
intent.action = Intent.ACTION_VIEW
intent.data = uri
context.startActivity(intent)
}
}
context(DialogState, Context, DestinationsNavigator)
suspend fun doGalleryInfoAction(info: BaseGalleryInfo) {
val downloaded = DownloadManager.getDownloadState(info.gid) != DownloadInfo.STATE_INVALID
@@ -17,6 +17,8 @@ package com.hippo.ehviewer.ui
import android.annotation.SuppressLint
import android.app.assist.AssistContent
import android.content.ContentResolver.SCHEME_CONTENT
import android.content.ContentResolver.SCHEME_FILE
import android.content.Intent
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE
@@ -265,10 +267,19 @@ class MainActivity : EhActivity() {
intentFlow.collect { intent ->
when (intent.action) {
Intent.ACTION_VIEW -> {
val url = intent.data?.toString()
if (url != null && !navigator.navWithUrl(url)) {
val new = dialogState.awaitInputText(initial = url, title = cannotParse)
addTextToClipboard(new)
val uri = intent.data ?: return@collect
when (uri.scheme) {
SCHEME_CONTENT, SCHEME_FILE -> {
navigator.navToReader(applicationContext, uri)
}
else -> {
val url = uri.toString()
if (!navigator.navWithUrl(url)) {
val new = dialogState.awaitInputText(initial = url, title = cannotParse)
addTextToClipboard(new)
}
}
}
}
Intent.ACTION_SEND -> {
@@ -1,5 +1,8 @@
package com.hippo.ehviewer.ui.reader
import android.content.Context
import android.net.Uri
import android.os.Parcelable
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
@@ -21,6 +24,7 @@ import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -37,8 +41,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowInsetsControllerCompat
import arrow.core.Either
import arrow.core.raise.ensure
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
@@ -48,8 +55,12 @@ 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.gallery.PageLoader2
import com.hippo.ehviewer.ui.composing
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.Await
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.util.displayString
import com.hippo.files.toOkioPath
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@@ -63,11 +74,51 @@ import kotlin.coroutines.resume
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import moe.tarsin.kt.unreachable
sealed interface ReaderScreenArgs : Parcelable {
@Parcelize
data class Gallery(val info: BaseGalleryInfo, val page: Int) : ReaderScreenArgs
@Parcelize
data class Archive(val uri: Uri) : ReaderScreenArgs
}
@Destination<RootGraph>
@Composable
fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsNavigator) = composing(navigator) {
fun ReaderScreen(args: ReaderScreenArgs, navigator: DestinationsNavigator) = composing(navigator) {
Await({ preparePageLoader(args) }) { pageLoader ->
DisposableEffect(pageLoader) {
pageLoader.start()
onDispose {
pageLoader.stop()
}
}
val readingFailed = stringResource(R.string.error_reading_failed)
Await({
Either.catch {
readingFailed.takeUnless { pageLoader.awaitReady() }
}.fold({ it.displayString() }, { it })
}) { error ->
if (error == null) {
val info = (args as? ReaderScreenArgs.Gallery)?.info
ReaderScreen(pageLoader, info, navigator)
} else {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
text = error,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.titleLarge,
)
}
}
}
}
}
@Composable
fun ReaderScreen(pageLoader: PageLoader2, info: BaseGalleryInfo?, navigator: DestinationsNavigator) = composing(navigator) {
ConfigureKeepScreenOn()
LaunchedEffect(Unit) {
val orientation = requestedOrientation
@@ -75,21 +126,11 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN
.onCompletion { requestedOrientation = orientation }
.collect { setOrientation(it) }
}
val pageLoader = remember {
val archive = DownloadManager.getDownloadInfo(info.gid)?.archiveFile
archive?.let { ArchivePageLoader(it, info.gid, page) } ?: EhPageLoader(info, page)
}
LaunchedEffect(Unit) {
LaunchedEffect(pageLoader) {
Settings.cropBorder.changesFlow().collect {
pageLoader.restart()
}
}
DisposableEffect(pageLoader) {
pageLoader.start()
onDispose {
pageLoader.stop()
}
}
val showSeekbar by Settings.showReaderSeekbar.collectAsState()
val readingMode by Settings.readingMode.collectAsState { ReadingModeType.fromPreference(it) }
val volumeKeysEnabled by Settings.readWithVolumeKeys.collectAsState()
@@ -103,139 +144,163 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN
uiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
}
}
Deferred({ pageLoader.awaitReady() }) {
val lazyListState = rememberLazyListState()
val pagerState = rememberPagerState { pageLoader.size }
val syncState = rememberSliderPagerDoubleSyncState(lazyListState, pagerState, pageLoader)
Box {
var appbarVisible by remember { mutableStateOf(false) }
val bgColor by collectBackgroundColorAsState()
syncState.Sync(ReadingModeType.isWebtoon(readingMode)) { appbarVisible = false }
if (fullscreen) {
LaunchedEffect(Unit) {
snapshotFlow { appbarVisible }.collect {
uiController.isSystemBarsVisible = it
}
val lazyListState = rememberLazyListState()
val pagerState = rememberPagerState { pageLoader.size }
val syncState = rememberSliderPagerDoubleSyncState(lazyListState, pagerState, pageLoader)
Box {
var appbarVisible by remember { mutableStateOf(false) }
val bgColor by collectBackgroundColorAsState()
syncState.Sync(ReadingModeType.isWebtoon(readingMode)) { appbarVisible = false }
if (fullscreen) {
LaunchedEffect(Unit) {
snapshotFlow { appbarVisible }.collect {
uiController.isSystemBarsVisible = it
}
}
VolumeKeysHandler(
enabled = { volumeKeysEnabled && !appbarVisible },
movePrevious = { syncState.sliderScrollTo(syncState.sliderValue - 1) },
moveNext = { syncState.sliderScrollTo(syncState.sliderValue + 1) },
)
GalleryPager(
type = readingMode,
pagerState = pagerState,
lazyListState = lazyListState,
pageLoader = pageLoader,
onSelectPage = { page ->
if (Settings.readerLongTapAction.value) {
launch {
dialog { cont ->
fun dispose() = cont.resume(Unit)
val state = rememberModalBottomSheetState()
ModalBottomSheet(
onDismissRequest = { dispose() },
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)),
sheetState = state,
contentWindowInsets = { WindowInsets(0) },
) {
ReaderPageSheetMeta(
retry = { pageLoader.retryPage(page.index) },
retryOrigin = { pageLoader.retryPage(page.index, true) },
share = { launchIO { with(pageLoader) { shareImage(page, info) } } },
copy = { launchIO { with(pageLoader) { copy(page) } } },
save = { launchIO { with(pageLoader) { save(page) } } },
saveTo = { launchIO { with(pageLoader) { saveTo(page) } } },
dismiss = { launch { state.hide().also { dispose() } } },
)
}
}
}
}
},
onMenuRegionClick = { appbarVisible = !appbarVisible },
modifier = Modifier.background(bgColor),
contentPadding = if (fullscreen) {
if (cutoutShort) {
PaddingValues(0.dp)
} else {
WindowInsets.displayCutout.asPaddingValues()
}
} else {
WindowInsets.systemBars.asPaddingValues()
},
)
val brightness by Settings.customBrightness.collectAsState()
val brightnessValue by Settings.customBrightnessValue.collectAsState()
val colorOverlayEnabled by Settings.colorFilter.collectAsState()
val colorOverlay by Settings.colorFilterValue.collectAsState()
val colorOverlayMode by Settings.colorFilterMode.collectAsState {
when (it) {
0 -> BlendMode.SrcOver
1 -> BlendMode.Multiply
2 -> BlendMode.Screen
3 -> BlendMode.Overlay
4 -> BlendMode.Lighten
5 -> BlendMode.Darken
else -> unreachable()
}
}
ReaderContentOverlay(
brightness = { brightnessValue }.takeIf { brightness && brightnessValue < 0 },
color = { colorOverlay }.takeIf { colorOverlayEnabled },
colorBlendMode = colorOverlayMode,
)
if (brightness) {
LaunchedEffect(Unit) {
Settings.customBrightnessValue.valueFlow().sample(100)
.onCompletion { setCustomBrightnessValue(0) }
.collect { setCustomBrightnessValue(it) }
}
}
val showPageNumber by Settings.showPageNumber.collectAsState()
if (showPageNumber && !appbarVisible) {
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodySmall) {
PageIndicatorText(
currentPage = syncState.sliderValue,
totalPages = pageLoader.size,
modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(),
)
}
}
ReaderAppBars(
visible = appbarVisible,
isRtl = readingMode == ReadingModeType.RIGHT_TO_LEFT,
showSeekBar = showSeekbar,
currentPage = syncState.sliderValue,
totalPages = pageLoader.size,
onSliderValueChange = { syncState.sliderScrollTo(it + 1) },
onClickSettings = {
}
VolumeKeysHandler(
enabled = { volumeKeysEnabled && !appbarVisible },
movePrevious = { syncState.sliderScrollTo(syncState.sliderValue - 1) },
moveNext = { syncState.sliderScrollTo(syncState.sliderValue + 1) },
)
GalleryPager(
type = readingMode,
pagerState = pagerState,
lazyListState = lazyListState,
pageLoader = pageLoader,
onSelectPage = { page ->
if (Settings.readerLongTapAction.value) {
launch {
dialog { cont ->
fun dispose() = cont.resume(Unit)
var isColorFilter by remember { mutableStateOf(false) }
val scrim by animateColorAsState(
targetValue = if (isColorFilter) Color.Transparent else BottomSheetDefaults.ScrimColor,
label = "ScrimColor",
)
val state = rememberModalBottomSheetState()
ModalBottomSheet(
onDismissRequest = { dispose() },
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)),
// Yeah, I know color state should not be read here, but we have to do it...
scrimColor = scrim,
dragHandle = null,
sheetState = state,
contentWindowInsets = { WindowInsets(0) },
) {
SettingsPager(modifier = Modifier.fillMaxSize()) { page ->
isColorFilter = page == 2
appbarVisible = !isColorFilter
}
ReaderPageSheetMeta(
retry = { pageLoader.retryPage(page.index) },
retryOrigin = { pageLoader.retryPage(page.index, true) },
share = { launchIO { with(pageLoader) { shareImage(page, info) } } },
copy = { launchIO { with(pageLoader) { copy(page) } } },
save = { launchIO { with(pageLoader) { save(page) } } },
saveTo = { launchIO { with(pageLoader) { saveTo(page) } } },
dismiss = { launch { state.hide().also { dispose() } } },
)
}
}
}
},
)
}
},
onMenuRegionClick = { appbarVisible = !appbarVisible },
modifier = Modifier.background(bgColor),
contentPadding = if (fullscreen) {
if (cutoutShort) {
PaddingValues(0.dp)
} else {
WindowInsets.displayCutout.asPaddingValues()
}
} else {
WindowInsets.systemBars.asPaddingValues()
},
)
val brightness by Settings.customBrightness.collectAsState()
val brightnessValue by Settings.customBrightnessValue.collectAsState()
val colorOverlayEnabled by Settings.colorFilter.collectAsState()
val colorOverlay by Settings.colorFilterValue.collectAsState()
val colorOverlayMode by Settings.colorFilterMode.collectAsState {
when (it) {
0 -> BlendMode.SrcOver
1 -> BlendMode.Multiply
2 -> BlendMode.Screen
3 -> BlendMode.Overlay
4 -> BlendMode.Lighten
5 -> BlendMode.Darken
else -> unreachable()
}
}
ReaderContentOverlay(
brightness = { brightnessValue }.takeIf { brightness && brightnessValue < 0 },
color = { colorOverlay }.takeIf { colorOverlayEnabled },
colorBlendMode = colorOverlayMode,
)
if (brightness) {
LaunchedEffect(Unit) {
Settings.customBrightnessValue.valueFlow().sample(100)
.onCompletion { setCustomBrightnessValue(0) }
.collect { setCustomBrightnessValue(it) }
}
}
val showPageNumber by Settings.showPageNumber.collectAsState()
if (showPageNumber && !appbarVisible) {
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodySmall) {
PageIndicatorText(
currentPage = syncState.sliderValue,
totalPages = pageLoader.size,
modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(),
)
}
}
ReaderAppBars(
visible = appbarVisible,
isRtl = readingMode == ReadingModeType.RIGHT_TO_LEFT,
showSeekBar = showSeekbar,
currentPage = syncState.sliderValue,
totalPages = pageLoader.size,
onSliderValueChange = { syncState.sliderScrollTo(it + 1) },
onClickSettings = {
launch {
dialog { cont ->
fun dispose() = cont.resume(Unit)
var isColorFilter by remember { mutableStateOf(false) }
val scrim by animateColorAsState(
targetValue = if (isColorFilter) Color.Transparent else BottomSheetDefaults.ScrimColor,
label = "ScrimColor",
)
ModalBottomSheet(
onDismissRequest = { dispose() },
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)),
// Yeah, I know color state should not be read here, but we have to do it...
scrimColor = scrim,
dragHandle = null,
contentWindowInsets = { WindowInsets(0) },
) {
SettingsPager(modifier = Modifier.fillMaxSize()) { page ->
isColorFilter = page == 2
appbarVisible = !isColorFilter
}
}
}
}
},
)
}
}
context(Context, DialogState, DestinationsNavigator)
private suspend fun preparePageLoader(args: ReaderScreenArgs) = when (args) {
is ReaderScreenArgs.Gallery -> {
val info = args.info
val page = args.page
val archive = DownloadManager.getDownloadInfo(info.gid)?.archiveFile
if (archive != null) {
ArchivePageLoader(archive, info.gid, page)
} else {
EhPageLoader(info, page)
}
}
is ReaderScreenArgs.Archive -> {
ArchivePageLoader(args.uri.toOkioPath()) { invalidator ->
awaitInputText(
title = getString(R.string.archive_need_passwd),
hint = getString(R.string.archive_passwd),
onUserDismiss = { popBackStack() },
) { text ->
ensure(text.isNotBlank()) { getString(R.string.passwd_cannot_be_empty) }
ensure(invalidator(text)) { getString(R.string.passwd_wrong) }
}
}
}
}
@@ -105,7 +105,7 @@ import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
import com.hippo.ehviewer.ui.main.plus
import com.hippo.ehviewer.ui.navToReader
import com.hippo.ehviewer.ui.showMoveDownloadLabelList
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.Await
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
import com.hippo.ehviewer.ui.tools.HapticFeedbackType
@@ -599,7 +599,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
}
}
Deferred({ delay(200) }) {
Await({ delay(200) }) {
if (list.isEmpty()) {
Column(
modifier = Modifier.padding(realPadding).fillMaxSize(),
@@ -124,7 +124,7 @@ import com.hippo.ehviewer.ui.main.GalleryInfoListItem
import com.hippo.ehviewer.ui.main.GalleryList
import com.hippo.ehviewer.ui.main.ImageSearch
import com.hippo.ehviewer.ui.main.SearchFilter
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.Await
import com.hippo.ehviewer.ui.tools.HapticFeedbackType
import com.hippo.ehviewer.ui.tools.animateFloatMergePredictiveBackAsState
import com.hippo.ehviewer.ui.tools.delegateSnapshotUpdate
@@ -467,7 +467,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) = c
}
}
}
Deferred({ delay(200) }) {
Await({ delay(200) }) {
if (quickSearchList.isEmpty()) {
Text(
text = stringResource(id = R.string.quick_search_tip),
@@ -52,7 +52,7 @@ import com.hippo.ehviewer.ui.composing
import com.hippo.ehviewer.ui.doGalleryInfoAction
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
import com.hippo.ehviewer.ui.main.plus
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.Await
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.tools.thenIf
@@ -164,7 +164,7 @@ fun HistoryScreen(navigator: DestinationsNavigator) = composing(navigator) {
}
}
}
Deferred(keyword, { delay(200) }) {
Await(keyword, { delay(200) }) {
if (historyData.itemCount == 0) {
Column(
modifier = Modifier.padding(paddingValues).padding(horizontal = marginH).fillMaxSize(),
@@ -58,7 +58,7 @@ import com.hippo.ehviewer.client.EhFilter.trigger
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.dao.Filter
import com.hippo.ehviewer.dao.FilterMode
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.Await
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.thenIf
import com.ramcosta.composedestinations.annotation.Destination
@@ -213,7 +213,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
}
},
) { paddingValues ->
Deferred({ allFilterMap.await() }) { filters ->
Await({ allFilterMap.await() }) { filters ->
LazyColumn(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
contentPadding = paddingValues,
@@ -0,0 +1,29 @@
package com.hippo.ehviewer.ui.tools
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import arrow.core.None
import arrow.core.Option
import arrow.core.some
@Composable
fun <T> Await(block: suspend () -> T, content: @Composable (T) -> Unit) {
var completed by remember(block) { mutableStateOf<Option<T>>(None) }
LaunchedEffect(block) {
completed = block().some()
}
completed.onSome { content(it) }
}
@Composable
fun <T> Await(key: Any?, block: suspend () -> T, content: @Composable (T) -> Unit) {
var completed by remember(key, block) { mutableStateOf<Option<T>>(None) }
LaunchedEffect(key, block) {
completed = block().some()
}
completed.onSome { content(it) }
}
@@ -1,26 +0,0 @@
package com.hippo.ehviewer.ui.tools
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@Composable
fun <T> Deferred(block: suspend () -> T, content: @Composable (T) -> Unit) {
var completed by remember(block) { mutableStateOf<T?>(null) }
LaunchedEffect(block) {
completed = block()
}
completed?.let { content(it) }
}
@Composable
fun <T> Deferred(key: Any?, block: suspend () -> T, content: @Composable (T) -> Unit) {
var completed by remember(key, block) { mutableStateOf<T?>(null) }
LaunchedEffect(key, block) {
completed = block()
}
completed?.let { content(it) }
}
@@ -156,13 +156,17 @@ class DialogState {
hint: String? = null,
isNumber: Boolean = false,
@StringRes confirmText: Int = android.R.string.ok,
onUserDismiss: (() -> Unit)? = null,
invalidator: (suspend Raise<String>.(String) -> Unit)? = null,
) = dialog { cont ->
val coroutineScope = rememberCoroutineScope()
var state by remember(cont) { mutableStateOf(initial) }
var error by remember(cont) { mutableStateOf<String?>(null) }
AlertDialog(
onDismissRequest = { cont.cancel() },
onDismissRequest = {
cont.cancel()
onUserDismiss?.invoke()
},
confirmButton = {
TextButton(onClick = {
if (invalidator == null) {
@@ -12,7 +12,6 @@ private const val MAX_CACHE_SIZE = 512 * 1024 * 1024
private const val MIN_CACHE_SIZE = 128 * 1024 * 1024
abstract class PageLoader {
private val cache by lazy {
lruCache<Int, Image>(
maxSize = if (isAtLeastO) {
@@ -27,6 +26,7 @@ abstract class PageLoader {
},
)
}
val pages by lazy {
check(size > 0)
(0 until size).map { ReaderPage(it) }
@@ -34,13 +34,16 @@ abstract class PageLoader {
private val preloads = com.hippo.ehviewer.Settings.preloadImage.coerceIn(0, 100)
abstract suspend fun awaitReady(): Boolean
@CallSuper
open suspend fun awaitReady(): Boolean {
cache
return true
}
abstract val isReady: Boolean
@CallSuper
open fun start() {
cache
}
abstract fun start()
@CallSuper
open fun stop() {