Store page scroll position in history
This commit is contained in:
@@ -15,6 +15,7 @@ import org.koin.core.context.startKoin
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
|
||||
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
|
||||
import org.koitharu.kotatsu.core.local.CbzFetcher
|
||||
import org.koitharu.kotatsu.core.local.PagesCache
|
||||
import org.koitharu.kotatsu.core.local.cookies.PersistentCookieJar
|
||||
@@ -110,5 +111,5 @@ class KotatsuApp : Application() {
|
||||
applicationContext,
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(Migration1To2)
|
||||
).addMigrations(Migration1To2, Migration2To3)
|
||||
}
|
||||
@@ -24,13 +24,13 @@ abstract class HistoryDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract suspend fun insert(entity: HistoryEntity): Long
|
||||
|
||||
@Query("UPDATE history SET page = :page, chapter_id = :chapterId, updated_at = :updatedAt WHERE manga_id = :mangaId")
|
||||
abstract suspend fun update(mangaId: Long, page: Int, chapterId: Long, updatedAt: Long): Int
|
||||
@Query("UPDATE history SET page = :page, chapter_id = :chapterId, scroll = :scroll, updated_at = :updatedAt WHERE manga_id = :mangaId")
|
||||
abstract suspend fun update(mangaId: Long, page: Int, chapterId: Long, scroll: Float, updatedAt: Long): Int
|
||||
|
||||
@Query("DELETE FROM history WHERE manga_id = :mangaId")
|
||||
abstract suspend fun delete(mangaId: Long)
|
||||
|
||||
suspend fun update(entity: HistoryEntity) = update(entity.mangaId, entity.page, entity.chapterId, entity.updatedAt)
|
||||
suspend fun update(entity: HistoryEntity) = update(entity.mangaId, entity.page, entity.chapterId, entity.scroll, entity.updatedAt)
|
||||
|
||||
@Transaction
|
||||
open suspend fun upsert(entity: HistoryEntity) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.db.entity.*
|
||||
entities = [
|
||||
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
|
||||
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class
|
||||
], version = 2
|
||||
], version = 3
|
||||
)
|
||||
abstract class MangaDatabase : RoomDatabase() {
|
||||
|
||||
|
||||
@@ -23,13 +23,15 @@ data class HistoryEntity(
|
||||
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(),
|
||||
@ColumnInfo(name = "updated_at") val updatedAt: Long,
|
||||
@ColumnInfo(name = "chapter_id") val chapterId: Long,
|
||||
@ColumnInfo(name = "page") val page: Int
|
||||
@ColumnInfo(name = "page") val page: Int,
|
||||
@ColumnInfo(name = "scroll") val scroll: Float
|
||||
) {
|
||||
|
||||
fun toMangaHistory() = MangaHistory(
|
||||
createdAt = Date(createdAt),
|
||||
updatedAt = Date(updatedAt),
|
||||
chapterId = chapterId,
|
||||
page = page
|
||||
page = page,
|
||||
scroll = scroll
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
object Migration2To3 : Migration(2, 3) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE history ADD COLUMN scroll REAL NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,6 @@ data class MangaHistory(
|
||||
val createdAt: Date,
|
||||
val updatedAt: Date,
|
||||
val chapterId: Long,
|
||||
val page: Int
|
||||
val page: Int,
|
||||
val scroll: Float
|
||||
) : Parcelable
|
||||
@@ -21,7 +21,7 @@ class HistoryRepository : KoinComponent {
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int) {
|
||||
suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Float) {
|
||||
val tags = manga.tags.map(TagEntity.Companion::fromMangaTag)
|
||||
db.tagsDao().upsert(tags)
|
||||
db.mangaDao().upsert(MangaEntity.from(manga), tags)
|
||||
@@ -31,7 +31,8 @@ class HistoryRepository : KoinComponent {
|
||||
createdAt = System.currentTimeMillis(),
|
||||
updatedAt = System.currentTimeMillis(),
|
||||
chapterId = chapterId,
|
||||
page = page
|
||||
page = page,
|
||||
scroll = scroll
|
||||
)
|
||||
)
|
||||
notifyHistoryChanged()
|
||||
@@ -43,7 +44,8 @@ class HistoryRepository : KoinComponent {
|
||||
createdAt = Date(it.createdAt),
|
||||
updatedAt = Date(it.updatedAt),
|
||||
chapterId = it.chapterId,
|
||||
page = it.page
|
||||
page = it.page,
|
||||
scroll = it.scroll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class MainPresenter : BasePresenter<MainView>() {
|
||||
val history = repo.getOne(manga) ?: throw EmptyHistoryException()
|
||||
ReaderState(
|
||||
MangaProviderFactory.create(manga.source).getDetails(manga),
|
||||
history.chapterId, history.page
|
||||
history.chapterId, history.page, history.scroll
|
||||
)
|
||||
}
|
||||
viewState.onOpenReader(state)
|
||||
|
||||
@@ -195,8 +195,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun saveState(chapterId: Long, page: Int) {
|
||||
state = state.copy(chapterId = chapterId, page = page)
|
||||
override fun saveState(chapterId: Long, page: Int, scroll: Float) {
|
||||
state = state.copy(chapterId = chapterId, page = page, scroll = scroll)
|
||||
ReaderPresenter.saveState(state)
|
||||
}
|
||||
|
||||
@@ -293,7 +293,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
override fun onChapterChanged(chapter: MangaChapter) {
|
||||
state = state.copy(
|
||||
chapterId = chapter.id,
|
||||
page = 0
|
||||
page = 0,
|
||||
scroll = 0f
|
||||
)
|
||||
reader?.updateState(chapterId = chapter.id)
|
||||
}
|
||||
@@ -371,7 +372,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
manga = manga,
|
||||
chapterId = if (chapterId == -1L) manga.chapters?.firstOrNull()?.id
|
||||
?: -1 else chapterId,
|
||||
page = 0
|
||||
page = 0,
|
||||
scroll = 0f
|
||||
)
|
||||
)
|
||||
|
||||
@@ -383,7 +385,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
context, ReaderState(
|
||||
manga = manga,
|
||||
chapterId = history.chapterId,
|
||||
page = history.page
|
||||
page = history.page,
|
||||
scroll = history.scroll
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ interface ReaderListener : BaseMvpView {
|
||||
|
||||
fun onPageChanged(chapter: MangaChapter, page: Int, total: Int)
|
||||
|
||||
fun saveState(chapterId: Long, page: Int)
|
||||
fun saveState(chapterId: Long, page: Int, scroll: Float)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.ui.reader
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.util.Log
|
||||
import android.webkit.URLUtil
|
||||
import kotlinx.coroutines.*
|
||||
import moxy.InjectViewState
|
||||
@@ -9,7 +8,6 @@ import moxy.presenterScope
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.koin.core.get
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaPage
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
@@ -103,7 +101,8 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
|
||||
HistoryRepository().addOrUpdate(
|
||||
manga = state.manga,
|
||||
chapterId = state.chapterId,
|
||||
page = state.page
|
||||
page = state.page,
|
||||
scroll = state.scroll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
data class ReaderState(
|
||||
val manga: Manga,
|
||||
val chapterId: Long,
|
||||
val page: Int
|
||||
val page: Int,
|
||||
val scroll: Float
|
||||
) : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -58,6 +58,9 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
pages.addLast(state.chapterId, it)
|
||||
adapter?.notifyDataSetChanged()
|
||||
setCurrentItem(state.page, false)
|
||||
if (state.scroll != 0f) {
|
||||
restorePageScroll(state.page, state.scroll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +70,8 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
ARG_STATE, ReaderState(
|
||||
manga = manga,
|
||||
chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return,
|
||||
page = pages.getRelativeIndex(getCurrentItem())
|
||||
page = pages.getRelativeIndex(getCurrentItem()),
|
||||
scroll = getCurrentPageScroll()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -174,7 +178,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
|
||||
val page = pages.getRelativeIndex(getCurrentItem())
|
||||
if (page != -1) {
|
||||
readerListener?.saveState(chapterId, page)
|
||||
readerListener?.saveState(chapterId, page, getCurrentPageScroll())
|
||||
}
|
||||
Log.i(TAG, "saveState(chapterId=$chapterId, page=$page)")
|
||||
}
|
||||
@@ -217,6 +221,10 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
|
||||
protected abstract fun getCurrentItem(): Int
|
||||
|
||||
protected abstract fun getCurrentPageScroll(): Float
|
||||
|
||||
protected abstract fun restorePageScroll(position: Int, scroll: Float)
|
||||
|
||||
protected abstract fun setCurrentItem(position: Int, isSmooth: Boolean)
|
||||
|
||||
protected abstract fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter
|
||||
|
||||
@@ -5,10 +5,10 @@ import android.view.View
|
||||
import kotlinx.android.synthetic.main.fragment_reader_standard.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaPage
|
||||
import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||
import org.koitharu.kotatsu.ui.reader.base.AbstractReader
|
||||
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
|
||||
import org.koitharu.kotatsu.ui.reader.base.GroupedList
|
||||
import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||
import org.koitharu.kotatsu.utils.ext.doOnPageChanged
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@@ -43,6 +43,10 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard) {
|
||||
pager.setCurrentItem(position, isSmooth)
|
||||
}
|
||||
|
||||
override fun getCurrentPageScroll() = 0f
|
||||
|
||||
override fun restorePageScroll(position: Int, scroll: Float) = Unit
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(state: ReaderState) = PagerReaderFragment().withArgs(1) {
|
||||
|
||||
@@ -20,6 +20,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader {
|
||||
|
||||
private var job: Job? = null
|
||||
private var scrollToRestore = 0f
|
||||
|
||||
init {
|
||||
ssiv.setOnImageEventListener(this)
|
||||
@@ -34,6 +35,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
|
||||
private fun doLoad(data: MangaPage, force: Boolean) {
|
||||
job?.cancel()
|
||||
scrollToRestore = 0f
|
||||
job = launch {
|
||||
layout_error.isVisible = false
|
||||
progressBar.isVisible = true
|
||||
@@ -51,6 +53,22 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
}
|
||||
}
|
||||
|
||||
fun getScrollY() = ssiv.center?.y ?: 0f
|
||||
|
||||
fun restoreScroll(scroll: Float) {
|
||||
if (ssiv.isReady) {
|
||||
ssiv.setScaleAndCenter(
|
||||
ssiv.scale,
|
||||
PointF(
|
||||
ssiv.sWidth / 2f,
|
||||
scroll
|
||||
)
|
||||
)
|
||||
} else {
|
||||
scrollToRestore = scroll
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReady() {
|
||||
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
|
||||
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
@@ -59,10 +77,10 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
ssiv.minScale,
|
||||
PointF(
|
||||
ssiv.sWidth / 2f,
|
||||
if (itemView.top < 0) {
|
||||
ssiv.sHeight.toFloat()
|
||||
} else {
|
||||
0f
|
||||
when {
|
||||
scrollToRestore != 0f -> scrollToRestore
|
||||
itemView.top < 0 -> ssiv.sHeight.toFloat()
|
||||
else -> 0f
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||
import org.koitharu.kotatsu.ui.reader.base.AbstractReader
|
||||
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
|
||||
import org.koitharu.kotatsu.ui.reader.base.GroupedList
|
||||
import org.koitharu.kotatsu.ui.reader.standard.PagerReaderFragment
|
||||
import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged
|
||||
import org.koitharu.kotatsu.utils.ext.findMiddleVisibleItemPosition
|
||||
import org.koitharu.kotatsu.utils.ext.firstItem
|
||||
@@ -56,7 +55,23 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
|
||||
}
|
||||
|
||||
override fun switchPageBy(delta: Int) {
|
||||
recyclerView.smoothScrollBy(0, (recyclerView.height * 0.9).toInt() * delta, scrollInterpolator)
|
||||
recyclerView.smoothScrollBy(
|
||||
0,
|
||||
(recyclerView.height * 0.9).toInt() * delta,
|
||||
scrollInterpolator
|
||||
)
|
||||
}
|
||||
|
||||
override fun getCurrentPageScroll(): Float {
|
||||
return (recyclerView.findViewHolderForAdapterPosition(getCurrentItem()) as? WebtoonHolder)
|
||||
?.getScrollY() ?: 0f
|
||||
}
|
||||
|
||||
override fun restorePageScroll(position: Int, scroll: Float) {
|
||||
recyclerView.post {
|
||||
val holder = recyclerView.findViewHolderForAdapterPosition(position) ?: return@post
|
||||
(holder as WebtoonHolder).restoreScroll(scroll)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user