From 14417d647ed7f9615bac9bcb7162ed658bbfc431 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Fri, 6 May 2022 22:19:39 -0400 Subject: [PATCH] Improve updates menu --- .../ca/gosyer/jui/ui/updates/UpdatesScreen.kt | 4 +- .../jui/ui/updates/UpdatesScreenViewModel.kt | 84 +++++++++++++------ .../components/UpdatesScreenContent.kt | 45 ++++++---- 3 files changed, 87 insertions(+), 46 deletions(-) diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreen.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreen.kt index d6d0d7f6..76d741c3 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreen.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreen.kt @@ -29,10 +29,10 @@ class UpdatesScreen : Screen { val readerLauncher = rememberReaderLauncher() UpdatesScreenContent( isLoading = vm.isLoading.collectAsState().value, - updates = vm.updates.collectAsState().value, + dateWithUpdates = vm.updates.collectAsState().value, loadNextPage = vm::loadNextPage, openChapter = readerLauncher::launch, - openManga = { navigator push ca.gosyer.jui.ui.manga.MangaScreen(it) }, + openManga = { navigator push MangaScreen(it) }, downloadChapter = vm::downloadChapter, deleteDownloadedChapter = vm::deleteDownloadedChapter, stopDownloadingChapter = vm::stopDownloadingChapter diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt index d34d9591..b318f829 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/UpdatesScreenViewModel.kt @@ -6,6 +6,10 @@ package ca.gosyer.jui.ui.updates +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateList import ca.gosyer.jui.data.download.DownloadService import ca.gosyer.jui.data.models.Chapter import ca.gosyer.jui.data.server.interactions.ChapterInteractionHandler @@ -13,15 +17,22 @@ import ca.gosyer.jui.data.server.interactions.UpdatesInteractionHandler import ca.gosyer.jui.ui.base.chapter.ChapterDownloadItem import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import me.tatarka.inject.annotations.Inject import org.lighthousegames.logging.logging @@ -34,8 +45,9 @@ class UpdatesScreenViewModel @Inject constructor( private val _isLoading = MutableStateFlow(true) val isLoading = _isLoading.asStateFlow() - private val _updates = MutableStateFlow>(emptyList()) - val updates = _updates.asStateFlow() + private val _updates = mutableStateMapOf>() + val updates = snapshotFlow { _updates.toList().sortedByDescending { it.first } } + .stateIn(scope, SharingStarted.Eagerly, emptyList()) private val currentPage = MutableStateFlow(1) private val hasNextPage = MutableStateFlow(false) @@ -44,36 +56,50 @@ class UpdatesScreenViewModel @Inject constructor( private var downloadServiceJob: Job? = null init { - scope.launch { - getUpdates() + scope.launch(Dispatchers.Default) { + getUpdates(currentPage.value) } } fun loadNextPage() { - scope.launch { + scope.launch(Dispatchers.Default) { if (hasNextPage.value && updatesMutex.tryLock()) { - getUpdates() + currentPage.value++ + getUpdates(currentPage.value) updatesMutex.unlock() } } } - private suspend fun getUpdates() { - updatesHandler.getRecentUpdates(currentPage.value) + private suspend fun getUpdates(page: Int) { + updatesHandler.getRecentUpdates(page) .onEach { updates -> - _updates.value += updates.page.map { - ChapterDownloadItem( - it.manga, - it.chapter - ) - } + updates.page + .map { + ChapterDownloadItem( + it.manga, + it.chapter + ) + } + .groupBy { + Instant.fromEpochSeconds(it.chapter.fetchedAt).toLocalDateTime(TimeZone.currentSystemDefault()).date + }.forEach { (date, chapters) -> + val list = _updates.getOrPut(date, ::mutableStateListOf) + list += chapters + list.sortByDescending { it.chapter.fetchedAt } + } + downloadServiceJob?.cancel() - downloadServiceJob = DownloadService.registerWatches(updates.page.map { it.manga.id }.toSet()) + val mangaIds = _updates.values.flatMap { items -> + items.mapNotNull { it.manga?.id } + }.toSet() + downloadServiceJob = DownloadService.registerWatches(mangaIds) .onEach { chapters -> - _updates.value - .forEach { + _updates.forEach { (_, updates) -> + updates.forEach { it.updateFrom(chapters) } + } } .launchIn(scope) @@ -82,8 +108,8 @@ class UpdatesScreenViewModel @Inject constructor( } .catch { log.warn(it) { "Error getting updates" } - if (currentPage.value > 1) { - currentPage.value-- + if (page > 1) { + currentPage.value = page - 1 } _isLoading.value = false } @@ -99,10 +125,12 @@ class UpdatesScreenViewModel @Inject constructor( } fun deleteDownloadedChapter(chapter: Chapter) { - updates.value - .find { - it.chapter.mangaId == chapter.mangaId && - it.chapter.index == chapter.index + _updates + .firstNotNullOfOrNull { (_, chapters) -> + chapters.find { + it.chapter.mangaId == chapter.mangaId && + it.chapter.index == chapter.index + } } ?.deleteDownload(chapterHandler) ?.catch { @@ -112,10 +140,12 @@ class UpdatesScreenViewModel @Inject constructor( } fun stopDownloadingChapter(chapter: Chapter) { - updates.value - .find { - it.chapter.mangaId == chapter.mangaId && - it.chapter.index == chapter.index + _updates + .firstNotNullOfOrNull {(_, chapters) -> + chapters.find { + it.chapter.mangaId == chapter.mangaId && + it.chapter.index == chapter.index + } } ?.stopDownloading(chapterHandler) ?.catch { diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/components/UpdatesScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/components/UpdatesScreenContent.kt index 6b7944c2..cce6077d 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/components/UpdatesScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/updates/components/UpdatesScreenContent.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment @@ -44,11 +45,12 @@ import ca.gosyer.jui.uicore.components.mangaAspectRatio import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter import ca.gosyer.jui.uicore.resources.stringResource import io.kamel.image.lazyPainterResource +import kotlinx.datetime.LocalDate @Composable fun UpdatesScreenContent( isLoading: Boolean, - updates: List, + dateWithUpdates: List>>, loadNextPage: () -> Unit, openChapter: (Int, Long) -> Unit, openManga: (Long) -> Unit, @@ -61,28 +63,37 @@ fun UpdatesScreenContent( Toolbar(stringResource(MR.strings.location_updates)) } ) { - if (isLoading || updates.isEmpty()) { + if (isLoading || dateWithUpdates.isEmpty()) { LoadingScreen(isLoading) } else { Box(Modifier.padding(it)) { val state = rememberLazyListState() LazyColumn(Modifier.fillMaxSize(), state) { - itemsIndexed(updates) { index, item -> - LaunchedEffect(Unit) { - if (index == updates.lastIndex) { - loadNextPage() - } + dateWithUpdates.forEachIndexed { index, (date, updates) -> + item { + Text( + text = date.toString(), + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), + fontWeight = FontWeight.Medium + ) + } + itemsIndexed(updates) { itemIndex, item -> + LaunchedEffect(Unit) { + if (index == dateWithUpdates.lastIndex && itemIndex == updates.lastIndex) { + loadNextPage() + } + } + val manga = item.manga!! + val chapter = item.chapter + UpdatesItem( + chapterDownloadItem = item, + onClickItem = { openChapter(chapter.index, chapter.mangaId) }, + onClickCover = { openManga(manga.id) }, + onClickDownload = downloadChapter, + onClickDeleteDownload = deleteDownloadedChapter, + onClickStopDownload = stopDownloadingChapter + ) } - val manga = item.manga!! - val chapter = item.chapter - UpdatesItem( - item, - onClickItem = { openChapter(chapter.index, chapter.mangaId) }, - onClickCover = { openManga(manga.id) }, - onClickDownload = downloadChapter, - onClickDeleteDownload = deleteDownloadedChapter, - onClickStopDownload = stopDownloadingChapter - ) } } VerticalScrollbar(