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
This commit is contained in:
parent
33a2219716
commit
f301dc64f0
10 changed files with 125 additions and 69 deletions
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
app/src/main/sqldelight/migrations/23.sqm
Normal file
23
app/src/main/sqldelight/migrations/23.sqm
Normal file
|
@ -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;
|
|
@ -7,6 +7,7 @@ SELECT
|
|||
chapters.scanlator,
|
||||
chapters.read,
|
||||
chapters.bookmark,
|
||||
chapters.last_page_read,
|
||||
mangas.source,
|
||||
mangas.favorite,
|
||||
mangas.thumbnail_url AS thumbnailUrl,
|
||||
|
|
Reference in a new issue