Reader: Save reading progress with SQLDelight (#7185)
* Use SQLDelight in reader to update history * Move chapter progress to sqldelight * Review Changes Co-Authored-By: inorichi <len@kanade.eu> * Review Changes 2 Co-authored-by: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Co-authored-by: inorichi <len@kanade.eu>
This commit is contained in:
parent
6b14f38cfa
commit
809da49301
3
app/src/main/java/eu/kanade/data/DatabaseUtils.kt
Normal file
3
app/src/main/java/eu/kanade/data/DatabaseUtils.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package eu.kanade.data
|
||||||
|
|
||||||
|
fun Boolean.toLong() = if (this) 1L else 0L
|
@ -0,0 +1,36 @@
|
|||||||
|
package eu.kanade.data.chapter
|
||||||
|
|
||||||
|
import eu.kanade.data.DatabaseHandler
|
||||||
|
import eu.kanade.data.toLong
|
||||||
|
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||||
|
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||||
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import logcat.LogPriority
|
||||||
|
|
||||||
|
class ChapterRepositoryImpl(
|
||||||
|
private val databaseHandler: DatabaseHandler,
|
||||||
|
) : ChapterRepository {
|
||||||
|
|
||||||
|
override suspend fun update(chapterUpdate: ChapterUpdate) {
|
||||||
|
try {
|
||||||
|
databaseHandler.await {
|
||||||
|
chaptersQueries.update(
|
||||||
|
chapterUpdate.mangaId,
|
||||||
|
chapterUpdate.url,
|
||||||
|
chapterUpdate.name,
|
||||||
|
chapterUpdate.scanlator,
|
||||||
|
chapterUpdate.read?.toLong(),
|
||||||
|
chapterUpdate.bookmark?.toLong(),
|
||||||
|
chapterUpdate.lastPageRead,
|
||||||
|
chapterUpdate.chapterNumber?.toDouble(),
|
||||||
|
chapterUpdate.sourceOrder,
|
||||||
|
chapterUpdate.dateFetch,
|
||||||
|
chapterUpdate.dateUpload,
|
||||||
|
chapterId = chapterUpdate.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,16 +4,17 @@ import eu.kanade.domain.history.model.History
|
|||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
val historyMapper: (Long, Long, Date?, Date?) -> History = { id, chapterId, readAt, _ ->
|
val historyMapper: (Long, Long, Date?, Long) -> History = { id, chapterId, readAt, readDuration ->
|
||||||
History(
|
History(
|
||||||
id = id,
|
id = id,
|
||||||
chapterId = chapterId,
|
chapterId = chapterId,
|
||||||
readAt = readAt,
|
readAt = readAt,
|
||||||
|
readDuration = readDuration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val historyWithRelationsMapper: (Long, Long, Long, String, String?, Float, Date?) -> HistoryWithRelations = {
|
val historyWithRelationsMapper: (Long, Long, Long, String, String?, Float, Date?, Long) -> HistoryWithRelations = {
|
||||||
historyId, mangaId, chapterId, title, thumbnailUrl, chapterNumber, readAt ->
|
historyId, mangaId, chapterId, title, thumbnailUrl, chapterNumber, readAt, readDuration ->
|
||||||
HistoryWithRelations(
|
HistoryWithRelations(
|
||||||
id = historyId,
|
id = historyId,
|
||||||
chapterId = chapterId,
|
chapterId = chapterId,
|
||||||
@ -22,5 +23,6 @@ val historyWithRelationsMapper: (Long, Long, Long, String, String?, Float, Date?
|
|||||||
thumbnailUrl = thumbnailUrl ?: "",
|
thumbnailUrl = thumbnailUrl ?: "",
|
||||||
chapterNumber = chapterNumber,
|
chapterNumber = chapterNumber,
|
||||||
readAt = readAt,
|
readAt = readAt,
|
||||||
|
readDuration = readDuration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import eu.kanade.data.DatabaseHandler
|
|||||||
import eu.kanade.data.chapter.chapterMapper
|
import eu.kanade.data.chapter.chapterMapper
|
||||||
import eu.kanade.data.manga.mangaMapper
|
import eu.kanade.data.manga.mangaMapper
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
|
import eu.kanade.domain.history.model.HistoryUpdate
|
||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
import eu.kanade.domain.history.repository.HistoryRepository
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
@ -89,4 +90,28 @@ class HistoryRepositoryImpl(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun upsertHistory(historyUpdate: HistoryUpdate) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
handler.await {
|
||||||
|
historyQueries.insert(
|
||||||
|
historyUpdate.chapterId,
|
||||||
|
historyUpdate.readAt,
|
||||||
|
historyUpdate.sessionReadDuration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
handler.await {
|
||||||
|
historyQueries.update(
|
||||||
|
historyUpdate.readAt,
|
||||||
|
historyUpdate.sessionReadDuration,
|
||||||
|
historyUpdate.chapterId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, throwable = e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package eu.kanade.domain
|
package eu.kanade.domain
|
||||||
|
|
||||||
|
import eu.kanade.data.chapter.ChapterRepositoryImpl
|
||||||
import eu.kanade.data.history.HistoryRepositoryImpl
|
import eu.kanade.data.history.HistoryRepositoryImpl
|
||||||
import eu.kanade.data.manga.MangaRepositoryImpl
|
import eu.kanade.data.manga.MangaRepositoryImpl
|
||||||
import eu.kanade.data.source.SourceRepositoryImpl
|
import eu.kanade.data.source.SourceRepositoryImpl
|
||||||
|
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||||
|
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionUpdates
|
import eu.kanade.domain.extension.interactor.GetExtensionUpdates
|
||||||
@ -12,6 +15,7 @@ import eu.kanade.domain.history.interactor.GetHistory
|
|||||||
import eu.kanade.domain.history.interactor.GetNextChapterForManga
|
import eu.kanade.domain.history.interactor.GetNextChapterForManga
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||||
|
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
import eu.kanade.domain.history.repository.HistoryRepository
|
||||||
import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId
|
import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId
|
||||||
import eu.kanade.domain.manga.interactor.ResetViewerFlags
|
import eu.kanade.domain.manga.interactor.ResetViewerFlags
|
||||||
@ -38,9 +42,13 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetNextChapterForManga(get()) }
|
addFactory { GetNextChapterForManga(get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
|
addFactory { UpdateChapter(get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { DeleteHistoryTable(get()) }
|
addFactory { DeleteHistoryTable(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
|
addFactory { UpsertHistory(get()) }
|
||||||
addFactory { RemoveHistoryById(get()) }
|
addFactory { RemoveHistoryById(get()) }
|
||||||
addFactory { RemoveHistoryByMangaId(get()) }
|
addFactory { RemoveHistoryByMangaId(get()) }
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||||
|
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||||
|
|
||||||
|
class UpdateChapter(
|
||||||
|
private val chapterRepository: ChapterRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(chapterUpdate: ChapterUpdate) {
|
||||||
|
chapterRepository.update(chapterUpdate)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package eu.kanade.domain.chapter.model
|
||||||
|
|
||||||
|
data class ChapterUpdate(
|
||||||
|
val id: Long,
|
||||||
|
val mangaId: Long? = null,
|
||||||
|
val read: Boolean? = null,
|
||||||
|
val bookmark: Boolean? = null,
|
||||||
|
val lastPageRead: Long? = null,
|
||||||
|
val dateFetch: Long? = null,
|
||||||
|
val sourceOrder: Long? = null,
|
||||||
|
val url: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
val dateUpload: Long? = null,
|
||||||
|
val chapterNumber: Float? = null,
|
||||||
|
val scanlator: String? = null,
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package eu.kanade.domain.chapter.repository
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||||
|
|
||||||
|
interface ChapterRepository {
|
||||||
|
|
||||||
|
suspend fun update(chapterUpdate: ChapterUpdate)
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.domain.history.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.history.model.HistoryUpdate
|
||||||
|
import eu.kanade.domain.history.repository.HistoryRepository
|
||||||
|
|
||||||
|
class UpsertHistory(
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(historyUpdate: HistoryUpdate) {
|
||||||
|
historyRepository.upsertHistory(historyUpdate)
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,8 @@ package eu.kanade.domain.history.model
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
data class History(
|
data class History(
|
||||||
val id: Long?,
|
val id: Long,
|
||||||
val chapterId: Long,
|
val chapterId: Long,
|
||||||
val readAt: Date?,
|
val readAt: Date?,
|
||||||
|
val readDuration: Long,
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package eu.kanade.domain.history.model
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
data class HistoryUpdate(
|
||||||
|
val chapterId: Long,
|
||||||
|
val readAt: Date,
|
||||||
|
val sessionReadDuration: Long,
|
||||||
|
)
|
@ -10,4 +10,5 @@ data class HistoryWithRelations(
|
|||||||
val thumbnailUrl: String,
|
val thumbnailUrl: String,
|
||||||
val chapterNumber: Float,
|
val chapterNumber: Float,
|
||||||
val readAt: Date?,
|
val readAt: Date?,
|
||||||
|
val readDuration: Long,
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.domain.history.repository
|
|||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
|
import eu.kanade.domain.history.model.HistoryUpdate
|
||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||||
|
|
||||||
interface HistoryRepository {
|
interface HistoryRepository {
|
||||||
@ -15,4 +16,6 @@ interface HistoryRepository {
|
|||||||
suspend fun resetHistoryByMangaId(mangaId: Long)
|
suspend fun resetHistoryByMangaId(mangaId: Long)
|
||||||
|
|
||||||
suspend fun deleteAllHistory(): Boolean
|
suspend fun deleteAllHistory(): Boolean
|
||||||
|
|
||||||
|
suspend fun upsertHistory(historyUpdate: HistoryUpdate)
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,7 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
Database(
|
Database(
|
||||||
driver = get(),
|
driver = get(),
|
||||||
historyAdapter = History.Adapter(
|
historyAdapter = History.Adapter(
|
||||||
history_last_readAdapter = dateAdapter,
|
last_readAdapter = dateAdapter,
|
||||||
history_time_readAdapter = dateAdapter,
|
|
||||||
),
|
),
|
||||||
mangasAdapter = Mangas.Adapter(
|
mangasAdapter = Mangas.Adapter(
|
||||||
genreAdapter = listOfStringsAdapter,
|
genreAdapter = listOfStringsAdapter,
|
||||||
|
@ -23,7 +23,7 @@ interface History : Serializable {
|
|||||||
var last_read: Long
|
var last_read: Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total time chapter was read - todo not yet implemented
|
* Total time chapter was read
|
||||||
*/
|
*/
|
||||||
var time_read: Long
|
var time_read: Long
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class HistoryImpl : History {
|
|||||||
override var last_read: Long = 0
|
override var last_read: Long = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total time chapter was read - todo not yet implemented
|
* Total time chapter was read
|
||||||
*/
|
*/
|
||||||
override var time_read: Long = 0
|
override var time_read: Long = 0
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
presenter.saveProgress()
|
presenter.saveCurrentChapterReadingProgress()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +242,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
*/
|
*/
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
presenter.setReadStartTime()
|
||||||
setMenuVisibility(menuVisible, animate = false)
|
setMenuVisibility(menuVisible, animate = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,12 @@ import android.app.Application
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
|
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||||
|
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||||
|
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||||
|
import eu.kanade.domain.history.model.HistoryUpdate
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@ -62,6 +65,8 @@ class ReaderPresenter(
|
|||||||
private val coverCache: CoverCache = Injekt.get(),
|
private val coverCache: CoverCache = Injekt.get(),
|
||||||
private val preferences: PreferencesHelper = Injekt.get(),
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
|
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
|
||||||
|
private val upsertHistory: UpsertHistory = Injekt.get(),
|
||||||
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
) : BasePresenter<ReaderActivity>() {
|
) : BasePresenter<ReaderActivity>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,6 +85,11 @@ class ReaderPresenter(
|
|||||||
*/
|
*/
|
||||||
private var loader: ChapterLoader? = null
|
private var loader: ChapterLoader? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time the chapter was started reading
|
||||||
|
*/
|
||||||
|
private var chapterReadStartTime: Long? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription to prevent setting chapters as active from multiple threads.
|
* Subscription to prevent setting chapters as active from multiple threads.
|
||||||
*/
|
*/
|
||||||
@ -168,8 +178,7 @@ class ReaderPresenter(
|
|||||||
val currentChapters = viewerChaptersRelay.value
|
val currentChapters = viewerChaptersRelay.value
|
||||||
if (currentChapters != null) {
|
if (currentChapters != null) {
|
||||||
currentChapters.unref()
|
currentChapters.unref()
|
||||||
saveChapterProgress(currentChapters.currChapter)
|
saveReadingProgress(currentChapters.currChapter)
|
||||||
saveChapterHistory(currentChapters.currChapter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +209,9 @@ class ReaderPresenter(
|
|||||||
*/
|
*/
|
||||||
fun onSaveInstanceStateNonConfigurationChange() {
|
fun onSaveInstanceStateNonConfigurationChange() {
|
||||||
val currentChapter = getCurrentChapter() ?: return
|
val currentChapter = getCurrentChapter() ?: return
|
||||||
saveChapterProgress(currentChapter)
|
launchIO {
|
||||||
|
saveChapterProgress(currentChapter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -397,7 +408,7 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
if (selectedChapter != currentChapters.currChapter) {
|
if (selectedChapter != currentChapters.currChapter) {
|
||||||
logcat { "Setting ${selectedChapter.chapter.url} as active" }
|
logcat { "Setting ${selectedChapter.chapter.url} as active" }
|
||||||
onChapterChanged(currentChapters.currChapter)
|
saveReadingProgress(currentChapters.currChapter)
|
||||||
loadNewChapter(selectedChapter)
|
loadNewChapter(selectedChapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,43 +440,57 @@ class ReaderPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun saveCurrentChapterReadingProgress() {
|
||||||
* Called when a chapter changed from [fromChapter] to [toChapter]. It updates [fromChapter]
|
getCurrentChapter()?.let { saveReadingProgress(it) }
|
||||||
* on the database.
|
|
||||||
*/
|
|
||||||
private fun onChapterChanged(fromChapter: ReaderChapter) {
|
|
||||||
saveChapterProgress(fromChapter)
|
|
||||||
saveChapterHistory(fromChapter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves this [chapter] progress (last read page and whether it's read).
|
* Called when reader chapter is changed in reader or when activity is paused.
|
||||||
|
*/
|
||||||
|
private fun saveReadingProgress(readerChapter: ReaderChapter) {
|
||||||
|
launchIO {
|
||||||
|
saveChapterProgress(readerChapter)
|
||||||
|
saveChapterHistory(readerChapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves this [readerChapter] 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 fun saveChapterProgress(chapter: ReaderChapter) {
|
private suspend fun saveChapterProgress(readerChapter: ReaderChapter) {
|
||||||
if (!incognitoMode || hasTrackers) {
|
if (!incognitoMode || hasTrackers) {
|
||||||
db.updateChapterProgress(chapter.chapter).asRxCompletable()
|
val chapter = readerChapter.chapter
|
||||||
.onErrorComplete()
|
updateChapter.await(
|
||||||
.subscribeOn(Schedulers.io())
|
ChapterUpdate(
|
||||||
.subscribe()
|
id = chapter.id!!,
|
||||||
|
read = chapter.read,
|
||||||
|
bookmark = chapter.bookmark,
|
||||||
|
lastPageRead = chapter.last_page_read.toLong(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves this [chapter] last read history if incognito mode isn't on.
|
* Saves this [readerChapter] last read history if incognito mode isn't on.
|
||||||
*/
|
*/
|
||||||
private fun saveChapterHistory(chapter: ReaderChapter) {
|
private suspend fun saveChapterHistory(readerChapter: ReaderChapter) {
|
||||||
if (!incognitoMode) {
|
if (!incognitoMode) {
|
||||||
val history = History.create(chapter.chapter).apply { last_read = Date().time }
|
val chapterId = readerChapter.chapter.id!!
|
||||||
db.upsertHistoryLastRead(history).asRxCompletable()
|
val readAt = Date()
|
||||||
.onErrorComplete()
|
val sessionReadDuration = chapterReadStartTime?.let { readAt.time - it } ?: 0
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.subscribe()
|
upsertHistory.await(
|
||||||
|
HistoryUpdate(chapterId, readAt, sessionReadDuration),
|
||||||
|
).also {
|
||||||
|
chapterReadStartTime = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveProgress() {
|
fun setReadStartTime() {
|
||||||
getCurrentChapter()?.let { onChapterChanged(it) }
|
chapterReadStartTime = Date().time
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -633,7 +658,7 @@ class ReaderPresenter(
|
|||||||
* Shares the image of this [page] and notifies the UI with the path of the file to share.
|
* Shares the image of this [page] and notifies the UI with the path of the file to share.
|
||||||
* The image must be first copied to the internal partition because there are many possible
|
* The image must be first copied to the internal partition because there are many possible
|
||||||
* formats it can come from, like a zipped chapter, in which case it's not possible to directly
|
* formats it can come from, like a zipped chapter, in which case it's not possible to directly
|
||||||
* get a path to the file and it has to be decompresssed somewhere first. Only the last shared
|
* get a path to the file and it has to be decompressed somewhere first. Only the last shared
|
||||||
* image will be kept so it won't be taking lots of internal disk space.
|
* image will be kept so it won't be taking lots of internal disk space.
|
||||||
*/
|
*/
|
||||||
fun shareImage(page: ReaderPage) {
|
fun shareImage(page: ReaderPage) {
|
||||||
|
@ -26,4 +26,19 @@ WHERE _id = :id;
|
|||||||
getChapterByMangaId:
|
getChapterByMangaId:
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM chapters
|
FROM chapters
|
||||||
WHERE manga_id = :mangaId;
|
WHERE manga_id = :mangaId;
|
||||||
|
|
||||||
|
update:
|
||||||
|
UPDATE chapters
|
||||||
|
SET manga_id = coalesce(:mangaId, manga_id),
|
||||||
|
url = coalesce(:url, url),
|
||||||
|
name = coalesce(:name, name),
|
||||||
|
scanlator = coalesce(:scanlator, scanlator),
|
||||||
|
read = coalesce(:read, read),
|
||||||
|
bookmark = coalesce(:bookmark, bookmark),
|
||||||
|
last_page_read = coalesce(:lastPageRead, last_page_read),
|
||||||
|
chapter_number = coalesce(:chapterNumber, chapter_number),
|
||||||
|
source_order = coalesce(:sourceOrder, source_order),
|
||||||
|
date_fetch = coalesce(:dateFetch, date_fetch),
|
||||||
|
date_upload = coalesce(:dateUpload, date_upload)
|
||||||
|
WHERE _id = :chapterId;
|
@ -1,31 +1,31 @@
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
CREATE TABLE history(
|
CREATE TABLE history(
|
||||||
history_id INTEGER NOT NULL PRIMARY KEY,
|
_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
history_chapter_id INTEGER NOT NULL UNIQUE,
|
chapter_id INTEGER NOT NULL UNIQUE,
|
||||||
history_last_read INTEGER AS Date,
|
last_read INTEGER AS Date,
|
||||||
history_time_read INTEGER AS Date,
|
time_read INTEGER NOT NULL,
|
||||||
FOREIGN KEY(history_chapter_id) REFERENCES chapters (_id)
|
FOREIGN KEY(chapter_id) REFERENCES chapters (_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX history_history_chapter_id_index ON history(history_chapter_id);
|
CREATE INDEX history_history_chapter_id_index ON history(chapter_id);
|
||||||
|
|
||||||
resetHistoryById:
|
resetHistoryById:
|
||||||
UPDATE history
|
UPDATE history
|
||||||
SET history_last_read = 0
|
SET last_read = 0
|
||||||
WHERE history_id = :historyId;
|
WHERE _id = :historyId;
|
||||||
|
|
||||||
resetHistoryByMangaId:
|
resetHistoryByMangaId:
|
||||||
UPDATE history
|
UPDATE history
|
||||||
SET history_last_read = 0
|
SET last_read = 0
|
||||||
WHERE history_id IN (
|
WHERE _id IN (
|
||||||
SELECT H.history_id
|
SELECT H._id
|
||||||
FROM mangas M
|
FROM mangas M
|
||||||
INNER JOIN chapters C
|
INNER JOIN chapters C
|
||||||
ON M._id = C.manga_id
|
ON M._id = C.manga_id
|
||||||
INNER JOIN history H
|
INNER JOIN history H
|
||||||
ON C._id = H.history_chapter_id
|
ON C._id = H.chapter_id
|
||||||
WHERE M._id = :mangaId
|
WHERE M._id = :mangaId
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -34,4 +34,14 @@ DELETE FROM history;
|
|||||||
|
|
||||||
removeResettedHistory:
|
removeResettedHistory:
|
||||||
DELETE FROM history
|
DELETE FROM history
|
||||||
WHERE history_last_read = 0;
|
WHERE last_read = 0;
|
||||||
|
|
||||||
|
insert:
|
||||||
|
INSERT INTO history(chapter_id, last_read, time_read)
|
||||||
|
VALUES (:chapterId, :readAt, :readDuration);
|
||||||
|
|
||||||
|
update:
|
||||||
|
UPDATE history
|
||||||
|
SET last_read = :readAt,
|
||||||
|
time_read = time_read + :sessionReadDuration
|
||||||
|
WHERE chapter_id = :chapterId;
|
||||||
|
52
app/src/main/sqldelight/migrations/15.sqm
Normal file
52
app/src/main/sqldelight/migrations/15.sqm
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS history_history_chapter_id_index;
|
||||||
|
DROP VIEW IF EXISTS historyView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [last_read] was made not-null
|
||||||
|
* [time_read] was kept as long and made non-null
|
||||||
|
* `history` prefix was removed from table name
|
||||||
|
*/
|
||||||
|
ALTER TABLE history RENAME TO history_temp;
|
||||||
|
CREATE TABLE history(
|
||||||
|
_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
chapter_id INTEGER NOT NULL UNIQUE,
|
||||||
|
last_read INTEGER AS Date NOT NULL,
|
||||||
|
time_read INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(chapter_id) REFERENCES chapters (_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO history
|
||||||
|
SELECT history_id, history_chapter_id, coalesce(history_last_read, 0), coalesce(history_time_read, 0)
|
||||||
|
FROM history_temp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [history.time_read] was added as a column in [historyView]
|
||||||
|
*/
|
||||||
|
CREATE VIEW historyView AS
|
||||||
|
SELECT
|
||||||
|
history._id AS id,
|
||||||
|
mangas._id AS mangaId,
|
||||||
|
chapters._id AS chapterId,
|
||||||
|
mangas.title,
|
||||||
|
mangas.thumbnail_url AS thumbnailUrl,
|
||||||
|
chapters.chapter_number AS chapterNumber,
|
||||||
|
history.last_read AS readAt,
|
||||||
|
history.time_read AS readDuration,
|
||||||
|
max_last_read.last_read AS maxReadAt,
|
||||||
|
max_last_read.chapter_id AS maxReadAtChapterId
|
||||||
|
FROM mangas
|
||||||
|
JOIN chapters
|
||||||
|
ON mangas._id = chapters.manga_id
|
||||||
|
JOIN history
|
||||||
|
ON chapters._id = history.chapter_id
|
||||||
|
JOIN (
|
||||||
|
SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read
|
||||||
|
FROM chapters JOIN history
|
||||||
|
ON chapters._id = history.chapter_id
|
||||||
|
GROUP BY chapters.manga_id
|
||||||
|
) AS max_last_read
|
||||||
|
ON chapters.manga_id = max_last_read.manga_id;
|
||||||
|
|
||||||
|
CREATE INDEX history_history_chapter_id_index ON history(chapter_id);
|
@ -1,24 +1,25 @@
|
|||||||
CREATE VIEW historyView AS
|
CREATE VIEW historyView AS
|
||||||
SELECT
|
SELECT
|
||||||
history.history_id AS id,
|
history._id AS id,
|
||||||
mangas._id AS mangaId,
|
mangas._id AS mangaId,
|
||||||
chapters._id AS chapterId,
|
chapters._id AS chapterId,
|
||||||
mangas.title,
|
mangas.title,
|
||||||
mangas.thumbnail_url AS thumnailUrl,
|
mangas.thumbnail_url AS thumbnailUrl,
|
||||||
chapters.chapter_number AS chapterNumber,
|
chapters.chapter_number AS chapterNumber,
|
||||||
history.history_last_read AS readAt,
|
history.last_read AS readAt,
|
||||||
max_last_read.history_last_read AS maxReadAt,
|
history.time_read AS readDuration,
|
||||||
max_last_read.history_chapter_id AS maxReadAtChapterId
|
max_last_read.last_read AS maxReadAt,
|
||||||
|
max_last_read.chapter_id AS maxReadAtChapterId
|
||||||
FROM mangas
|
FROM mangas
|
||||||
JOIN chapters
|
JOIN chapters
|
||||||
ON mangas._id = chapters.manga_id
|
ON mangas._id = chapters.manga_id
|
||||||
JOIN history
|
JOIN history
|
||||||
ON chapters._id = history.history_chapter_id
|
ON chapters._id = history.chapter_id
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT chapters.manga_id,chapters._id AS history_chapter_id, MAX(history.history_last_read) AS history_last_read
|
SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read
|
||||||
FROM chapters JOIN history
|
FROM chapters JOIN history
|
||||||
ON chapters._id = history.history_chapter_id
|
ON chapters._id = history.chapter_id
|
||||||
GROUP BY chapters.manga_id
|
GROUP BY chapters.manga_id
|
||||||
) AS max_last_read
|
) AS max_last_read
|
||||||
ON chapters.manga_id = max_last_read.manga_id;
|
ON chapters.manga_id = max_last_read.manga_id;
|
||||||
|
|
||||||
@ -35,9 +36,10 @@ id,
|
|||||||
mangaId,
|
mangaId,
|
||||||
chapterId,
|
chapterId,
|
||||||
title,
|
title,
|
||||||
thumnailUrl,
|
thumbnailUrl,
|
||||||
chapterNumber,
|
chapterNumber,
|
||||||
readAt
|
readAt,
|
||||||
|
readDuration
|
||||||
FROM historyView
|
FROM historyView
|
||||||
WHERE historyView.readAt > 0
|
WHERE historyView.readAt > 0
|
||||||
AND maxReadAtChapterId = historyView.chapterId
|
AND maxReadAtChapterId = historyView.chapterId
|
||||||
|
Loading…
Reference in New Issue
Block a user