diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt b/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt index 0959b3362..1f348899b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt @@ -25,4 +25,5 @@ class BookmarkEntity( @ColumnInfo(name = "scroll") val scroll: Int, @ColumnInfo(name = "image") val imageUrl: String, @ColumnInfo(name = "created_at") val createdAt: Long, + @ColumnInfo(name = "percent") val percent: Float, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt b/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt index 981aa05ea..0ab69dd18 100644 --- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt +++ b/app/src/main/java/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt @@ -18,6 +18,7 @@ fun BookmarkEntity.toBookmark(manga: Manga) = Bookmark( scroll = scroll, imageUrl = imageUrl, createdAt = Date(createdAt), + percent = percent, ) fun Bookmark.toEntity() = BookmarkEntity( @@ -28,4 +29,5 @@ fun Bookmark.toEntity() = BookmarkEntity( scroll = scroll, imageUrl = imageUrl, createdAt = createdAt.time, + percent = percent, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt b/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt index 0b76c6537..5b6ff3bf0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt +++ b/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt @@ -11,6 +11,7 @@ class Bookmark( val scroll: Int, val imageUrl: String, val createdAt: Date, + val percent: Float, ) { override fun equals(other: Any?): Boolean { @@ -26,6 +27,7 @@ class Bookmark( if (scroll != other.scroll) return false if (imageUrl != other.imageUrl) return false if (createdAt != other.createdAt) return false + if (percent != other.percent) return false return true } @@ -38,6 +40,7 @@ class Bookmark( result = 31 * result + scroll result = 31 * result + imageUrl.hashCode() result = 31 * result + createdAt.hashCode() + result = 31 * result + percent.hashCode() return result } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt index 4b42b4c60..7212b8569 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupRepository.kt @@ -111,6 +111,7 @@ class BackupRepository(private val db: MangaDatabase) { jo.put("chapter_id", chapterId) jo.put("page", page) jo.put("scroll", scroll) + jo.put("percent", percent) return jo } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt index 57fd4d6ee..4e20b955f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/RestoreRepository.kt @@ -9,10 +9,7 @@ import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.history.data.HistoryEntity import org.koitharu.kotatsu.parsers.model.SortOrder -import org.koitharu.kotatsu.parsers.util.json.JSONIterator -import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault -import org.koitharu.kotatsu.parsers.util.json.getStringOrNull -import org.koitharu.kotatsu.parsers.util.json.mapJSON +import org.koitharu.kotatsu.parsers.util.json.* class RestoreRepository(private val db: MangaDatabase) { @@ -95,7 +92,8 @@ class RestoreRepository(private val db: MangaDatabase) { updatedAt = json.getLong("updated_at"), chapterId = json.getLong("chapter_id"), page = json.getInt("page"), - scroll = json.getDouble("scroll").toFloat() + scroll = json.getDouble("scroll").toFloat(), + percent = json.getFloatOrDefault("percent", -1f), ) private fun parseCategory(json: JSONObject) = FavouriteCategoryEntity( diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 2c74455f8..fbea719f5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -6,8 +6,14 @@ import androidx.room.Room import androidx.room.RoomDatabase import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity import org.koitharu.kotatsu.bookmarks.data.BookmarksDao -import org.koitharu.kotatsu.core.db.dao.* -import org.koitharu.kotatsu.core.db.entity.* +import org.koitharu.kotatsu.core.db.dao.MangaDao +import org.koitharu.kotatsu.core.db.dao.PreferencesDao +import org.koitharu.kotatsu.core.db.dao.TagsDao +import org.koitharu.kotatsu.core.db.dao.TrackLogsDao +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity +import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.migrations.* import org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity @@ -27,7 +33,7 @@ import org.koitharu.kotatsu.tracker.data.TracksDao FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class, ], - version = 11, + version = 12, ) abstract class MangaDatabase : RoomDatabase() { @@ -67,6 +73,7 @@ fun MangaDatabase(context: Context): MangaDatabase = Room.databaseBuilder( Migration8To9(), Migration9To10(), Migration10To11(), + Migration11To12(), ).addCallback( DatabasePrePopulateCallback(context.resources) ).build() \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt new file mode 100644 index 000000000..923c1b401 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.core.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration11To12 : Migration(11, 12) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE history ADD COLUMN `percent` REAL NOT NULL DEFAULT -1") + database.execSQL("ALTER TABLE bookmarks ADD COLUMN `percent` REAL NOT NULL DEFAULT -1") + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt index 95f736f2b..9ac085183 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaHistory.kt @@ -11,4 +11,5 @@ data class MangaHistory( val chapterId: Long, val page: Int, val scroll: Int, + val percent: Float, ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt index c03300b99..0e5624559 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt @@ -1,12 +1,13 @@ package org.koitharu.kotatsu.history.data -import java.util.* import org.koitharu.kotatsu.core.model.MangaHistory +import java.util.* fun HistoryEntity.toMangaHistory() = MangaHistory( createdAt = Date(createdAt), updatedAt = Date(updatedAt), chapterId = chapterId, page = page, - scroll = scroll.toInt() + scroll = scroll.toInt(), + percent = percent, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt index 24a487c60..181499fa4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt @@ -13,16 +13,17 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity entity = MangaEntity::class, parentColumns = ["manga_id"], childColumns = ["manga_id"], - onDelete = ForeignKey.CASCADE + onDelete = ForeignKey.CASCADE, ) ] ) class HistoryEntity( @PrimaryKey(autoGenerate = false) @ColumnInfo(name = "manga_id") val mangaId: Long, - @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(), + @ColumnInfo(name = "created_at") val createdAt: Long, @ColumnInfo(name = "updated_at") val updatedAt: Long, @ColumnInfo(name = "chapter_id") val chapterId: Long, @ColumnInfo(name = "page") val page: Int, @ColumnInfo(name = "scroll") val scroll: Float, + @ColumnInfo(name = "percent") val percent: Float, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 4519b60e4..fa043ad14 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -59,7 +59,7 @@ class HistoryRepository( .distinctUntilChanged() } - suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int) { + suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int, percent: Float) { if (manga.isNsfw && settings.isHistoryExcludeNsfw) { return } @@ -75,6 +75,7 @@ class HistoryRepository( chapterId = chapterId, page = page, scroll = scroll.toFloat(), // we migrate to int, but decide to not update database + percent = percent, ) ) trackingRepository.syncWithHistory(manga, chapterId) diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 931461de0..39ec6946a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -6,7 +6,6 @@ import androidx.activity.result.ActivityResultLauncher import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import java.util.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.koitharu.kotatsu.R @@ -32,6 +31,7 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.processLifecycleScope +import java.util.* private const val BOUNDS_PAGE_OFFSET = 2 private const val PAGES_TRIM_THRESHOLD = 120 @@ -135,13 +135,16 @@ class ReaderViewModel( } } + // TODO check performance fun saveCurrentState(state: ReaderState? = null) { if (state != null) { currentState.value = state } + val readerState = state ?: currentState.value ?: return historyRepository.saveStateAsync( - mangaData.value ?: return, - state ?: currentState.value ?: return + manga = mangaData.value ?: return, + state = readerState, + percent = computePercent(readerState.chapterId, readerState.page) ) } @@ -223,7 +226,7 @@ class ReaderViewModel( if (bookmarkJob?.isActive == true) { return } - bookmarkJob = launchJob { + bookmarkJob = launchJob(Dispatchers.Default) { loadingJob?.join() val state = checkNotNull(currentState.value) val page = checkNotNull(getCurrentPage()) { "Page not found" } @@ -235,9 +238,10 @@ class ReaderViewModel( scroll = state.scroll, imageUrl = page.preview ?: pageLoader.getPageUrl(page), createdAt = Date(), + percent = computePercent(state.chapterId, state.page), ) bookmarksRepository.addBookmark(bookmark) - onShowToast.call(R.string.bookmark_added) + onShowToast.postCall(R.string.bookmark_added) } } @@ -279,7 +283,8 @@ class ReaderViewModel( val pages = loadChapter(requireNotNull(currentState.value).chapterId) // save state currentState.value?.let { - historyRepository.addOrUpdate(manga, it.chapterId, it.page, it.scroll) + val percent = computePercent(it.chapterId, it.page) + historyRepository.addOrUpdate(manga, it.chapterId, it.page, it.scroll, percent) shortcutsRepository.updateShortcuts() } @@ -364,20 +369,32 @@ class ReaderViewModel( it.printStackTraceDebug() }.getOrDefault(defaultMode) } + + private fun computePercent(chapterId: Long, pageIndex: Int): Float { + val chapters = manga?.chapters ?: return -1f + val chaptersCount = chapters.size + val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId } + val pages = content.value?.pages ?: return -1f + val pagesCount = pages.count { x -> x.chapterId == chapterId } + val chapterPercent = (chapterIndex + 1) / chaptersCount.toFloat() + val pagePercent = (pageIndex + 1) / pagesCount.toFloat() + return pagePercent * chapterPercent // FIXME + } } /** * This function is not a member of the ReaderViewModel * because it should work independently of the ViewModel's lifecycle. */ -private fun HistoryRepository.saveStateAsync(manga: Manga, state: ReaderState): Job { +private fun HistoryRepository.saveStateAsync(manga: Manga, state: ReaderState, percent: Float): Job { return processLifecycleScope.launch(Dispatchers.Default) { runCatching { addOrUpdate( manga = manga, chapterId = state.chapterId, page = state.page, - scroll = state.scroll + scroll = state.scroll, + percent = percent, ) }.onFailure { it.printStackTraceDebug()