浏览代码

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
zbue 2 年之前
父节点
当前提交
f301dc64f0

+ 3 - 2
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(

+ 1 - 0
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,

+ 52 - 3
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<ChapterItem>, 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<ChapterItem>, 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<ChapterItem>,
+    dateRelativeTime: Int,
+    dateFormat: DateFormat,
     onChapterClicked: (Chapter) -> Unit,
     onDownloadChapter: ((List<ChapterItem>, 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,

+ 1 - 1
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 {

+ 22 - 1
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(

+ 2 - 0
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) },

+ 9 - 49
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<ChapterItem>?
         get() = successState?.processedChapters
 
+    val relativeTime by uiPreferences.relativeTime().asState(coroutineScope)
+    val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
+
     private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
     private val selectedChapterIds: HashSet<Long> = HashSet()
 
@@ -126,26 +129,16 @@ class MangaInfoScreenModel(
     }
 
     init {
-        val toChapterItemsParams: List<Chapter>.(manga: Manga) -> List<ChapterItem> = { 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<Chapter>.toChapterItems(
-        context: Context,
-        manga: Manga,
-        dateRelativeTime: Int,
-        dateFormat: DateFormat,
-    ): List<ChapterItem> {
+    private fun List<Chapter>.toChapterItems(manga: Manga): List<ChapterItem> {
         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

+ 11 - 13
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<Event> = _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<Int> = arrayOf(-1, -1)
@@ -110,13 +107,13 @@ class UpdatesScreenModel(
     }
 
     private fun List<UpdatesWithRelations>.toUpdateItems(): List<UpdatesItem> {
-        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<UpdatesUiModel> {
-        val dateFormat = UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get())
+        val dateFormat by mutableStateOf(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()))
+
         return items
             .map { UpdatesUiModel.Item(it) }
             .insertSeparators { before, after ->

+ 23 - 0
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;

+ 1 - 0
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,