mirror of
https://github.com/null2264/yokai.git
synced 2025-06-20 18:24:42 +00:00
feat: Mark duplicate read chapters as read
This also refactor how chapters progress are saved. Chapters' progress now save when user "flipped" the page. Closes GH-409 Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
parent
d3050d5799
commit
850151720b
7 changed files with 134 additions and 81 deletions
|
@ -14,10 +14,12 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co
|
||||||
- Add random library sort
|
- Add random library sort
|
||||||
- Add the ability to save search queries
|
- Add the ability to save search queries
|
||||||
- Add toggle to enable/disable hide source on swipe (@Hiirbaf)
|
- Add toggle to enable/disable hide source on swipe (@Hiirbaf)
|
||||||
|
- Add the ability to mark duplicate read chapters as read (@AntsyLich)
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Temporarily disable log file
|
- Temporarily disable log file
|
||||||
- Categories' header now show filtered count when you search the library when you have "Show number of items" enabled (@LeeSF03)
|
- Categories' header now show filtered count when you search the library when you have "Show number of items" enabled (@LeeSF03)
|
||||||
|
- Chapter progress now saved everything the page is changed
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Allow users to bypass onboarding's permission step if Shizuku is installed
|
- Allow users to bypass onboarding's permission step if Shizuku is installed
|
||||||
|
|
|
@ -148,6 +148,14 @@ import eu.kanade.tachiyomi.util.view.setMessage
|
||||||
import eu.kanade.tachiyomi.util.view.snack
|
import eu.kanade.tachiyomi.util.view.snack
|
||||||
import eu.kanade.tachiyomi.widget.doOnEnd
|
import eu.kanade.tachiyomi.widget.doOnEnd
|
||||||
import eu.kanade.tachiyomi.widget.doOnStart
|
import eu.kanade.tachiyomi.widget.doOnStart
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.DecimalFormatSymbols
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.roundToInt
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -167,13 +175,6 @@ import yokai.domain.ui.settings.ReaderPreferences
|
||||||
import yokai.domain.ui.settings.ReaderPreferences.LandscapeCutoutBehaviour
|
import yokai.domain.ui.settings.ReaderPreferences.LandscapeCutoutBehaviour
|
||||||
import yokai.i18n.MR
|
import yokai.i18n.MR
|
||||||
import yokai.util.lang.getString
|
import yokai.util.lang.getString
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import android.R as AR
|
import android.R as AR
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -512,7 +513,6 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModel.onSaveInstanceState()
|
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1304,13 +1304,13 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
viewModel.saveCurrentChapterReadingProgress()
|
viewModel.flushReadTimer()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.setReadStartTime()
|
viewModel.restartReadTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
|
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
|
||||||
|
|
|
@ -55,9 +55,7 @@ import eu.kanade.tachiyomi.util.system.withIOContext
|
||||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
@ -81,6 +79,7 @@ import yokai.domain.chapter.models.ChapterUpdate
|
||||||
import yokai.domain.download.DownloadPreferences
|
import yokai.domain.download.DownloadPreferences
|
||||||
import yokai.domain.history.interactor.GetHistory
|
import yokai.domain.history.interactor.GetHistory
|
||||||
import yokai.domain.history.interactor.UpsertHistory
|
import yokai.domain.history.interactor.UpsertHistory
|
||||||
|
import yokai.domain.library.LibraryPreferences
|
||||||
import yokai.domain.manga.interactor.GetManga
|
import yokai.domain.manga.interactor.GetManga
|
||||||
import yokai.domain.manga.interactor.InsertManga
|
import yokai.domain.manga.interactor.InsertManga
|
||||||
import yokai.domain.manga.interactor.UpdateManga
|
import yokai.domain.manga.interactor.UpdateManga
|
||||||
|
@ -102,6 +101,7 @@ class ReaderViewModel(
|
||||||
private val chapterFilter: ChapterFilter = Injekt.get(),
|
private val chapterFilter: ChapterFilter = Injekt.get(),
|
||||||
private val storageManager: StorageManager = Injekt.get(),
|
private val storageManager: StorageManager = Injekt.get(),
|
||||||
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
||||||
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val getCategories: GetCategories by injectLazy()
|
private val getCategories: GetCategories by injectLazy()
|
||||||
private val getChapter: GetChapter by injectLazy()
|
private val getChapter: GetChapter by injectLazy()
|
||||||
|
@ -160,8 +160,6 @@ class ReaderViewModel(
|
||||||
|
|
||||||
private var chapterItems = emptyList<ReaderChapterItem>()
|
private var chapterItems = emptyList<ReaderChapterItem>()
|
||||||
|
|
||||||
private var scope = CoroutineScope(Job() + Dispatchers.Default)
|
|
||||||
|
|
||||||
private var hasTrackers: Boolean = false
|
private var hasTrackers: Boolean = false
|
||||||
private suspend fun checkTrackers(manga: Manga) = getTrack.awaitAllByMangaId(manga.id).isNotEmpty()
|
private suspend fun checkTrackers(manga: Manga) = getTrack.awaitAllByMangaId(manga.id).isNotEmpty()
|
||||||
|
|
||||||
|
@ -192,24 +190,12 @@ class ReaderViewModel(
|
||||||
val currentChapters = state.value.viewerChapters
|
val currentChapters = state.value.viewerChapters
|
||||||
if (currentChapters != null) {
|
if (currentChapters != null) {
|
||||||
currentChapters.unref()
|
currentChapters.unref()
|
||||||
saveReadingProgress(currentChapters.currChapter)
|
|
||||||
chapterToDownload?.let {
|
chapterToDownload?.let {
|
||||||
downloadManager.addDownloadsToStartOfQueue(listOf(it))
|
downloadManager.addDownloadsToStartOfQueue(listOf(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the activity is saved and not changing configurations. It updates the database
|
|
||||||
* to persist the current progress of the active chapter.
|
|
||||||
*/
|
|
||||||
fun onSaveInstanceState() {
|
|
||||||
val currentChapter = getCurrentChapter() ?: return
|
|
||||||
viewModelScope.launchNonCancellableIO {
|
|
||||||
saveChapterProgress(currentChapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this presenter is initialized yet.
|
* Whether this presenter is initialized yet.
|
||||||
*/
|
*/
|
||||||
|
@ -375,12 +361,15 @@ class ReaderViewModel(
|
||||||
* Called when the user changed to the given [chapter] when changing pages from the viewer.
|
* Called when the user changed to the given [chapter] when changing pages from the viewer.
|
||||||
* It's used only to set this chapter as active.
|
* It's used only to set this chapter as active.
|
||||||
*/
|
*/
|
||||||
private suspend fun loadNewChapter(chapter: ReaderChapter) {
|
private fun loadNewChapter(chapter: ReaderChapter) {
|
||||||
val loader = loader ?: return
|
val loader = loader ?: return
|
||||||
|
|
||||||
Logger.d { "Loading ${chapter.chapter.url}" }
|
viewModelScope.launchIO {
|
||||||
|
Logger.d { "Loading ${chapter.chapter.url}" }
|
||||||
|
|
||||||
|
flushReadTimer()
|
||||||
|
restartReadTimer()
|
||||||
|
|
||||||
withIOContext {
|
|
||||||
try {
|
try {
|
||||||
loadChapter(loader, chapter)
|
loadChapter(loader, chapter)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -511,28 +500,15 @@ class ReaderViewModel(
|
||||||
val selectedChapter = page.chapter
|
val selectedChapter = page.chapter
|
||||||
|
|
||||||
// Save last page read and mark as read if needed
|
// Save last page read and mark as read if needed
|
||||||
selectedChapter.chapter.last_page_read = page.index
|
viewModelScope.launchNonCancellableIO {
|
||||||
selectedChapter.chapter.pages_left =
|
saveChapterProgress(selectedChapter, page, hasExtraPage)
|
||||||
(selectedChapter.pages?.size ?: page.index) - page.index
|
|
||||||
val shouldTrack = !preferences.incognitoMode().get() || hasTrackers
|
|
||||||
if (shouldTrack &&
|
|
||||||
// For double pages, check if the second to last page is doubled up
|
|
||||||
(
|
|
||||||
(selectedChapter.pages?.lastIndex == page.index && page.firstHalf != true) ||
|
|
||||||
(hasExtraPage && selectedChapter.pages?.lastIndex?.minus(1) == page.index)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
selectedChapter.chapter.read = true
|
|
||||||
updateTrackChapterAfterReading(selectedChapter)
|
|
||||||
deleteChapterIfNeeded(selectedChapter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedChapter != currentChapters.currChapter) {
|
if (selectedChapter != currentChapters.currChapter) {
|
||||||
Logger.d { "Setting ${selectedChapter.chapter.url} as active" }
|
Logger.d { "Setting ${selectedChapter.chapter.url} as active" }
|
||||||
saveReadingProgress(currentChapters.currChapter)
|
loadNewChapter(selectedChapter)
|
||||||
setReadStartTime()
|
|
||||||
scope.launch { loadNewChapter(selectedChapter) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val pages = page.chapter.pages ?: return
|
val pages = page.chapter.pages ?: return
|
||||||
val inDownloadRange = page.number.toDouble() / pages.size > 0.2
|
val inDownloadRange = page.number.toDouble() / pages.size > 0.2
|
||||||
if (inDownloadRange) {
|
if (inDownloadRange) {
|
||||||
|
@ -620,28 +596,28 @@ class ReaderViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when reader chapter is changed in reader or when activity is paused.
|
|
||||||
*/
|
|
||||||
private fun saveReadingProgress(readerChapter: ReaderChapter) {
|
|
||||||
viewModelScope.launchNonCancellableIO {
|
|
||||||
saveChapterProgress(readerChapter)
|
|
||||||
saveChapterHistory(readerChapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveCurrentChapterReadingProgress() = getCurrentChapter()?.let { saveReadingProgress(it) }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves this [readerChapter]'s progress (last read page and whether it's read).
|
* Saves this [readerChapter]'s progress (last read page and whether it's read).
|
||||||
* If incognito mode isn't on or has at least 1 tracker
|
* If incognito mode isn't on or has at least 1 tracker
|
||||||
*/
|
*/
|
||||||
private suspend fun saveChapterProgress(readerChapter: ReaderChapter) {
|
private suspend fun saveChapterProgress(readerChapter: ReaderChapter, page: ReaderPage, hasExtraPage: Boolean) {
|
||||||
readerChapter.requestedPage = readerChapter.chapter.last_page_read
|
readerChapter.requestedPage = readerChapter.chapter.last_page_read
|
||||||
getChapter.awaitById(readerChapter.chapter.id!!)?.let { dbChapter ->
|
getChapter.awaitById(readerChapter.chapter.id!!)?.let { dbChapter ->
|
||||||
readerChapter.chapter.bookmark = dbChapter.bookmark
|
readerChapter.chapter.bookmark = dbChapter.bookmark
|
||||||
}
|
}
|
||||||
if (!preferences.incognitoMode().get() || hasTrackers) {
|
|
||||||
|
val shouldTrack = !preferences.incognitoMode().get() || hasTrackers
|
||||||
|
if (shouldTrack && page.status != Page.State.ERROR) {
|
||||||
|
readerChapter.chapter.last_page_read = page.index
|
||||||
|
readerChapter.chapter.pages_left = (readerChapter.pages?.size ?: page.index) - page.index
|
||||||
|
// For double pages, check if the second to last page is doubled up
|
||||||
|
if (
|
||||||
|
(readerChapter.pages?.lastIndex == page.index && page.firstHalf != true) ||
|
||||||
|
(hasExtraPage && readerChapter.pages?.lastIndex?.minus(1) == page.index)
|
||||||
|
) {
|
||||||
|
onChapterReadComplete(readerChapter)
|
||||||
|
}
|
||||||
|
|
||||||
updateChapter.await(
|
updateChapter.await(
|
||||||
ChapterUpdate(
|
ChapterUpdate(
|
||||||
id = readerChapter.chapter.id!!,
|
id = readerChapter.chapter.id!!,
|
||||||
|
@ -654,24 +630,57 @@ class ReaderViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun onChapterReadComplete(readerChapter: ReaderChapter) {
|
||||||
|
readerChapter.chapter.read = true
|
||||||
|
updateTrackChapterAfterReading(readerChapter)
|
||||||
|
deleteChapterIfNeeded(readerChapter)
|
||||||
|
|
||||||
|
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
|
||||||
|
.contains(LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING)
|
||||||
|
if (!markDuplicateAsRead) return
|
||||||
|
|
||||||
|
val duplicateUnreadChapters = chapterList
|
||||||
|
.mapNotNull {
|
||||||
|
val chapter = it.chapter
|
||||||
|
if (
|
||||||
|
!chapter.read &&
|
||||||
|
chapter.isRecognizedNumber &&
|
||||||
|
chapter.chapter_number == readerChapter.chapter.chapter_number
|
||||||
|
) {
|
||||||
|
ChapterUpdate(id = chapter.id!!, read = true)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateChapter.awaitAll(duplicateUnreadChapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restartReadTimer() {
|
||||||
|
chapterReadStartTime = Date().time
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flushReadTimer() {
|
||||||
|
getCurrentChapter()?.let {
|
||||||
|
viewModelScope.launchNonCancellableIO {
|
||||||
|
saveChapterHistory(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves this [readerChapter] last read history.
|
* Saves this [readerChapter] last read history.
|
||||||
*/
|
*/
|
||||||
private suspend fun saveChapterHistory(readerChapter: ReaderChapter) {
|
private suspend fun saveChapterHistory(readerChapter: ReaderChapter) {
|
||||||
if (!preferences.incognitoMode().get()) {
|
if (preferences.incognitoMode().get()) return
|
||||||
val readAt = Date().time
|
|
||||||
val sessionReadDuration = chapterReadStartTime?.let { readAt - it } ?: 0
|
|
||||||
val history = History.create(readerChapter.chapter).apply {
|
|
||||||
last_read = readAt
|
|
||||||
time_read = sessionReadDuration
|
|
||||||
}
|
|
||||||
upsertHistory.await(history)
|
|
||||||
chapterReadStartTime = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setReadStartTime() {
|
val endTime = Date().time
|
||||||
chapterReadStartTime = Date().time
|
val sessionReadDuration = chapterReadStartTime?.let { endTime - it } ?: 0
|
||||||
|
val history = History.create(readerChapter.chapter).apply {
|
||||||
|
last_read = endTime
|
||||||
|
time_read = sessionReadDuration
|
||||||
|
}
|
||||||
|
upsertHistory.await(history)
|
||||||
|
chapterReadStartTime = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -876,7 +885,7 @@ class ReaderViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
||||||
scope.launch {
|
viewModelScope.launch {
|
||||||
if (firstPage.status != Page.State.READY) return@launch
|
if (firstPage.status != Page.State.READY) return@launch
|
||||||
if (secondPage.status != Page.State.READY) return@launch
|
if (secondPage.status != Page.State.READY) return@launch
|
||||||
val manga = manga ?: return@launch
|
val manga = manga ?: return@launch
|
||||||
|
@ -925,7 +934,7 @@ class ReaderViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
||||||
scope.launch {
|
viewModelScope.launch {
|
||||||
if (firstPage.status != Page.State.READY) return@launch
|
if (firstPage.status != Page.State.READY) return@launch
|
||||||
if (secondPage.status != Page.State.READY) return@launch
|
if (secondPage.status != Page.State.READY) return@launch
|
||||||
val manga = manga ?: return@launch
|
val manga = manga ?: return@launch
|
||||||
|
|
|
@ -34,6 +34,7 @@ import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import yokai.domain.category.interactor.GetCategories
|
import yokai.domain.category.interactor.GetCategories
|
||||||
|
import yokai.domain.library.LibraryPreferences
|
||||||
import yokai.domain.manga.interactor.GetLibraryManga
|
import yokai.domain.manga.interactor.GetLibraryManga
|
||||||
import yokai.domain.ui.UiPreferences
|
import yokai.domain.ui.UiPreferences
|
||||||
import yokai.i18n.MR
|
import yokai.i18n.MR
|
||||||
|
@ -48,6 +49,7 @@ class SettingsLibraryController : SettingsLegacyController() {
|
||||||
private val getCategories: GetCategories by injectLazy()
|
private val getCategories: GetCategories by injectLazy()
|
||||||
|
|
||||||
private val uiPreferences: UiPreferences by injectLazy()
|
private val uiPreferences: UiPreferences by injectLazy()
|
||||||
|
private val libraryPreferences: LibraryPreferences by injectLazy()
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
||||||
titleRes = MR.strings.library
|
titleRes = MR.strings.library
|
||||||
|
@ -212,12 +214,25 @@ class SettingsLibraryController : SettingsLegacyController() {
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceCategory {
|
preferenceCategory {
|
||||||
titleRes = MR.strings.chapters
|
titleRes = MR.strings.pref_behavior
|
||||||
|
|
||||||
switchPreference {
|
switchPreference {
|
||||||
bindTo(uiPreferences.enableChapterSwipeAction())
|
bindTo(uiPreferences.enableChapterSwipeAction())
|
||||||
titleRes = MR.strings.enable_chapter_swipe_action
|
titleRes = MR.strings.enable_chapter_swipe_action
|
||||||
}
|
}
|
||||||
|
multiSelectListPreferenceMat(activity) {
|
||||||
|
bindTo(preferences.libraryUpdateMangaRestriction())
|
||||||
|
titleRes = MR.strings.pref_mark_as_read_duplicate_read_chapter
|
||||||
|
val entries = mapOf(
|
||||||
|
MR.strings.pref_mark_as_read_duplicate_read_chapter_existing to
|
||||||
|
LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING,
|
||||||
|
MR.strings.pref_mark_as_read_duplicate_read_chapter_new to
|
||||||
|
LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_NEW,
|
||||||
|
)
|
||||||
|
entriesRes = entries.keys.toTypedArray()
|
||||||
|
entryValues = entries.values.toList()
|
||||||
|
noSelectionRes = MR.strings.none
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import yokai.domain.chapter.interactor.InsertChapter
|
||||||
import yokai.domain.chapter.interactor.UpdateChapter
|
import yokai.domain.chapter.interactor.UpdateChapter
|
||||||
import yokai.domain.chapter.models.ChapterUpdate
|
import yokai.domain.chapter.models.ChapterUpdate
|
||||||
import yokai.domain.chapter.services.ChapterRecognition
|
import yokai.domain.chapter.services.ChapterRecognition
|
||||||
|
import yokai.domain.library.LibraryPreferences
|
||||||
import yokai.domain.manga.interactor.UpdateManga
|
import yokai.domain.manga.interactor.UpdateManga
|
||||||
import yokai.domain.manga.models.MangaUpdate
|
import yokai.domain.manga.models.MangaUpdate
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ suspend fun syncChaptersWithSource(
|
||||||
updateChapter: UpdateChapter = Injekt.get(),
|
updateChapter: UpdateChapter = Injekt.get(),
|
||||||
updateManga: UpdateManga = Injekt.get(),
|
updateManga: UpdateManga = Injekt.get(),
|
||||||
handler: DatabaseHandler = Injekt.get(),
|
handler: DatabaseHandler = Injekt.get(),
|
||||||
|
libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
): Pair<List<Chapter>, List<Chapter>> {
|
): Pair<List<Chapter>, List<Chapter>> {
|
||||||
if (rawSourceChapters.isEmpty()) {
|
if (rawSourceChapters.isEmpty()) {
|
||||||
throw Exception("No chapters found")
|
throw Exception("No chapters found")
|
||||||
|
@ -122,11 +124,18 @@ suspend fun syncChaptersWithSource(
|
||||||
return Pair(emptyList(), emptyList())
|
return Pair(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
val reAdded = mutableListOf<Chapter>()
|
val changedOrDuplicateReadUrls = mutableSetOf<String>()
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Float>()
|
val deletedChapterNumbers = TreeSet<Float>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
||||||
|
|
||||||
|
val readChapterNumbers = dbChapters
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.read && it.isRecognizedNumber }
|
||||||
|
.map { it.chapter_number }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
toDelete.forEach {
|
toDelete.forEach {
|
||||||
if (it.read) deletedReadChapterNumbers.add(it.chapter_number)
|
if (it.read) deletedReadChapterNumbers.add(it.chapter_number)
|
||||||
if (it.bookmark) deletedBookmarkedChapterNumbers.add(it.chapter_number)
|
if (it.bookmark) deletedBookmarkedChapterNumbers.add(it.chapter_number)
|
||||||
|
@ -135,6 +144,9 @@ suspend fun syncChaptersWithSource(
|
||||||
|
|
||||||
val now = Date().time
|
val now = Date().time
|
||||||
|
|
||||||
|
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
|
||||||
|
.contains(LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_NEW)
|
||||||
|
|
||||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
var itemCount = toAdd.size
|
var itemCount = toAdd.size
|
||||||
|
@ -143,6 +155,11 @@ suspend fun syncChaptersWithSource(
|
||||||
|
|
||||||
chapter.date_fetch = now + itemCount--
|
chapter.date_fetch = now + itemCount--
|
||||||
|
|
||||||
|
if (chapter.chapter_number in readChapterNumbers && markDuplicateAsRead) {
|
||||||
|
changedOrDuplicateReadUrls.add(chapter.url)
|
||||||
|
chapter.read = true
|
||||||
|
}
|
||||||
|
|
||||||
if (!chapter.isRecognizedNumber || chapter.chapter_number !in deletedChapterNumbers) return@map chapter
|
if (!chapter.isRecognizedNumber || chapter.chapter_number !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
chapter.read = chapter.chapter_number in deletedReadChapterNumbers
|
chapter.read = chapter.chapter_number in deletedReadChapterNumbers
|
||||||
|
@ -154,7 +171,7 @@ suspend fun syncChaptersWithSource(
|
||||||
chapter.date_fetch = it.date_fetch
|
chapter.date_fetch = it.date_fetch
|
||||||
}
|
}
|
||||||
|
|
||||||
reAdded.add(chapter)
|
changedOrDuplicateReadUrls.add(chapter.url)
|
||||||
|
|
||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
|
@ -192,14 +209,13 @@ suspend fun syncChaptersWithSource(
|
||||||
manga.last_update = Date().time
|
manga.last_update = Date().time
|
||||||
updateManga.await(MangaUpdate(manga.id!!, lastUpdate = manga.last_update))
|
updateManga.await(MangaUpdate(manga.id!!, lastUpdate = manga.last_update))
|
||||||
|
|
||||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
|
||||||
val filteredScanlators = ChapterUtil.getScanlators(manga.filtered_scanlators).toHashSet()
|
val filteredScanlators = ChapterUtil.getScanlators(manga.filtered_scanlators).toHashSet()
|
||||||
|
|
||||||
return Pair(
|
return Pair(
|
||||||
updatedToAdd.filterNot {
|
updatedToAdd.filterNot {
|
||||||
it.url in reAddedUrls || it.scanlator in filteredScanlators
|
it.url in changedOrDuplicateReadUrls || it.scanlator in filteredScanlators
|
||||||
},
|
},
|
||||||
toDelete.filterNot { it.url in reAddedUrls },
|
toDelete.filterNot { it.url in changedOrDuplicateReadUrls },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,11 @@ import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class LibraryPreferences(private val preferenceStore: PreferenceStore) {
|
class LibraryPreferences(private val preferenceStore: PreferenceStore) {
|
||||||
fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0)
|
fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0)
|
||||||
|
|
||||||
|
fun markDuplicateReadChapterAsRead() = preferenceStore.getStringSet("mark_duplicate_read_chapter_read", emptySet())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MARK_DUPLICATE_READ_CHAPTER_READ_NEW = "new"
|
||||||
|
const val MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING = "existing"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,10 @@
|
||||||
<string name="ungrouped">Ungrouped</string>
|
<string name="ungrouped">Ungrouped</string>
|
||||||
|
|
||||||
<string name="pref_use_compose_library">Use experimental compose library</string>
|
<string name="pref_use_compose_library">Use experimental compose library</string>
|
||||||
|
<string name="pref_behavior">Behavior</string>
|
||||||
|
<string name="pref_mark_as_read_duplicate_read_chapter">Mark duplicate read chapter as read</string>
|
||||||
|
<string name="pref_mark_as_read_duplicate_read_chapter_existing">After reading a chapter</string>
|
||||||
|
<string name="pref_mark_as_read_duplicate_read_chapter_new">After fetching new chapter</string>
|
||||||
|
|
||||||
<!-- Library Sort -->
|
<!-- Library Sort -->
|
||||||
<string name="sort_by">Sort by</string>
|
<string name="sort_by">Sort by</string>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue