From de996759693271c664ed60e4c5002808d71b4229 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Mon, 16 Jan 2023 12:49:57 -0500 Subject: [PATCH] Infinite scrolling for continuous vertical and webtoon --- .../ca/gosyer/jui/ui/reader/ReaderMenu.kt | 45 +++++- .../jui/ui/reader/ReaderMenuViewModel.kt | 149 +++++++++++------- .../ui/reader/loader/TachideskPageLoader.kt | 2 +- .../jui/ui/reader/model/ReaderChapter.kt | 11 +- .../jui/ui/reader/model/ViewerChapters.kt | 41 +++-- .../gosyer/jui/ui/reader/viewer/Continuous.kt | 33 ++-- .../ca/gosyer/jui/ui/reader/viewer/Pager.kt | 19 ++- 7 files changed, 197 insertions(+), 103 deletions(-) diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt index ed7f61ff..bbdbb4e5 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenu.kt @@ -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>, 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)) diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenuViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenuViewModel.kt index 5051a942..680cd151 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenuViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/ReaderMenuViewModel.kt @@ -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(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.Wait) val state = _state.asStateFlow() - private val _pages = MutableStateFlow>(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(null) val currentPage = _currentPage.asStateFlow() @@ -148,6 +171,35 @@ class ReaderMenuViewModel @Inject constructor( } } + init { + scope.launchDefault { + currentPage + .filterIsInstance() + .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() - .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() + .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() - .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() } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/loader/TachideskPageLoader.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/loader/TachideskPageLoader.kt index e5bc4559..6816b9d8 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/loader/TachideskPageLoader.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/loader/TachideskPageLoader.kt @@ -161,7 +161,7 @@ class TachideskPageLoader( */ private fun preloadNextPages(currentPage: ReaderPage, amount: Int): List { 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 diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ReaderChapter.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ReaderChapter.kt index b9cff2d3..7428420e 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ReaderChapter.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ReaderChapter.kt @@ -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? - get() = (state as? State.Loaded)?.pages + val pages: StateFlow = _state.filterIsInstance() + .flatMapLatest { it.pages } + .stateIn(scope, SharingStarted.Eagerly, PagesState.Loading) var pageLoader: PageLoader? = null diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ViewerChapters.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ViewerChapters.kt index 4ea29f7e..6d2ffab6 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ViewerChapters.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/model/ViewerChapters.kt @@ -6,33 +6,32 @@ package ca.gosyer.jui.ui.reader.model -import kotlinx.coroutines.flow.MutableStateFlow - data class ViewerChapters( - val currChapter: MutableStateFlow, - val prevChapter: MutableStateFlow, - val nextChapter: MutableStateFlow + 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 + ) } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Continuous.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Continuous.kt index 45fd0a3b..98560019 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Continuous.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Continuous.kt @@ -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>, 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 + ) } } } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Pager.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Pager.kt index 6cf4ab22..4225439d 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Pager.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/reader/viewer/Pager.kt @@ -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>, 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 + ) } }