From f301dc64f00c2d8a19cb89610cf6fba7eb78917d Mon Sep 17 00:00:00 2001 From: zbue <108109611+zbue@users.noreply.github.com> Date: Sun, 15 Jan 2023 07:26:40 +0800 Subject: [PATCH] Allow partially read chapters to be marked as unread in updates screen (#8884) * Allow partially read chapters to be marked as unread in updates screen * Review changes * Review changes 2 --- .../eu/kanade/data/updates/UpdatesMapper.kt | 5 +- .../updates/model/UpdatesWithRelations.kt | 1 + .../kanade/presentation/manga/MangaScreen.kt | 55 +++++++++++++++++- .../presentation/updates/UpdatesScreen.kt | 2 +- .../presentation/updates/UpdatesUiItem.kt | 23 +++++++- .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 2 + .../tachiyomi/ui/manga/MangaScreenModel.kt | 58 +++---------------- .../ui/updates/UpdatesScreenModel.kt | 24 ++++---- app/src/main/sqldelight/migrations/23.sqm | 23 ++++++++ app/src/main/sqldelight/view/updatesView.sq | 1 + 10 files changed, 125 insertions(+), 69 deletions(-) create mode 100644 app/src/main/sqldelight/migrations/23.sqm diff --git a/app/src/main/java/eu/kanade/data/updates/UpdatesMapper.kt b/app/src/main/java/eu/kanade/data/updates/UpdatesMapper.kt index 4870096746..5ec633e68a 100644 --- a/app/src/main/java/eu/kanade/data/updates/UpdatesMapper.kt +++ b/app/src/main/java/eu/kanade/data/updates/UpdatesMapper.kt @@ -3,8 +3,8 @@ package eu.kanade.data.updates import eu.kanade.domain.manga.model.MangaCover import eu.kanade.domain.updates.model.UpdatesWithRelations -val updateWithRelationMapper: (Long, String, Long, String, String?, Boolean, Boolean, Long, Boolean, String?, Long, Long, Long) -> UpdatesWithRelations = { - mangaId, mangaTitle, chapterId, chapterName, scanlator, read, bookmark, sourceId, favorite, thumbnailUrl, coverLastModified, _, dateFetch -> +val updateWithRelationMapper: (Long, String, Long, String, String?, Boolean, Boolean, Long, Long, Boolean, String?, Long, Long, Long) -> UpdatesWithRelations = { + mangaId, mangaTitle, chapterId, chapterName, scanlator, read, bookmark, lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, _, dateFetch -> UpdatesWithRelations( mangaId = mangaId, mangaTitle = mangaTitle, @@ -13,6 +13,7 @@ val updateWithRelationMapper: (Long, String, Long, String, String?, Boolean, Boo scanlator = scanlator, read = read, bookmark = bookmark, + lastPageRead = lastPageRead, sourceId = sourceId, dateFetch = dateFetch, coverData = MangaCover( diff --git a/app/src/main/java/eu/kanade/domain/updates/model/UpdatesWithRelations.kt b/app/src/main/java/eu/kanade/domain/updates/model/UpdatesWithRelations.kt index a1386a7608..fdba51c69f 100644 --- a/app/src/main/java/eu/kanade/domain/updates/model/UpdatesWithRelations.kt +++ b/app/src/main/java/eu/kanade/domain/updates/model/UpdatesWithRelations.kt @@ -10,6 +10,7 @@ data class UpdatesWithRelations( val scanlator: String?, val read: Boolean, val bookmark: Boolean, + val lastPageRead: Long, val sourceId: Long, val dateFetch: Long, val coverData: MangaCover, diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index f77d19bb95..850cd89cc0 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection @@ -47,6 +48,7 @@ import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastMap import eu.kanade.domain.chapter.model.Chapter +import eu.kanade.domain.manga.model.Manga import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.LazyColumn @@ -69,11 +71,17 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.MangaScreenState +import eu.kanade.tachiyomi.ui.manga.chapterDecimalFormat +import eu.kanade.tachiyomi.util.lang.toRelativeString +import java.text.DateFormat +import java.util.Date @Composable fun MangaScreen( state: MangaScreenState.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Int, + dateFormat: DateFormat, isTabletUi: Boolean, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, @@ -112,6 +120,8 @@ fun MangaScreen( MangaScreenSmallImpl( state = state, snackbarHostState = snackbarHostState, + dateRelativeTime = dateRelativeTime, + dateFormat = dateFormat, onBackClicked = onBackClicked, onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, @@ -141,6 +151,8 @@ fun MangaScreen( MangaScreenLargeImpl( state = state, snackbarHostState = snackbarHostState, + dateRelativeTime = dateRelativeTime, + dateFormat = dateFormat, onBackClicked = onBackClicked, onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, @@ -173,6 +185,8 @@ fun MangaScreen( private fun MangaScreenSmallImpl( state: MangaScreenState.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Int, + dateFormat: DateFormat, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, @@ -364,7 +378,10 @@ private fun MangaScreenSmallImpl( } sharedChapterItems( + manga = state.manga, chapters = chapters, + dateRelativeTime = dateRelativeTime, + dateFormat = dateFormat, onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, onChapterSelected = onChapterSelected, @@ -379,6 +396,8 @@ private fun MangaScreenSmallImpl( fun MangaScreenLargeImpl( state: MangaScreenState.Success, snackbarHostState: SnackbarHostState, + dateRelativeTime: Int, + dateFormat: DateFormat, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, @@ -564,7 +583,10 @@ fun MangaScreenLargeImpl( } sharedChapterItems( + manga = state.manga, chapters = chapters, + dateRelativeTime = dateRelativeTime, + dateFormat = dateFormat, onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, onChapterSelected = onChapterSelected, @@ -620,7 +642,10 @@ private fun SharedMangaBottomActionMenu( } private fun LazyListScope.sharedChapterItems( + manga: Manga, chapters: List, + dateRelativeTime: Int, + dateFormat: DateFormat, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, @@ -631,10 +656,34 @@ private fun LazyListScope.sharedChapterItems( contentType = { MangaScreenItem.CHAPTER }, ) { chapterItem -> val haptic = LocalHapticFeedback.current + val context = LocalContext.current + MangaChapterListItem( - title = chapterItem.chapterTitleString, - date = chapterItem.dateUploadString, - readProgress = chapterItem.readProgressString, + title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) { + stringResource( + R.string.display_mode_chapter, + chapterDecimalFormat.format(chapterItem.chapter.chapterNumber.toDouble()), + ) + } else { + chapterItem.chapter.name + }, + date = chapterItem.chapter.dateUpload + .takeIf { it > 0L } + ?.let { + Date(it).toRelativeString( + context, + dateRelativeTime, + dateFormat, + ) + }, + readProgress = chapterItem.chapter.lastPageRead + .takeIf { !chapterItem.chapter.read && it > 0L } + ?.let { + stringResource( + R.string.chapter_progress, + it + 1, + ) + }, scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() }, read = chapterItem.chapter.read, bookmark = chapterItem.chapter.bookmark, 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 1d7d0f4c17..7707336694 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -193,7 +193,7 @@ private fun UpdatesBottomBar( }.takeIf { selected.fastAny { !it.update.read } }, onMarkAsUnreadClicked = { onMultiMarkAsReadClicked(selected, false) - }.takeIf { selected.fastAny { it.update.read } }, + }.takeIf { selected.fastAny { it.update.read || it.update.lastPageRead > 0L } }, onDownloadClicked = { onDownloadChapter(selected, ChapterDownloadAction.START) }.takeIf { 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 7b99163187..7da6ab5b19 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt @@ -38,6 +38,7 @@ import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ChapterDownloadIndicator import eu.kanade.presentation.components.ListGroupHeader import eu.kanade.presentation.components.MangaCover +import eu.kanade.presentation.manga.components.DotSeparatorText import eu.kanade.presentation.util.ReadItemAlpha import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.selectedBackground @@ -113,6 +114,14 @@ fun LazyListScope.updatesUiItems( modifier = Modifier.animateItemPlacement(), update = updatesItem.update, selected = updatesItem.selected, + readProgress = updatesItem.update.lastPageRead + .takeIf { !updatesItem.update.read && it > 0L } + ?.let { + stringResource( + R.string.chapter_progress, + it + 1, + ) + }, onLongClick = { onUpdateSelected(updatesItem, !updatesItem.selected, true, true) }, @@ -139,6 +148,7 @@ fun UpdatesUiItem( modifier: Modifier, update: UpdatesWithRelations, selected: Boolean, + readProgress: String?, onClick: () -> Unit, onLongClick: () -> Unit, onClickCover: (() -> Unit)?, @@ -203,8 +213,19 @@ fun UpdatesUiItem( style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, - modifier = Modifier.alpha(textAlpha), + modifier = Modifier + .weight(weight = 1f, fill = false) + .alpha(textAlpha), ) + if (readProgress != null) { + DotSeparatorText() + Text( + text = readProgress, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.alpha(ReadItemAlpha), + ) + } } } ChapterDownloadIndicator( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 6a29d43ac7..374e0cb52f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -101,6 +101,8 @@ class MangaScreen( MangaScreen( state = successState, snackbarHostState = screenModel.snackbarHostState, + dateRelativeTime = screenModel.relativeTime, + dateFormat = screenModel.dateFormat, isTabletUi = isTabletUi(), onBackClicked = navigator::pop, onChapterClicked = { openChapter(context, it) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index f9c5acdfab..647ec997a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -4,9 +4,12 @@ import android.content.Context import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.core.prefs.CheckboxState +import eu.kanade.core.prefs.asState import eu.kanade.core.prefs.mapAsCheckboxState import eu.kanade.core.util.addOrRemove import eu.kanade.data.chapter.NoChaptersException @@ -49,7 +52,6 @@ import eu.kanade.tachiyomi.util.chapter.getChapterSort import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchNonCancellable -import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.removeCovers @@ -69,10 +71,8 @@ import kotlinx.coroutines.launch import logcat.LogPriority import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.DateFormat import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import java.util.Date class MangaInfoScreenModel( val context: Context, @@ -115,6 +115,9 @@ class MangaInfoScreenModel( private val processedChapters: Sequence? get() = successState?.processedChapters + val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) + val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) + private val selectedPositions: Array = arrayOf(-1, -1) // first and last selected index in list private val selectedChapterIds: HashSet = HashSet() @@ -126,26 +129,16 @@ class MangaInfoScreenModel( } init { - val toChapterItemsParams: List.(manga: Manga) -> List = { manga -> - toChapterItems( - context = context, - manga = manga, - dateRelativeTime = uiPreferences.relativeTime().get(), - dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()), - ) - } - coroutineScope.launchIO { combine( getMangaAndChapters.subscribe(mangaId).distinctUntilChanged(), downloadCache.changes, ) { mangaAndChapters, _ -> mangaAndChapters } .collectLatest { (manga, chapters) -> - val chapterItems = chapters.toChapterItemsParams(manga) updateSuccessState { it.copy( manga = manga, - chapters = chapterItems, + chapters = chapters.toChapterItems(manga), ) } } @@ -156,7 +149,7 @@ class MangaInfoScreenModel( coroutineScope.launchIO { val manga = getMangaAndChapters.awaitManga(mangaId) val chapters = getMangaAndChapters.awaitChapters(mangaId) - .toChapterItemsParams(manga) + .toChapterItems(manga) if (!manga.favorite) { setMangaDefaultChapterFlags.await(manga) @@ -463,12 +456,7 @@ class MangaInfoScreenModel( } } - private fun List.toChapterItems( - context: Context, - manga: Manga, - dateRelativeTime: Int, - dateFormat: DateFormat, - ): List { + private fun List.toChapterItems(manga: Manga): List { val isLocal = manga.isLocal() return map { chapter -> val activeDownload = if (isLocal) { @@ -491,29 +479,6 @@ class MangaInfoScreenModel( chapter = chapter, downloadState = downloadState, downloadProgress = activeDownload?.progress ?: 0, - chapterTitleString = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) { - context.getString( - R.string.display_mode_chapter, - chapterDecimalFormat.format(chapter.chapterNumber.toDouble()), - ) - } else { - chapter.name - }, - dateUploadString = chapter.dateUpload - .takeIf { it > 0 } - ?.let { - Date(it).toRelativeString( - context, - dateRelativeTime, - dateFormat, - ) - }, - readProgressString = chapter.lastPageRead.takeIf { !chapter.read && it > 0 }?.let { - context.getString( - R.string.chapter_progress, - it + 1, - ) - }, selected = chapter.id in selectedChapterIds, ) } @@ -1068,11 +1033,6 @@ data class ChapterItem( val chapter: Chapter, val downloadState: Download.State, val downloadProgress: Int, - - val chapterTitleString: String, - val dateUploadString: String?, - val readProgressString: String?, - val selected: Boolean = false, ) { val isDownloaded = downloadState == Download.State.DOWNLOADED 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 b67f0dc538..fe4bf9591b 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 @@ -46,7 +46,6 @@ import kotlinx.coroutines.launch import logcat.LogPriority import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.DateFormat import java.util.Calendar import java.util.Date @@ -68,9 +67,7 @@ class UpdatesScreenModel( val events: Flow = _events.receiveAsFlow() val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState(coroutineScope) - - val relativeTime: Int by uiPreferences.relativeTime().asState(coroutineScope) - val dateFormat: DateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) + val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) // First and last selected index in list private val selectedPositions: Array = arrayOf(-1, -1) @@ -110,13 +107,13 @@ class UpdatesScreenModel( } private fun List.toUpdateItems(): List { - return this.map { - val activeDownload = downloadManager.getQueuedDownloadOrNull(it.chapterId) + return this.map { update -> + val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId) val downloaded = downloadManager.isChapterDownloaded( - it.chapterName, - it.scanlator, - it.mangaTitle, - it.sourceId, + update.chapterName, + update.scanlator, + update.mangaTitle, + update.sourceId, ) val downloadState = when { activeDownload != null -> activeDownload.status @@ -124,10 +121,10 @@ class UpdatesScreenModel( else -> Download.State.NOT_DOWNLOADED } UpdatesItem( - update = it, + update = update, downloadStateProvider = { downloadState }, downloadProgressProvider = { activeDownload?.progress ?: 0 }, - selected = it.chapterId in selectedChapterIds, + selected = update.chapterId in selectedChapterIds, ) } } @@ -390,7 +387,8 @@ data class UpdatesState( val selectionMode = selected.isNotEmpty() fun getUiModel(context: Context, relativeTime: Int): List { - val dateFormat = UiPreferences.dateFormat(Injekt.get().dateFormat().get()) + val dateFormat by mutableStateOf(UiPreferences.dateFormat(Injekt.get().dateFormat().get())) + return items .map { UpdatesUiModel.Item(it) } .insertSeparators { before, after -> diff --git a/app/src/main/sqldelight/migrations/23.sqm b/app/src/main/sqldelight/migrations/23.sqm new file mode 100644 index 0000000000..6b25113543 --- /dev/null +++ b/app/src/main/sqldelight/migrations/23.sqm @@ -0,0 +1,23 @@ +DROP VIEW IF EXISTS updatesView; + +CREATE VIEW updatesView AS +SELECT + mangas._id AS mangaId, + mangas.title AS mangaTitle, + chapters._id AS chapterId, + chapters.name AS chapterName, + chapters.scanlator, + chapters.read, + chapters.bookmark, + chapters.last_page_read, + mangas.source, + mangas.favorite, + mangas.thumbnail_url AS thumbnailUrl, + mangas.cover_last_modified AS coverLastModified, + chapters.date_upload AS dateUpload, + chapters.date_fetch AS datefetch +FROM mangas JOIN chapters +ON mangas._id = chapters.manga_id +WHERE favorite = 1 +AND date_fetch > date_added +ORDER BY date_fetch DESC; \ No newline at end of file diff --git a/app/src/main/sqldelight/view/updatesView.sq b/app/src/main/sqldelight/view/updatesView.sq index 6a4323a066..437b86ea0d 100644 --- a/app/src/main/sqldelight/view/updatesView.sq +++ b/app/src/main/sqldelight/view/updatesView.sq @@ -7,6 +7,7 @@ SELECT chapters.scanlator, chapters.read, chapters.bookmark, + chapters.last_page_read, mangas.source, mangas.favorite, mangas.thumbnail_url AS thumbnailUrl,