Store page scroll position in history

This commit is contained in:
Koitharu
2020-03-28 18:49:01 +02:00
parent e7a150bd9a
commit 85b18d118b
16 changed files with 96 additions and 31 deletions

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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
)
}

View File

@@ -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")
}
}

View File

@@ -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

View File

@@ -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
)
}
}

View File

@@ -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)

View File

@@ -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
)
)
}

View File

@@ -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)
}

View File

@@ -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
)
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}
)
)

View File

@@ -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 {