mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2026-01-30 15:34:07 +01:00
Infinite scrolling for continuous vertical and webtoon
This commit is contained in:
@@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.ModalBottomSheetLayout
|
||||
@@ -188,6 +189,7 @@ fun ReaderMenu(
|
||||
retry = vm::retry,
|
||||
progress = vm::progress,
|
||||
updateLastPageReadOffset = vm::updateLastPageReadOffset,
|
||||
requestPreloadChapter = vm::requestPreloadChapter,
|
||||
sideMenuOpen = readerSettingsMenuOpen,
|
||||
setSideMenuOpen = vm::setReaderSettingsMenuOpen,
|
||||
setMangaReaderMode = vm::setMangaReaderMode,
|
||||
@@ -217,6 +219,7 @@ fun ReaderMenu(
|
||||
retry = vm::retry,
|
||||
progress = vm::progress,
|
||||
updateLastPageReadOffset = vm::updateLastPageReadOffset,
|
||||
requestPreloadChapter = vm::requestPreloadChapter,
|
||||
readerMenuOpen = readerSettingsMenuOpen,
|
||||
setMangaReaderMode = vm::setMangaReaderMode,
|
||||
movePrevChapter = vm::prevChapter,
|
||||
@@ -266,6 +269,7 @@ fun WideReaderMenu(
|
||||
retry: (ReaderPage) -> Unit,
|
||||
progress: (ReaderItem) -> Unit,
|
||||
updateLastPageReadOffset: (Int) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit,
|
||||
sideMenuOpen: Boolean,
|
||||
setSideMenuOpen: (Boolean) -> Unit,
|
||||
setMangaReaderMode: (String) -> Unit,
|
||||
@@ -319,7 +323,8 @@ fun WideReaderMenu(
|
||||
pageEmitterHolder = pageEmitterHolder,
|
||||
retry = retry,
|
||||
progress = progress,
|
||||
updateLastPageReadOffset = updateLastPageReadOffset
|
||||
updateLastPageReadOffset = updateLastPageReadOffset,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
SideMenuButton(sideMenuOpen, onOpenSideMenuClicked = { setSideMenuOpen(true) })
|
||||
}
|
||||
@@ -348,6 +353,7 @@ fun ThinReaderMenu(
|
||||
retry: (ReaderPage) -> Unit,
|
||||
progress: (ReaderItem) -> Unit,
|
||||
updateLastPageReadOffset: (Int) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit,
|
||||
readerMenuOpen: Boolean,
|
||||
setMangaReaderMode: (String) -> Unit,
|
||||
movePrevChapter: () -> Unit,
|
||||
@@ -389,7 +395,8 @@ fun ThinReaderMenu(
|
||||
pageEmitterHolder = pageEmitterHolder,
|
||||
retry = retry,
|
||||
progress = progress,
|
||||
updateLastPageReadOffset = updateLastPageReadOffset
|
||||
updateLastPageReadOffset = updateLastPageReadOffset,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
AnimatedVisibility(
|
||||
readerMenuOpen,
|
||||
@@ -449,7 +456,8 @@ fun ReaderLayout(
|
||||
pageEmitterHolder: StableHolder<SharedFlow<PageMove>>,
|
||||
retry: (ReaderPage) -> Unit,
|
||||
progress: (ReaderItem) -> Unit,
|
||||
updateLastPageReadOffset: (Int) -> Unit
|
||||
updateLastPageReadOffset: (Int) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit
|
||||
) {
|
||||
val loadingModifier = Modifier.widthIn(max = 700.dp)
|
||||
.fillMaxWidth()
|
||||
@@ -491,7 +499,8 @@ fun ReaderLayout(
|
||||
pageEmitterHolder = pageEmitterHolder,
|
||||
retry = retry,
|
||||
progress = progress,
|
||||
updateLastPageReadOffset = updateLastPageReadOffset
|
||||
updateLastPageReadOffset = updateLastPageReadOffset,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
} else {
|
||||
PagerReader(
|
||||
@@ -503,7 +512,8 @@ fun ReaderLayout(
|
||||
pageContentScale = imageScale.toContentScale(),
|
||||
pageEmitterHolder = pageEmitterHolder,
|
||||
retry = retry,
|
||||
progress = progress
|
||||
progress = progress,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -577,18 +587,39 @@ fun ReaderImage(
|
||||
@Composable
|
||||
fun ChapterSeparator(
|
||||
previousChapter: ReaderChapter?,
|
||||
nextChapter: ReaderChapter?
|
||||
nextChapter: ReaderChapter?,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit
|
||||
) {
|
||||
Box(Modifier.fillMaxWidth().height(350.dp), contentAlignment = Alignment.Center) {
|
||||
Column {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
when {
|
||||
previousChapter == null && nextChapter != null -> {
|
||||
Text(stringResource(MR.strings.no_previous_chapter))
|
||||
}
|
||||
previousChapter != null && nextChapter != null -> {
|
||||
val prevChapter by previousChapter.stateObserver.collectAsState()
|
||||
when (prevChapter) {
|
||||
ReaderChapter.State.Loading, ReaderChapter.State.Wait -> {
|
||||
LaunchedEffect(Unit) {
|
||||
requestPreloadChapter(previousChapter)
|
||||
}
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
Text(stringResource(MR.strings.previous_chapter, previousChapter.chapter.name))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(stringResource(MR.strings.next_chapter, nextChapter.chapter.name))
|
||||
val nexChapter by previousChapter.stateObserver.collectAsState()
|
||||
when (nexChapter) {
|
||||
ReaderChapter.State.Loading, ReaderChapter.State.Wait -> {
|
||||
LaunchedEffect(Unit) {
|
||||
requestPreloadChapter(nextChapter)
|
||||
}
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
previousChapter != null && nextChapter == null -> {
|
||||
Text(stringResource(MR.strings.no_next_chapter))
|
||||
|
||||
@@ -37,7 +37,6 @@ import ca.gosyer.jui.ui.reader.model.ViewerChapters
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||
import io.ktor.http.decodeURLQueryComponent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@@ -53,15 +52,18 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.singleOrNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
import org.lighthousegames.logging.logging
|
||||
@@ -82,20 +84,41 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
) : ViewModel(contextWrapper) {
|
||||
override val scope = MainScope()
|
||||
private val _manga = MutableStateFlow<Manga?>(null)
|
||||
private val viewerChapters = ViewerChapters(
|
||||
MutableStateFlow(null),
|
||||
MutableStateFlow(null),
|
||||
MutableStateFlow(null)
|
||||
)
|
||||
val previousChapter = viewerChapters.prevChapter.asStateFlow()
|
||||
val chapter = viewerChapters.currChapter.asStateFlow()
|
||||
val nextChapter = viewerChapters.nextChapter.asStateFlow()
|
||||
private val viewerChapters = MutableStateFlow(ViewerChapters(null, null, null))
|
||||
val previousChapter = viewerChapters.map { it.prevChapter }.stateIn(scope, SharingStarted.Eagerly, null)
|
||||
val chapter = viewerChapters.map { it.currChapter }.stateIn(scope, SharingStarted.Eagerly, null)
|
||||
val nextChapter = viewerChapters.map { it.nextChapter }.stateIn(scope, SharingStarted.Eagerly, null)
|
||||
|
||||
private val _state = MutableStateFlow<ReaderChapter.State>(ReaderChapter.State.Wait)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
private val _pages = MutableStateFlow<ImmutableList<ReaderItem>>(persistentListOf())
|
||||
val pages = _pages.asStateFlow()
|
||||
val pages = viewerChapters.flatMapLatest {
|
||||
val previousChapterPages = it.prevChapter
|
||||
?.pages
|
||||
?.map { (it as? PagesState.Success)?.pages }
|
||||
?: flowOf(null)
|
||||
val chapterPages = it.currChapter
|
||||
?.pages
|
||||
?.map { (it as? PagesState.Success)?.pages }
|
||||
?: flowOf(null)
|
||||
val nextChapterPages = it.nextChapter
|
||||
?.pages
|
||||
?.map { (it as? PagesState.Success)?.pages }
|
||||
?: flowOf(null)
|
||||
combine(previousChapterPages, chapterPages, nextChapterPages, readerModeSettings.continuous) { prev, cur, next, cont ->
|
||||
if (cont) {
|
||||
(prev.orEmpty() +
|
||||
ReaderPageSeparator(it.prevChapter, it.currChapter) +
|
||||
cur.orEmpty() +
|
||||
ReaderPageSeparator(it.currChapter, it.nextChapter) +
|
||||
next.orEmpty()).toImmutableList()
|
||||
} else {
|
||||
(listOf(ReaderPageSeparator(it.prevChapter, it.currChapter)) +
|
||||
cur.orEmpty() +
|
||||
ReaderPageSeparator(it.currChapter, it.nextChapter)).toImmutableList()
|
||||
}
|
||||
}
|
||||
}.stateIn(scope, SharingStarted.Eagerly, persistentListOf())
|
||||
|
||||
private val _currentPage = MutableStateFlow<ReaderItem?>(null)
|
||||
val currentPage = _currentPage.asStateFlow()
|
||||
@@ -148,6 +171,35 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
scope.launchDefault {
|
||||
currentPage
|
||||
.filterIsInstance<ReaderPage>()
|
||||
.collectLatest { page ->
|
||||
page.chapter.pageLoader?.loadPage(page)
|
||||
if (page.chapter == chapter.value) {
|
||||
if ((page.index + 1) >= page.chapter.chapter.pageCount!!) {
|
||||
markChapterRead(page.chapter)
|
||||
}
|
||||
val nextChapter = nextChapter.value
|
||||
if (nextChapter != null && (page.index + 1) >= (page.chapter.chapter.pageCount!! - 5)) {
|
||||
requestPreloadChapter(nextChapter)
|
||||
}
|
||||
} else {
|
||||
val previousChapter = previousChapter.value
|
||||
val nextChapter = nextChapter.value
|
||||
if (page.chapter == previousChapter) {
|
||||
viewerChapters.value = viewerChapters.value.movePrev()
|
||||
initChapters(params.mangaId, page.chapter.chapter.index, fromMenuButton = false)
|
||||
} else if (page.chapter == nextChapter) {
|
||||
viewerChapters.value = viewerChapters.value.moveNext()
|
||||
initChapters(params.mangaId, page.chapter.chapter.index, fromMenuButton = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun navigate(navigationRegion: Navigation): Boolean {
|
||||
scope.launch {
|
||||
val moveTo = when (navigationRegion) {
|
||||
@@ -198,12 +250,6 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
chapter.value?.pageLoader?.retryPage(page)
|
||||
}
|
||||
|
||||
private fun resetValues() {
|
||||
viewerChapters.recycle()
|
||||
_pages.value = persistentListOf()
|
||||
_currentPage.value = null
|
||||
}
|
||||
|
||||
fun setMangaReaderMode(mode: String) {
|
||||
scope.launchDefault {
|
||||
_manga.value?.let {
|
||||
@@ -223,7 +269,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
try {
|
||||
_state.value = ReaderChapter.State.Wait
|
||||
sendProgress()
|
||||
viewerChapters.movePrev()
|
||||
viewerChapters.value = viewerChapters.value.movePrev()
|
||||
initChapters(params.mangaId, prevChapter.chapter.index, fromMenuButton = true)
|
||||
} catch (e: Exception) {
|
||||
log.warn(e) { "Error loading prev chapter" }
|
||||
@@ -237,7 +283,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
try {
|
||||
_state.value = ReaderChapter.State.Wait
|
||||
sendProgress()
|
||||
viewerChapters.moveNext()
|
||||
viewerChapters.value = viewerChapters.value.moveNext()
|
||||
initChapters(params.mangaId, nextChapter.chapter.index, fromMenuButton = true)
|
||||
} catch (e: Exception) {
|
||||
log.warn(e) { "Error loading next chapter" }
|
||||
@@ -263,12 +309,12 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
chapterIndex: Int,
|
||||
fromMenuButton: Boolean = true
|
||||
) {
|
||||
// resetValues()
|
||||
log.debug { "Loading chapter index $chapterIndex" }
|
||||
val (chapter, pages) = coroutineScope {
|
||||
val getCurrentChapter = async {
|
||||
val chapter = getReaderChapter(chapterIndex) ?: return@async null
|
||||
val pages = loader.loadChapter(chapter)
|
||||
viewerChapters.currChapter.value = chapter
|
||||
viewerChapters.update { it.copy(currChapter = chapter) }
|
||||
chapter to pages
|
||||
}
|
||||
|
||||
@@ -279,22 +325,24 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
).orEmpty()
|
||||
|
||||
val nextChapter = async {
|
||||
if (viewerChapters.nextChapter.value == null) {
|
||||
if (viewerChapters.value.nextChapter == null) {
|
||||
val nextChapter = chapters.find { it.index == chapterIndex + 1 }
|
||||
if (nextChapter != null) {
|
||||
viewerChapters.nextChapter.value = getReaderChapter(nextChapter.index)
|
||||
val nextReaderChapter = getReaderChapter(nextChapter.index)
|
||||
viewerChapters.update { it.copy(nextChapter = nextReaderChapter) }
|
||||
} else {
|
||||
viewerChapters.nextChapter.value = null
|
||||
viewerChapters.update { it.copy(nextChapter = null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
val prevChapter = async {
|
||||
if (viewerChapters.prevChapter.value == null) {
|
||||
if (viewerChapters.value.prevChapter == null) {
|
||||
val prevChapter = chapters.find { it.index == chapterIndex - 1 }
|
||||
if (prevChapter != null) {
|
||||
viewerChapters.prevChapter.value = getReaderChapter(prevChapter.index)
|
||||
val prevReaderChapter = getReaderChapter(prevChapter.index)
|
||||
viewerChapters.update { it.copy(prevChapter = prevReaderChapter) }
|
||||
} else {
|
||||
viewerChapters.prevChapter.value = null
|
||||
viewerChapters.update { it.copy(prevChapter = null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,19 +354,16 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
getCurrentChapter.await()
|
||||
} ?: return
|
||||
|
||||
chapter.stateObserver
|
||||
.onEach {
|
||||
_state.value = it
|
||||
}
|
||||
.launchIn(chapter.scope)
|
||||
pages
|
||||
.filterIsInstance<PagesState.Success>()
|
||||
.onEach { (pageList) ->
|
||||
val prevSeparator = ReaderPageSeparator(viewerChapters.prevChapter.value, chapter)
|
||||
val nextSeparator = ReaderPageSeparator(chapter, viewerChapters.nextChapter.value)
|
||||
_pages.value = (listOf(prevSeparator) + pageList + nextSeparator).toImmutableList()
|
||||
if (fromMenuButton) {
|
||||
chapter.stateObserver
|
||||
.onEach {
|
||||
_state.value = it
|
||||
}
|
||||
.launchIn(chapter.scope)
|
||||
|
||||
if (fromMenuButton) {
|
||||
pages
|
||||
.filterIsInstance<PagesState.Success>()
|
||||
.onEach { (pageList) ->
|
||||
val lastPageReadOffset = chapter.chapter.meta.juiPageOffset
|
||||
if (lastPageReadOffset != 0) {
|
||||
_currentPageOffset.value = lastPageReadOffset
|
||||
@@ -330,19 +375,9 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
pageList.first()
|
||||
}.also { chapter.pageLoader?.loadPage(it) }
|
||||
}
|
||||
}
|
||||
.launchIn(chapter.scope)
|
||||
.launchIn(chapter.scope)
|
||||
}
|
||||
|
||||
_currentPage
|
||||
.filterIsInstance<ReaderPage>()
|
||||
.filter { it.chapter.chapter.index == chapterIndex }
|
||||
.onEach { page ->
|
||||
chapter.pageLoader?.loadPage(page)
|
||||
if ((page.index + 1) >= chapter.chapter.pageCount!!) {
|
||||
markChapterRead(chapter)
|
||||
}
|
||||
}
|
||||
.launchIn(chapter.scope)
|
||||
}
|
||||
|
||||
private suspend fun getReaderChapter(chapterIndex: Int): ReaderChapter? {
|
||||
@@ -357,6 +392,14 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
fun requestPreloadChapter(chapter: ReaderChapter) {
|
||||
if (chapter.state != ReaderChapter.State.Wait && chapter.state !is ReaderChapter.State.Error) {
|
||||
return
|
||||
}
|
||||
log.debug { "Preloading ${chapter.chapter.index}" }
|
||||
loader.loadChapter(chapter)
|
||||
}
|
||||
|
||||
private fun markChapterRead(chapter: ReaderChapter) {
|
||||
scope.launch {
|
||||
updateChapterRead.await(chapter.chapter, read = true, onError = { toast(it.message.orEmpty()) })
|
||||
@@ -391,7 +434,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onDispose() {
|
||||
viewerChapters.recycle()
|
||||
viewerChapters.value.recycle()
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class TachideskPageLoader(
|
||||
*/
|
||||
private fun preloadNextPages(currentPage: ReaderPage, amount: Int): List<PriorityPage> {
|
||||
val pageIndex = currentPage.index
|
||||
val pages = (currentPage.chapter.pages?.value as? PagesState.Success)?.pages ?: return emptyList()
|
||||
val pages = (currentPage.chapter.pages.value as? PagesState.Success)?.pages ?: return emptyList()
|
||||
if (pageIndex >= pages.lastIndex) return emptyList()
|
||||
|
||||
return pages
|
||||
|
||||
@@ -15,8 +15,12 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.lighthousegames.logging.logging
|
||||
|
||||
@Immutable
|
||||
@@ -31,10 +35,11 @@ data class ReaderChapter(val chapter: Chapter) {
|
||||
_state.value = value
|
||||
}
|
||||
|
||||
val stateObserver by lazy { _state.asStateFlow() }
|
||||
val stateObserver = _state.asStateFlow()
|
||||
|
||||
val pages: StateFlow<PagesState>?
|
||||
get() = (state as? State.Loaded)?.pages
|
||||
val pages: StateFlow<PagesState> = _state.filterIsInstance<State.Loaded>()
|
||||
.flatMapLatest { it.pages }
|
||||
.stateIn(scope, SharingStarted.Eagerly, PagesState.Loading)
|
||||
|
||||
var pageLoader: PageLoader? = null
|
||||
|
||||
|
||||
@@ -6,33 +6,32 @@
|
||||
|
||||
package ca.gosyer.jui.ui.reader.model
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
data class ViewerChapters(
|
||||
val currChapter: MutableStateFlow<ReaderChapter?>,
|
||||
val prevChapter: MutableStateFlow<ReaderChapter?>,
|
||||
val nextChapter: MutableStateFlow<ReaderChapter?>
|
||||
val currChapter: ReaderChapter?,
|
||||
val prevChapter: ReaderChapter?,
|
||||
val nextChapter: ReaderChapter?
|
||||
) {
|
||||
fun recycle() {
|
||||
currChapter.value?.recycle()
|
||||
prevChapter.value?.recycle()
|
||||
nextChapter.value?.recycle()
|
||||
currChapter.value = null
|
||||
prevChapter.value = null
|
||||
nextChapter.value = null
|
||||
currChapter?.recycle()
|
||||
prevChapter?.recycle()
|
||||
nextChapter?.recycle()
|
||||
}
|
||||
|
||||
fun movePrev() {
|
||||
nextChapter.value?.recycle()
|
||||
nextChapter.value = currChapter.value
|
||||
currChapter.value = prevChapter.value
|
||||
prevChapter.value = null
|
||||
fun movePrev(): ViewerChapters {
|
||||
nextChapter?.recycle()
|
||||
return ViewerChapters(
|
||||
nextChapter = currChapter,
|
||||
currChapter = prevChapter,
|
||||
prevChapter = null
|
||||
)
|
||||
}
|
||||
|
||||
fun moveNext() {
|
||||
prevChapter.value?.recycle()
|
||||
prevChapter.value = currChapter.value
|
||||
currChapter.value = nextChapter.value
|
||||
nextChapter.value = null
|
||||
fun moveNext(): ViewerChapters {
|
||||
prevChapter?.recycle()
|
||||
return ViewerChapters(
|
||||
prevChapter = currChapter,
|
||||
currChapter = nextChapter,
|
||||
nextChapter = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
@@ -37,6 +36,7 @@ import ca.gosyer.jui.ui.reader.ChapterSeparator
|
||||
import ca.gosyer.jui.ui.reader.ReaderImage
|
||||
import ca.gosyer.jui.ui.reader.model.MoveTo
|
||||
import ca.gosyer.jui.ui.reader.model.PageMove
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderChapter
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderItem
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderPage
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderPageSeparator
|
||||
@@ -46,10 +46,8 @@ import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
||||
import ca.gosyer.jui.uicore.components.scrollbarPadding
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
@Composable
|
||||
fun ContinuousReader(
|
||||
@@ -65,7 +63,8 @@ fun ContinuousReader(
|
||||
pageEmitterHolder: StableHolder<SharedFlow<PageMove>>,
|
||||
retry: (ReaderPage) -> Unit,
|
||||
progress: (ReaderItem) -> Unit,
|
||||
updateLastPageReadOffset: (Int) -> Unit
|
||||
updateLastPageReadOffset: (Int) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit
|
||||
) {
|
||||
BoxWithConstraints(modifier then Modifier.fillMaxSize()) {
|
||||
val state = rememberLazyListState(pages.indexOf(currentPage).coerceAtLeast(1), currentPageOffset)
|
||||
@@ -102,11 +101,12 @@ fun ContinuousReader(
|
||||
updateLastPageReadOffset(state.firstVisibleItemScrollOffset)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(state) {
|
||||
snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull()?.index }
|
||||
.filterNotNull()
|
||||
.mapNotNull { pages.getOrNull(it) }
|
||||
.collect(progress)
|
||||
LaunchedEffect(state.layoutInfo.visibleItemsInfo.lastOrNull()?.index) {
|
||||
val index = state.layoutInfo.visibleItemsInfo.lastOrNull()?.index
|
||||
val page = index?.let { pages.getOrNull(it) }
|
||||
if (page != null) {
|
||||
progress(page)
|
||||
}
|
||||
}
|
||||
|
||||
val imageModifier = if (maxSize != 0) {
|
||||
@@ -140,7 +140,8 @@ fun ContinuousReader(
|
||||
imageModifier = imageModifier,
|
||||
loadingModifier = loadingModifier,
|
||||
pageContentScale = pageContentScale,
|
||||
retry = ::retry
|
||||
retry = ::retry,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
VerticalScrollbar(
|
||||
@@ -165,7 +166,8 @@ fun ContinuousReader(
|
||||
imageModifier = imageModifier,
|
||||
loadingModifier = loadingModifier,
|
||||
pageContentScale = pageContentScale,
|
||||
retry = ::retry
|
||||
retry = ::retry,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
HorizontalScrollbar(
|
||||
@@ -185,7 +187,8 @@ private fun LazyListScope.items(
|
||||
imageModifier: Modifier,
|
||||
loadingModifier: Modifier,
|
||||
pageContentScale: ContentScale,
|
||||
retry: (Int) -> Unit
|
||||
retry: (Int) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit
|
||||
) {
|
||||
items(
|
||||
pages,
|
||||
@@ -211,7 +214,11 @@ private fun LazyListScope.items(
|
||||
retry = retry
|
||||
)
|
||||
}
|
||||
is ReaderPageSeparator -> ChapterSeparator(previousChapter = image.previousChapter, nextChapter = image.nextChapter)
|
||||
is ReaderPageSeparator -> ChapterSeparator(
|
||||
previousChapter = image.previousChapter,
|
||||
nextChapter = image.nextChapter,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import ca.gosyer.jui.ui.reader.ChapterSeparator
|
||||
import ca.gosyer.jui.ui.reader.ReaderImage
|
||||
import ca.gosyer.jui.ui.reader.model.MoveTo
|
||||
import ca.gosyer.jui.ui.reader.model.PageMove
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderChapter
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderItem
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderPage
|
||||
import ca.gosyer.jui.ui.reader.model.ReaderPageSeparator
|
||||
@@ -40,7 +41,8 @@ fun PagerReader(
|
||||
pageContentScale: ContentScale,
|
||||
pageEmitterHolder: StableHolder<SharedFlow<PageMove>>,
|
||||
retry: (ReaderPage) -> Unit,
|
||||
progress: (ReaderItem) -> Unit
|
||||
progress: (ReaderItem) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit
|
||||
) {
|
||||
val state = rememberPagerState(initialPage = pages.indexOf(currentPage).coerceAtLeast(1))
|
||||
val currentPageState = rememberUpdatedState(currentPage)
|
||||
@@ -93,7 +95,8 @@ fun PagerReader(
|
||||
page = it,
|
||||
loadingModifier = loadingModifier,
|
||||
pageContentScale = pageContentScale,
|
||||
retry = ::retry
|
||||
retry = ::retry,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -108,7 +111,8 @@ fun PagerReader(
|
||||
page = it,
|
||||
loadingModifier = loadingModifier,
|
||||
pageContentScale = pageContentScale,
|
||||
retry = ::retry
|
||||
retry = ::retry,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -120,7 +124,8 @@ fun HandlePager(
|
||||
page: Int,
|
||||
loadingModifier: Modifier,
|
||||
pageContentScale: ContentScale,
|
||||
retry: (Int) -> Unit
|
||||
retry: (Int) -> Unit,
|
||||
requestPreloadChapter: (ReaderChapter) -> Unit
|
||||
) {
|
||||
when (val image = pages[page]) {
|
||||
is ReaderPage -> {
|
||||
@@ -136,6 +141,10 @@ fun HandlePager(
|
||||
contentScale = pageContentScale
|
||||
)
|
||||
}
|
||||
is ReaderPageSeparator -> ChapterSeparator(image.previousChapter, image.nextChapter)
|
||||
is ReaderPageSeparator -> ChapterSeparator(
|
||||
previousChapter = image.previousChapter,
|
||||
nextChapter = image.nextChapter,
|
||||
requestPreloadChapter = requestPreloadChapter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user