mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
Save read duration (#1360)
* Save read duration * Set readStartTime when switching chapters in a single reader session
This commit is contained in:
parent
26dae149a7
commit
0cf6393ad6
12 changed files with 65 additions and 105 deletions
|
@ -184,7 +184,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||||
if (historyForManga.isNotEmpty()) {
|
if (historyForManga.isNotEmpty()) {
|
||||||
val history = historyForManga.mapNotNull { history ->
|
val history = historyForManga.mapNotNull { history ->
|
||||||
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
||||||
url?.let { BackupHistory(url, history.last_read) }
|
url?.let { BackupHistory(url, history.last_read, history.time_read) }
|
||||||
}
|
}
|
||||||
if (history.isNotEmpty()) {
|
if (history.isNotEmpty()) {
|
||||||
mangaObject.history = history
|
mangaObject.history = history
|
||||||
|
@ -283,12 +283,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||||
internal fun restoreHistoryForManga(history: List<BackupHistory>) {
|
internal fun restoreHistoryForManga(history: List<BackupHistory>) {
|
||||||
// List containing history to be updated
|
// List containing history to be updated
|
||||||
val historyToBeUpdated = ArrayList<History>(history.size)
|
val historyToBeUpdated = ArrayList<History>(history.size)
|
||||||
for ((url, lastRead) in history) {
|
for ((url, lastRead, readDuration) in history) {
|
||||||
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
||||||
// Check if history already in database and update
|
// Check if history already in database and update
|
||||||
if (dbHistory != null) {
|
if (dbHistory != null) {
|
||||||
dbHistory.apply {
|
dbHistory.apply {
|
||||||
last_read = max(lastRead, dbHistory.last_read)
|
last_read = max(lastRead, dbHistory.last_read)
|
||||||
|
time_read = max(readDuration, dbHistory.time_read)
|
||||||
}
|
}
|
||||||
historyToBeUpdated.add(dbHistory)
|
historyToBeUpdated.add(dbHistory)
|
||||||
} else {
|
} else {
|
||||||
|
@ -296,6 +297,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||||
databaseHelper.getChapter(url).executeAsBlocking()?.let {
|
databaseHelper.getChapter(url).executeAsBlocking()?.let {
|
||||||
val historyToAdd = History.create(it).apply {
|
val historyToAdd = History.create(it).apply {
|
||||||
last_read = lastRead
|
last_read = lastRead
|
||||||
|
time_read = readDuration
|
||||||
}
|
}
|
||||||
historyToBeUpdated.add(historyToAdd)
|
historyToBeUpdated.add(historyToAdd)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
|
||||||
val manga = backupManga.getMangaImpl()
|
val manga = backupManga.getMangaImpl()
|
||||||
val chapters = backupManga.getChaptersImpl()
|
val chapters = backupManga.getChaptersImpl()
|
||||||
val categories = backupManga.categories
|
val categories = backupManga.categories
|
||||||
val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history
|
val history =
|
||||||
|
backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } + backupManga.history
|
||||||
val tracks = backupManga.getTrackingImpl()
|
val tracks = backupManga.getTrackingImpl()
|
||||||
val customManga = backupManga.getCustomMangaInfo()
|
val customManga = backupManga.getCustomMangaInfo()
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,12 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
data class BrokenBackupHistory(
|
data class BrokenBackupHistory(
|
||||||
@ProtoNumber(0) var url: String,
|
@ProtoNumber(0) var url: String,
|
||||||
@ProtoNumber(1) var lastRead: Long,
|
@ProtoNumber(1) var lastRead: Long,
|
||||||
|
@ProtoNumber(2) var readDuration: Long = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BackupHistory(
|
data class BackupHistory(
|
||||||
@ProtoNumber(1) var url: String,
|
@ProtoNumber(1) var url: String,
|
||||||
@ProtoNumber(2) var lastRead: Long,
|
@ProtoNumber(2) var lastRead: Long,
|
||||||
|
@ProtoNumber(3) var readDuration: Long = 0,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.HistoryUpsertResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.HistoryUpsertResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||||
|
@ -122,9 +121,9 @@ interface HistoryQueries : DbProvider {
|
||||||
* Inserts history object if not yet in database
|
* Inserts history object if not yet in database
|
||||||
* @param history history object
|
* @param history history object
|
||||||
*/
|
*/
|
||||||
fun updateHistoryLastRead(history: History) = db.put()
|
fun upsertHistoryLastRead(history: History) = db.put()
|
||||||
.`object`(history)
|
.`object`(history)
|
||||||
.withPutResolver(HistoryLastReadPutResolver())
|
.withPutResolver(HistoryUpsertResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,16 +136,6 @@ interface HistoryQueries : DbProvider {
|
||||||
.withPutResolver(HistoryUpsertResolver())
|
.withPutResolver(HistoryUpsertResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the history last read.
|
|
||||||
* Inserts history object if not yet in database
|
|
||||||
* @param historyList history object list
|
|
||||||
*/
|
|
||||||
fun updateHistoryLastRead(historyList: List<History>) = db.put()
|
|
||||||
.objects(historyList)
|
|
||||||
.withPutResolver(HistoryLastReadPutResolver())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun deleteHistory() = db.delete()
|
fun deleteHistory() = db.delete()
|
||||||
.byQuery(
|
.byQuery(
|
||||||
DeleteQuery.builder()
|
DeleteQuery.builder()
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.data.database.resolvers
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull
|
|
||||||
import androidx.core.content.contentValuesOf
|
|
||||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
|
||||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
|
||||||
import eu.kanade.tachiyomi.data.database.mappers.HistoryPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
|
||||||
|
|
||||||
class HistoryLastReadPutResolver : HistoryPutResolver() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates last_read time of chapter
|
|
||||||
*/
|
|
||||||
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
|
|
||||||
val updateQuery = mapToUpdateQuery(history)
|
|
||||||
|
|
||||||
val cursor = db.lowLevel().query(
|
|
||||||
Query.builder()
|
|
||||||
.table(updateQuery.table())
|
|
||||||
.where(updateQuery.where())
|
|
||||||
.whereArgs(updateQuery.whereArgs())
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val putResult = cursor.use { putCursor ->
|
|
||||||
if (putCursor.count == 0) {
|
|
||||||
val insertQuery = mapToInsertQuery(history)
|
|
||||||
val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history))
|
|
||||||
PutResult.newInsertResult(insertedId, insertQuery.table())
|
|
||||||
} else {
|
|
||||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history))
|
|
||||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
putResult
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates update query
|
|
||||||
* @param obj history object
|
|
||||||
*/
|
|
||||||
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
|
|
||||||
.table(HistoryTable.TABLE)
|
|
||||||
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
|
|
||||||
.whereArgs(obj.chapter_id)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create content query
|
|
||||||
* @param history object
|
|
||||||
*/
|
|
||||||
private fun mapToUpdateContentValues(history: History) =
|
|
||||||
contentValuesOf(
|
|
||||||
HistoryTable.COL_LAST_READ to history.last_read,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -48,5 +48,6 @@ class HistoryUpsertResolver : HistoryPutResolver() {
|
||||||
private fun mapToUpdateContentValues(history: History) =
|
private fun mapToUpdateContentValues(history: History) =
|
||||||
contentValuesOf(
|
contentValuesOf(
|
||||||
HistoryTable.COL_LAST_READ to history.last_read,
|
HistoryTable.COL_LAST_READ to history.last_read,
|
||||||
|
HistoryTable.COL_TIME_READ to history.time_read,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,10 @@ class MigrationProcessAdapter(
|
||||||
prevHistoryList.find { it.chapter_id == prevChapter.id }
|
prevHistoryList.find { it.chapter_id == prevChapter.id }
|
||||||
?.let { prevHistory ->
|
?.let { prevHistory ->
|
||||||
val history = History.create(chapter)
|
val history = History.create(chapter)
|
||||||
.apply { last_read = prevHistory.last_read }
|
.apply {
|
||||||
|
last_read = prevHistory.last_read
|
||||||
|
time_read = prevHistory.time_read
|
||||||
|
}
|
||||||
historyList.add(history)
|
historyList.add(history)
|
||||||
}
|
}
|
||||||
} else if (chapter.chapter_number <= maxChapterRead) {
|
} else if (chapter.chapter_number <= maxChapterRead) {
|
||||||
|
@ -173,7 +176,7 @@ class MigrationProcessAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.insertChapters(dbChapters).executeAsBlocking()
|
db.insertChapters(dbChapters).executeAsBlocking()
|
||||||
db.updateHistoryLastRead(historyList).executeAsBlocking()
|
db.upsertHistoryLastRead(historyList).executeAsBlocking()
|
||||||
}
|
}
|
||||||
// Update categories
|
// Update categories
|
||||||
if (MigrationFlags.hasCategories(flags)) {
|
if (MigrationFlags.hasCategories(flags)) {
|
||||||
|
|
|
@ -1114,10 +1114,15 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
presenter.saveProgress()
|
presenter.saveCurrentChapterReadingProgress()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
presenter.setReadStartTime()
|
||||||
|
}
|
||||||
|
|
||||||
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
|
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
|
||||||
val pViewer = viewer as? PagerViewer ?: return
|
val pViewer = viewer as? PagerViewer ?: return
|
||||||
pViewer.updateShifting()
|
pViewer.updateShifting()
|
||||||
|
|
|
@ -94,6 +94,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.
|
||||||
*/
|
*/
|
||||||
|
@ -184,8 +189,7 @@ class ReaderPresenter(
|
||||||
val isChapterDownloaded = currentChapters.currChapter.pageLoader is DownloadPageLoader
|
val isChapterDownloaded = currentChapters.currChapter.pageLoader is DownloadPageLoader
|
||||||
currentChapters.unref()
|
currentChapters.unref()
|
||||||
val currentChapter = currentChapters.currChapter
|
val currentChapter = currentChapters.currChapter
|
||||||
saveChapterProgress(currentChapter)
|
saveReadingProgress(currentChapter)
|
||||||
saveChapterHistory(currentChapter)
|
|
||||||
val currentChapterPageCount = currentChapter.chapter.last_page_read + currentChapter.chapter.pages_left
|
val currentChapterPageCount = currentChapter.chapter.last_page_read + currentChapter.chapter.pages_left
|
||||||
if ((currentChapter.chapter.last_page_read + 1.0) / currentChapterPageCount > 0.33 || isAnyPrevChapterDownloaded) {
|
if ((currentChapter.chapter.last_page_read + 1.0) / currentChapterPageCount > 0.33 || isAnyPrevChapterDownloaded) {
|
||||||
downloadNextChapters(isChapterDownloaded)
|
downloadNextChapters(isChapterDownloaded)
|
||||||
|
@ -425,7 +429,7 @@ class ReaderPresenter(
|
||||||
fun loadChapter(chapter: Chapter) {
|
fun loadChapter(chapter: Chapter) {
|
||||||
val loader = loader ?: return
|
val loader = loader ?: return
|
||||||
|
|
||||||
viewerChaptersRelay.value?.currChapter?.let(::onChapterChanged)
|
viewerChaptersRelay.value?.currChapter?.let(::saveReadingProgress)
|
||||||
|
|
||||||
Timber.d("Loading ${chapter.url}")
|
Timber.d("Loading ${chapter.url}")
|
||||||
|
|
||||||
|
@ -532,7 +536,8 @@ class ReaderPresenter(
|
||||||
|
|
||||||
if (selectedChapter != currentChapters.currChapter) {
|
if (selectedChapter != currentChapters.currChapter) {
|
||||||
Timber.d("Setting ${selectedChapter.chapter.url} as active")
|
Timber.d("Setting ${selectedChapter.chapter.url} as active")
|
||||||
onChapterChanged(currentChapters.currChapter)
|
saveReadingProgress(currentChapters.currChapter)
|
||||||
|
setReadStartTime()
|
||||||
loadNewChapter(selectedChapter)
|
loadNewChapter(selectedChapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -592,39 +597,49 @@ class ReaderPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a chapter changed from [fromChapter] to [toChapter]. It updates [fromChapter]
|
* Called when reader chapter is changed in reader or when activity is paused.
|
||||||
* on the database.
|
|
||||||
*/
|
*/
|
||||||
private fun onChapterChanged(fromChapter: ReaderChapter) {
|
private fun saveReadingProgress(readerChapter: ReaderChapter) {
|
||||||
saveChapterProgress(fromChapter)
|
saveChapterProgress(readerChapter)
|
||||||
saveChapterHistory(fromChapter)
|
saveChapterHistory(readerChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveProgress() = getCurrentChapter()?.let { onChapterChanged(it) }
|
fun saveCurrentChapterReadingProgress() = getCurrentChapter()?.let { saveReadingProgress(it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves this [chapter] progress (last read page and whether it's read).
|
* 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 fun saveChapterProgress(readerChapter: ReaderChapter) {
|
||||||
db.getChapter(chapter.chapter.id!!).executeAsBlocking()?.let { dbChapter ->
|
db.getChapter(readerChapter.chapter.id!!).executeAsBlocking()?.let { dbChapter ->
|
||||||
chapter.chapter.bookmark = dbChapter.bookmark
|
readerChapter.chapter.bookmark = dbChapter.bookmark
|
||||||
}
|
}
|
||||||
if (!preferences.incognitoMode().get() || hasTrackers) {
|
if (!preferences.incognitoMode().get() || hasTrackers) {
|
||||||
db.updateChapterProgress(chapter.chapter).executeAsBlocking()
|
db.updateChapterProgress(readerChapter.chapter).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves this [chapter] last read history.
|
* Saves this [readerChapter] last read history.
|
||||||
*/
|
*/
|
||||||
private fun saveChapterHistory(chapter: ReaderChapter) {
|
private fun saveChapterHistory(readerChapter: ReaderChapter) {
|
||||||
if (!preferences.incognitoMode().get()) {
|
if (!preferences.incognitoMode().get()) {
|
||||||
val history = History.create(chapter.chapter).apply { last_read = Date().time }
|
val readAt = Date().time
|
||||||
db.updateHistoryLastRead(history).executeAsBlocking()
|
val sessionReadDuration = chapterReadStartTime?.let { readAt - it } ?: 0
|
||||||
|
val oldTimeRead = db.getHistoryByChapterUrl(readerChapter.chapter.url).executeAsBlocking()?.time_read ?: 0
|
||||||
|
val history = History.create(readerChapter.chapter).apply {
|
||||||
|
last_read = readAt
|
||||||
|
time_read = sessionReadDuration + oldTimeRead
|
||||||
|
}
|
||||||
|
db.upsertHistoryLastRead(history).executeAsBlocking()
|
||||||
|
chapterReadStartTime = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setReadStartTime() {
|
||||||
|
chapterReadStartTime = Date().time
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the activity to preload the given [chapter].
|
* Called from the activity to preload the given [chapter].
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -488,7 +488,8 @@ class RecentsPresenter(
|
||||||
*/
|
*/
|
||||||
fun removeFromHistory(history: History) {
|
fun removeFromHistory(history: History) {
|
||||||
history.last_read = 0L
|
history.last_read = 0L
|
||||||
db.updateHistoryLastRead(history).executeAsBlocking()
|
history.time_read = 0L
|
||||||
|
db.upsertHistoryLastRead(history).executeAsBlocking()
|
||||||
getRecents()
|
getRecents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,8 +499,11 @@ class RecentsPresenter(
|
||||||
*/
|
*/
|
||||||
fun removeAllFromHistory(mangaId: Long) {
|
fun removeAllFromHistory(mangaId: Long) {
|
||||||
val history = db.getHistoryByMangaId(mangaId).executeAsBlocking()
|
val history = db.getHistoryByMangaId(mangaId).executeAsBlocking()
|
||||||
history.forEach { it.last_read = 0L }
|
history.forEach {
|
||||||
db.updateHistoryLastRead(history).executeAsBlocking()
|
it.last_read = 0L
|
||||||
|
it.time_read = 0L
|
||||||
|
}
|
||||||
|
db.upsertHistoryLastRead(history).executeAsBlocking()
|
||||||
getRecents()
|
getRecents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue