From a3abccb34ba5df5fee7ca9fa40b8c10735c371cd Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Thu, 15 May 2025 16:31:19 +0700 Subject: [PATCH] feat(updates-selection): Enable whole-group selection & Fix range selection in Updates screen (#913) * fix(updates-screen): Fix range selection in Updates screen * feat(updates-screen): select Updates whole-group * improve performance * simplify onUpdateSelected parameters using UpdateSelectionOptions --- .../presentation/updates/UpdatesScreen.kt | 2 +- .../presentation/updates/UpdatesUiItem.kt | 31 ++++++++-- .../ui/updates/UpdatesScreenModel.kt | 61 +++++++++++++++---- 3 files changed, 78 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index 5b2f10069..939052464 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -60,7 +60,7 @@ fun UpdateScreen( onMultiBookmarkClicked: (List, bookmark: Boolean) -> Unit, onMultiMarkAsReadClicked: (List, read: Boolean) -> Unit, onMultiDeleteClicked: (List) -> Unit, - onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit, + onUpdateSelected: (UpdatesItem, /* KMK --> */ UpdatesScreenModel.UpdateSelectionOptions /* KMK <-- */) -> Unit, onOpenChapter: (UpdatesItem) -> Unit, // KMK --> collapseToggle: (key: String) -> Unit, diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt index 4a30ebda8..3c2d1845d 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt @@ -54,6 +54,7 @@ import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.updates.UpdatesItem +import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.UpdateSelectionOptions import eu.kanade.tachiyomi.ui.updates.groupByDateAndManga import mihon.feature.upcoming.DateHeading import tachiyomi.domain.updates.model.UpdatesWithRelations @@ -91,7 +92,7 @@ internal fun LazyListScope.updatesUiItems( // SY --> preserveReadingPosition: Boolean, // SY <-- - onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit, + onUpdateSelected: (UpdatesItem, /* KMK --> */ UpdateSelectionOptions /* KMK <-- */) -> Unit, onClickCover: (UpdatesItem) -> Unit, onClickUpdate: (UpdatesItem) -> Unit, onDownloadChapter: (List, ChapterDownloadAction) -> Unit, @@ -124,10 +125,10 @@ internal fun LazyListScope.updatesUiItems( } is UpdatesUiModel.Item -> { val updatesItem = item.item + // KMK --> val isLeader = item is UpdatesUiModel.Leader val isExpanded = expandedState.contains(updatesItem.update.groupByDateAndManga()) - // KMK --> AnimatedVisibility( visible = isLeader || isExpanded, enter = fadeIn() + expandVertically(), @@ -153,11 +154,33 @@ internal fun LazyListScope.updatesUiItems( ) }, onLongClick = { - onUpdateSelected(updatesItem, !updatesItem.selected, true, true) + onUpdateSelected( + updatesItem, + // KMK --> + UpdateSelectionOptions( + selected = !updatesItem.selected, + userSelected = true, + fromLongPress = true, + isGroup = isLeader && item.isExpandable, + isExpanded = isExpanded, + ), + // KMK <-- + ) }, onClick = { when { - selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false) + selectionMode -> onUpdateSelected( + updatesItem, + // KMK --> + UpdateSelectionOptions( + selected = !updatesItem.selected, + userSelected = true, + fromLongPress = false, + isGroup = isLeader && item.isExpandable, + isExpanded = isExpanded, + ), + // KMK <-- + ) else -> onClickUpdate(updatesItem) } }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index 1fe8980a6..df5018788 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -94,10 +94,20 @@ class UpdatesScreenModel( _events.send(Event.InternalError) } .collectLatest { updates -> - mutableState.update { - it.copy( + mutableState.update { state -> + state.copy( isLoading = false, - items = updates.toUpdateItems(), + items = updates.toUpdateItems() + // KMK --> + .groupBy { it.update.mangaId } + .values + .flatMap { mangaChapters -> + val (unread, read) = mangaChapters.partition { !it.update.read } + unread.sortedBy { it.update.dateFetch } + + read.sortedByDescending { it.update.dateFetch } + } + .toPersistentList(), + // KMK <-- ) } } @@ -282,12 +292,26 @@ class UpdatesScreenModel( setDialog(Dialog.DeleteConfirmation(updatesItem)) } + // KMK --> + /** Bundles all of the boolean flags for update‐selection into one type */ + data class UpdateSelectionOptions( + val selected: Boolean, + val userSelected: Boolean = false, + val fromLongPress: Boolean = false, + val isGroup: Boolean = false, + val isExpanded: Boolean = false, + ) + // KMK <-- + fun toggleSelection( item: UpdatesItem, - selected: Boolean, - userSelected: Boolean = false, - fromLongPress: Boolean = false, + // KMK --> + selectionOptions: UpdateSelectionOptions, + // KMK <-- ) { + // KMK --> + val (selected, userSelected, fromLongPress, isGroup, isExpanded) = selectionOptions + // KMK <-- mutableState.update { state -> val newItems = state.items.toMutableList().apply { val selectedIndex = indexOfFirst { it.update.chapterId == item.update.chapterId } @@ -300,6 +324,25 @@ class UpdatesScreenModel( set(selectedIndex, selectedItem.copy(selected = selected)) selectedChapterIds.addOrRemove(item.update.chapterId, selected) + // KMK --> + if (isGroup && !isExpanded) { + val selectedItemDate = selectedItem.update.dateFetch.toLocalDate() + val zone = java.time.ZoneId.systemDefault() + val dayStartMillis = selectedItemDate.atStartOfDay(zone).toInstant().toEpochMilli() + val dayEndMillis = selectedItemDate.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli() + + state.items.mapIndexed { index, item -> index to item } + .filter { + it.second.update.mangaId == selectedItem.update.mangaId && + it.second.update.dateFetch in dayStartMillis.. + set(index, item.copy(selected = selected)) + selectedChapterIds.addOrRemove(item.update.chapterId, selected) + } + } + // KMK <-- + if (selected && userSelected && fromLongPress) { if (firstSelection) { selectedPositions[0] = selectedIndex @@ -413,13 +456,9 @@ class UpdatesScreenModel( .groupBy { it.update.mangaId } .values .flatMap { mangaChapters -> - val (unread, read) = mangaChapters.partition { !it.update.read } - val sortedChapters = unread.sortedBy { it.update.dateFetch } + - read.sortedByDescending { it.update.dateFetch } val isExpandable = mangaChapters.size > 1 - var lastMangaId = -1L - sortedChapters.map { chapter -> + mangaChapters.map { chapter -> if (chapter.update.mangaId != lastMangaId) { lastMangaId = chapter.update.mangaId UpdatesUiModel.Leader(chapter, isExpandable)