diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index cfac2ffc..7395b9f6 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -3,9 +3,9 @@ import org.gradle.api.JavaVersion object Config { const val tachideskVersion = "v0.5.4" // Match this to the Tachidesk-Server commit count - const val serverCode = 1031 + const val serverCode = 1043 const val preview = true - const val previewCommit = "420d14fc37a18269a9d7232519e3f9a21c6302a2" + const val previewCommit = "5e47b7ae6b37931ce3a8eee33cafb9475d7a77bb" val jvmTarget = JavaVersion.VERSION_15 } \ No newline at end of file diff --git a/src/main/kotlin/ca/gosyer/data/models/Updates.kt b/src/main/kotlin/ca/gosyer/data/models/Updates.kt index e69de29b..caaed15f 100644 --- a/src/main/kotlin/ca/gosyer/data/models/Updates.kt +++ b/src/main/kotlin/ca/gosyer/data/models/Updates.kt @@ -0,0 +1,15 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.data.models + +import kotlinx.serialization.Serializable + +@Serializable +data class Updates( + val page: List, + val hasNextPage: Boolean +) diff --git a/src/main/kotlin/ca/gosyer/data/server/interactions/UpdatesInteractionHandler.kt b/src/main/kotlin/ca/gosyer/data/server/interactions/UpdatesInteractionHandler.kt index 30dc9e75..3a1eaf4b 100644 --- a/src/main/kotlin/ca/gosyer/data/server/interactions/UpdatesInteractionHandler.kt +++ b/src/main/kotlin/ca/gosyer/data/server/interactions/UpdatesInteractionHandler.kt @@ -6,7 +6,7 @@ package ca.gosyer.data.server.interactions -import ca.gosyer.data.models.MangaAndChapter +import ca.gosyer.data.models.Updates import ca.gosyer.data.server.Http import ca.gosyer.data.server.ServerPreferences import ca.gosyer.data.server.requests.recentUpdatesQuery @@ -19,9 +19,9 @@ class UpdatesInteractionHandler @Inject constructor( serverPreferences: ServerPreferences ) : BaseInteractionHandler(client, serverPreferences) { - suspend fun getRecentUpdates() = withIOContext { - client.get>( - serverUrl + recentUpdatesQuery() + suspend fun getRecentUpdates(pageNum: Int) = withIOContext { + client.get( + serverUrl + recentUpdatesQuery(pageNum) ) } } diff --git a/src/main/kotlin/ca/gosyer/data/server/requests/Updates.kt b/src/main/kotlin/ca/gosyer/data/server/requests/Updates.kt index f0ae0d02..11cef9b4 100644 --- a/src/main/kotlin/ca/gosyer/data/server/requests/Updates.kt +++ b/src/main/kotlin/ca/gosyer/data/server/requests/Updates.kt @@ -7,5 +7,5 @@ package ca.gosyer.data.server.requests @Get -fun recentUpdatesQuery() = - "/api/v1/update/recentChapters" +fun recentUpdatesQuery(pageNum: Int) = + "/api/v1/update/recentChapters/$pageNum" diff --git a/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenu.kt b/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenu.kt index 5bdea255..4cd39aca 100644 --- a/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenu.kt +++ b/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenu.kt @@ -14,9 +14,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -54,11 +55,16 @@ fun UpdatesMenu( LoadingScreen(isLoading) } else { LazyColumn { - items(updates) { - val manga = it.manga!! - val chapter = it.chapter + itemsIndexed(updates) { index, item -> + LaunchedEffect(Unit) { + if (index == updates.lastIndex) { + vm.loadNextPage() + } + } + val manga = item.manga!! + val chapter = item.chapter UpdatesItem( - it, + item, onClickItem = { openChapter(chapter.index, chapter.mangaId) }, onClickCover = { openManga(manga.id) }, onClickDownload = vm::downloadChapter, diff --git a/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenuViewModel.kt b/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenuViewModel.kt index edfe1e88..e83af9a8 100644 --- a/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenuViewModel.kt +++ b/src/main/kotlin/ca/gosyer/ui/updates/UpdatesMenuViewModel.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex import javax.inject.Inject class UpdatesMenuViewModel @Inject constructor( @@ -35,26 +36,15 @@ class UpdatesMenuViewModel @Inject constructor( private val _updates = MutableStateFlow>(emptyList()) val updates = _updates.asStateFlow() + private val currentPage = MutableStateFlow(1) + private val hasNextPage = MutableStateFlow(false) + + private val updatesMutex = Mutex() + init { scope.launch { try { - val updates = updatesHandler.getRecentUpdates() - mangaIds = updates.map { it.manga.id }.toSet() - - _updates.value = updates.map { - ChapterDownloadItem( - it.manga, - it.chapter - ) - } - downloadService.registerWatches(mangaIds).merge() - .onEach { (mangaId, chapters) -> - _updates.value.filter { it.chapter.mangaId == mangaId } - .forEach { - it.updateFrom(chapters) - } - } - .launchIn(scope) + getUpdates(1) } catch (e: Exception) { e.throwIfCancellation() } finally { @@ -63,6 +53,42 @@ class UpdatesMenuViewModel @Inject constructor( } } + fun loadNextPage() { + scope.launch { + if (hasNextPage.value && updatesMutex.tryLock()) { + try { + getUpdates(currentPage.value++) + } catch (e: Exception) { + e.throwIfCancellation() + currentPage.value-- + } + updatesMutex.unlock() + } + } + } + + private suspend fun getUpdates(pageNum: Int) { + val updates = updatesHandler.getRecentUpdates(pageNum) + mangaIds = updates.page.map { it.manga.id }.toSet() + + _updates.value += updates.page.map { + ChapterDownloadItem( + it.manga, + it.chapter + ) + } + downloadService.registerWatches(mangaIds).merge() + .onEach { (mangaId, chapters) -> + _updates.value.filter { it.chapter.mangaId == mangaId } + .forEach { + it.updateFrom(chapters) + } + } + .launchIn(scope) + + hasNextPage.value = updates.hasNextPage + } + fun downloadChapter(chapter: Chapter) { scope.launch { chapterHandler.queueChapterDownload(chapter)