diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt index 7311fe40de..8b99503365 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt @@ -10,13 +10,15 @@ import eu.kanade.tachiyomi.data.track.TrackService interface TrackQueries : DbProvider { - fun getTracks(manga: Manga) = db.get() + fun getTracks(manga: Manga) = getTracks(manga.id) + + fun getTracks(mangaId: Long?) = db.get() .listOfObjects(Track::class.java) .withQuery( Query.builder() .table(TrackTable.TABLE) .where("${TrackTable.COL_MANGA_ID} = ?") - .whereArgs(manga.id) + .whereArgs(mangaId) .build() ) .prepare() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 7c53d9ca32..f0cd18c45a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.setting.AboutController +import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.notificationManager @@ -192,18 +193,21 @@ class NotificationReceiver : BroadcastReceiver() { */ private fun markAsRead(chapterUrls: Array, mangaId: Long) { val db: DatabaseHelper = Injekt.get() - chapterUrls.forEach { + val preferences: PreferencesHelper = Injekt.get() + val chapters = chapterUrls.map { val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return chapter.read = true db.updateChapterProgress(chapter).executeAsBlocking() - val preferences: PreferencesHelper = Injekt.get() if (preferences.removeAfterMarkedAsRead()) { val manga = db.getManga(mangaId).executeAsBlocking() ?: return val sourceManager: SourceManager = Injekt.get() val source = sourceManager.get(manga.source) ?: return downloadManager.deleteChapters(listOf(chapter), manga, source) } + return@map chapter } + val newLastChapter = chapters.maxByOrNull { it.chapter_number.toInt() } + updateTrackChapterMarkedAsRead(db, preferences, newLastChapter, mangaId, 0) } /** Method called when user wants to stop a restore diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index c369b77c8a..e948d4bceb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -95,6 +95,8 @@ object PreferenceKeys { const val autoUpdateTrack = "pref_auto_update_manga_sync_key" + const val trackMarkedAsRead = "track_marked_as_read" + const val trackingsToAddOnline = "pref_tracking_for_online" const val lastUsedCatalogueSource = "last_catalogue_source" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 7e2a0b4b24..e9709a6b70 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -194,6 +194,8 @@ class PreferencesHelper(val context: Context) { fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true) + fun trackMarkedAsRead() = prefs.getBoolean(Keys.trackMarkedAsRead, false) + fun trackingsToAddOnline() = flowPrefs.getStringSet(Keys.trackingsToAddOnline, emptySet()) fun lastUsedCatalogueSource() = flowPrefs.getLong(Keys.lastUsedCatalogueSource, -1) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index aaa013a69e..7b6eeb56cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -44,9 +44,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.ArrayList import java.util.Calendar -import java.util.Comparator import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index fc0bf84d72..a869fe69ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -80,6 +80,7 @@ import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.addOrRemoveToFavorites +import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.moveCategories import eu.kanade.tachiyomi.util.storage.getUriCompat @@ -864,6 +865,8 @@ class MangaDetailsController : } fun toggleReadChapter(position: Int) { + val preferences = presenter.preferences + val db = presenter.db val item = adapter?.getItem(position) as? ChapterItem ?: return val chapter = item.chapter val lastRead = chapter.last_page_read @@ -885,8 +888,13 @@ class MangaDetailsController : object : BaseTransientBottomBar.BaseCallback() { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { super.onDismissed(transientBottomBar, event) - if (!undoing && !read && presenter.preferences.removeAfterMarkedAsRead()) { - presenter.deleteChapters(listOf(item)) + if (!undoing && !read) { + if (preferences.removeAfterMarkedAsRead()) { + presenter.deleteChapters(listOf(item)) + } + updateTrackChapterMarkedAsRead(db, preferences, chapter, manga?.id) { + presenter.fetchTracks() + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index ae72095491..3313ef3607 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -48,6 +48,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterSort import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay +import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead import eu.kanade.tachiyomi.util.lang.trimOrNull import eu.kanade.tachiyomi.util.manga.MangaShortcutManager import eu.kanade.tachiyomi.util.shouldDownloadNewChapters @@ -488,6 +489,12 @@ class MangaDetailsPresenter( } getChapters() withContext(Dispatchers.Main) { controller?.updateChapters(chapters) } + if (read && deleteNow) { + val latestReadChapter = selectedChapters.maxByOrNull { it.chapter_number.toInt() }?.chapter + updateTrackChapterMarkedAsRead(db, preferences, latestReadChapter, manga.id) { + fetchTracks() + } + } } } @@ -858,7 +865,7 @@ class MangaDetailsPresenter( } } - private suspend fun fetchTracks() { + suspend fun fetchTracks() { tracks = withContext(Dispatchers.IO) { db.getTracks(manga).executeAsBlocking() } trackList = loggedServices.map { service -> TrackItem(tracks.find { it.sync_id == service.id }, service) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index aafedba1b9..af0917d9a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -17,8 +17,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.track.DelayedTrackingUpdateJob -import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager @@ -35,16 +33,16 @@ import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.chapter.ChapterSort import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource +import eu.kanade.tachiyomi.util.chapter.updateTrackChapterRead import eu.kanade.tachiyomi.util.lang.getUrlWithoutDomain import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.executeOnIO -import eu.kanade.tachiyomi.util.system.isOnline +import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.withUIContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -491,7 +489,7 @@ class ReaderPresenter( ) ) { selectedChapter.chapter.read = true - updateTrackChapterRead(selectedChapter) + updateTrackChapterAfterReading(selectedChapter) deleteChapterIfNeeded(selectedChapter) } @@ -877,41 +875,12 @@ class ReaderPresenter( * Starts the service that updates the last chapter read in sync services. This operation * will run in a background thread and errors are ignored. */ - private fun updateTrackChapterRead(readerChapter: ReaderChapter) { + private fun updateTrackChapterAfterReading(readerChapter: ReaderChapter) { if (!preferences.autoUpdateTrack()) return - val manga = manga ?: return - val chapterRead = readerChapter.chapter.chapter_number.toInt() - - val trackManager = Injekt.get() - - // We want these to execute even if the presenter is destroyed so launch on GlobalScope - GlobalScope.launch { - withContext(Dispatchers.IO) { - val trackList = db.getTracks(manga).executeAsBlocking() - trackList.map { track -> - val service = trackManager.getService(track.sync_id) - if (service != null && service.isLogged && chapterRead > track.last_chapter_read) { - if (!preferences.context.isOnline()) { - val mangaId = manga.id ?: return@map - val trackings = preferences.trackingsToAddOnline().get().toMutableSet() - val currentTracking = trackings.find { it.startsWith("$mangaId:${track.sync_id}:") } - trackings.remove(currentTracking) - trackings.add("$mangaId:${track.sync_id}:$chapterRead") - preferences.trackingsToAddOnline().set(trackings) - DelayedTrackingUpdateJob.setupTask(preferences.context) - } else { - try { - track.last_chapter_read = chapterRead - service.update(track, true) - db.insertTrack(track).executeAsBlocking() - } catch (e: Exception) { - Timber.e(e) - } - } - } - } - } + launchIO { + val newChapterRead = readerChapter.chapter.chapter_number.toInt() + updateTrackChapterRead(db, preferences, manga?.id, newChapterRead, true) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index 168ed633b2..b1f6d61b57 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -50,6 +50,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.recents.options.TabbedRecentsOptionsSheet import eu.kanade.tachiyomi.ui.source.browse.ProgressItem +import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getBottomGestureInsets import eu.kanade.tachiyomi.util.system.getResourceColor @@ -668,6 +669,8 @@ class RecentsController(bundle: Bundle? = null) : } override fun markAsRead(position: Int) { + val preferences = presenter.preferences + val db = presenter.db val item = adapter.getItem(position) as? RecentMangaItem ?: return val chapter = item.chapter val manga = item.mch.manga @@ -691,11 +694,14 @@ class RecentsController(bundle: Bundle? = null) : object : BaseTransientBottomBar.BaseCallback() { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { super.onDismissed(transientBottomBar, event) - if (!undoing && presenter.preferences.removeAfterMarkedAsRead() && - !wasRead - ) { - lastChapterId = chapter.id - presenter.deleteChapter(chapter, manga) + if (!undoing && !wasRead) { + if (preferences.removeAfterMarkedAsRead()) { + lastChapterId = chapter.id + presenter.deleteChapter(chapter, manga) + } + updateTrackChapterMarkedAsRead(db, preferences, chapter, manga.id) { + (router.backstack.lastOrNull()?.controller as? MangaDetailsController)?.presenter?.fetchTracks() + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index f52f7a81d4..8622cc6cbf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -40,7 +40,7 @@ import kotlin.math.abs class RecentsPresenter( val preferences: PreferencesHelper = Injekt.get(), val downloadManager: DownloadManager = Injekt.get(), - private val db: DatabaseHelper = Injekt.get(), + val db: DatabaseHelper = Injekt.get(), private val chapterFilter: ChapterFilter = Injekt.get() ) : BaseCoroutinePresenter(), DownloadQueue.DownloadListener, LibraryServiceListener, DownloadServiceListener { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt index 322981ca7d..334795bbdc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt @@ -33,9 +33,14 @@ class SettingsTrackingController : switchPreference { key = Keys.autoUpdateTrack - titleRes = R.string.sync_chapters_after_reading + titleRes = R.string.update_tracking_after_reading defaultValue = true } + switchPreference { + key = Keys.trackMarkedAsRead + titleRes = R.string.update_tracking_marked_read + defaultValue = false + } preferenceCategory { titleRes = R.string.services diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterTrackSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterTrackSync.kt index 35906a5ed6..ec3f636258 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterTrackSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterTrackSync.kt @@ -3,9 +3,17 @@ package eu.kanade.tachiyomi.util.chapter import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Track +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.track.DelayedTrackingUpdateJob +import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService +import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.launchIO +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get /** * Helper method for syncing a remote track with the local chapters, and back @@ -40,3 +48,68 @@ fun syncChaptersWithTrackServiceTwoWay(db: DatabaseHelper, chapters: List>() + +/** + * Starts the service that updates the last chapter read in sync services. This operation + * will run in a background thread and errors are ignored. + */ +fun updateTrackChapterMarkedAsRead( + db: DatabaseHelper, + preferences: PreferencesHelper, + newLastChapter: Chapter?, + mangaId: Long?, + delay: Long = 3000, + fetchTracks: (suspend () -> Unit)? = null +) { + if (!preferences.trackMarkedAsRead()) return + mangaId ?: return + + val newChapterRead = newLastChapter?.chapter_number?.toInt() ?: 0 + + // To avoid unnecessary calls if multiple marked as read for same manga + if (trackingJobs[mangaId]?.second ?: 0 < newChapterRead) { + trackingJobs[mangaId]?.first?.cancel() + + // We want these to execute even if the presenter is destroyed + trackingJobs[mangaId] = launchIO { + delay(delay) + updateTrackChapterRead(db, preferences, mangaId, newChapterRead) + fetchTracks?.invoke() + trackingJobs.remove(mangaId) + } to newChapterRead + } +} + +suspend fun updateTrackChapterRead( + db: DatabaseHelper, + preferences: PreferencesHelper, + mangaId: Long?, + newChapterRead: Int, + retryWhenOnline: Boolean = false +) { + val trackManager = Injekt.get() + val trackList = db.getTracks(mangaId).executeAsBlocking() + trackList.map { track -> + val service = trackManager.getService(track.sync_id) + if (service != null && service.isLogged && newChapterRead > track.last_chapter_read) { + if (retryWhenOnline && !preferences.context.isOnline()) { + val trackings = preferences.trackingsToAddOnline().get().toMutableSet() + val currentTracking = trackings.find { it.startsWith("$mangaId:${track.sync_id}:") } + trackings.remove(currentTracking) + trackings.add("$mangaId:${track.sync_id}:$newChapterRead") + preferences.trackingsToAddOnline().set(trackings) + DelayedTrackingUpdateJob.setupTask(preferences.context) + } else if (preferences.context.isOnline()) { + try { + track.last_chapter_read = newChapterRead + service.update(track, true) + db.insertTrack(track).executeAsBlocking() + } catch (e: Exception) { + Timber.e(e) + } + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44ac85d832..ca57444714 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -562,7 +562,8 @@ One-way sync to update the chapter progress in tracking services. Set up tracking for individual manga entries from their tracking button. Enhanced services Services that provide enhanced features for specific sources. Manga are automatically tracked when added to your library. - Sync chapters after reading + Update tracking after reading + Update tracking when marked as read Track when adding to library Only applies to silent trackers, such as Komga Reading