Store page scroll position in history

pull/1/head
Koitharu 6 years ago
parent e7a150bd9a
commit 85b18d118b

@ -15,6 +15,7 @@ import org.koin.core.context.startKoin
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.migrations.Migration1To2 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.CbzFetcher
import org.koitharu.kotatsu.core.local.PagesCache import org.koitharu.kotatsu.core.local.PagesCache
import org.koitharu.kotatsu.core.local.cookies.PersistentCookieJar import org.koitharu.kotatsu.core.local.cookies.PersistentCookieJar
@ -110,5 +111,5 @@ class KotatsuApp : Application() {
applicationContext, applicationContext,
MangaDatabase::class.java, MangaDatabase::class.java,
"kotatsu-db" "kotatsu-db"
).addMigrations(Migration1To2) ).addMigrations(Migration1To2, Migration2To3)
} }

@ -24,13 +24,13 @@ abstract class HistoryDao {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(entity: HistoryEntity): Long abstract suspend fun insert(entity: HistoryEntity): Long
@Query("UPDATE history SET page = :page, chapter_id = :chapterId, updated_at = :updatedAt WHERE manga_id = :mangaId") @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, updatedAt: Long): Int abstract suspend fun update(mangaId: Long, page: Int, chapterId: Long, scroll: Float, updatedAt: Long): Int
@Query("DELETE FROM history WHERE manga_id = :mangaId") @Query("DELETE FROM history WHERE manga_id = :mangaId")
abstract suspend fun delete(mangaId: Long) 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 @Transaction
open suspend fun upsert(entity: HistoryEntity) { open suspend fun upsert(entity: HistoryEntity) {

@ -8,7 +8,7 @@ import org.koitharu.kotatsu.core.db.entity.*
entities = [ entities = [
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class, MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class
], version = 2 ], version = 3
) )
abstract class MangaDatabase : RoomDatabase() { abstract class MangaDatabase : RoomDatabase() {

@ -23,13 +23,15 @@ data class HistoryEntity(
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(), @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(),
@ColumnInfo(name = "updated_at") val updatedAt: Long, @ColumnInfo(name = "updated_at") val updatedAt: Long,
@ColumnInfo(name = "chapter_id") val chapterId: 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( fun toMangaHistory() = MangaHistory(
createdAt = Date(createdAt), createdAt = Date(createdAt),
updatedAt = Date(updatedAt), updatedAt = Date(updatedAt),
chapterId = chapterId, 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 createdAt: Date,
val updatedAt: Date, val updatedAt: Date,
val chapterId: Long, val chapterId: Long,
val page: Int val page: Int,
val scroll: Float
) : Parcelable ) : Parcelable

@ -21,7 +21,7 @@ class HistoryRepository : KoinComponent {
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) } 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) val tags = manga.tags.map(TagEntity.Companion::fromMangaTag)
db.tagsDao().upsert(tags) db.tagsDao().upsert(tags)
db.mangaDao().upsert(MangaEntity.from(manga), tags) db.mangaDao().upsert(MangaEntity.from(manga), tags)
@ -31,7 +31,8 @@ class HistoryRepository : KoinComponent {
createdAt = System.currentTimeMillis(), createdAt = System.currentTimeMillis(),
updatedAt = System.currentTimeMillis(), updatedAt = System.currentTimeMillis(),
chapterId = chapterId, chapterId = chapterId,
page = page page = page,
scroll = scroll
) )
) )
notifyHistoryChanged() notifyHistoryChanged()
@ -43,7 +44,8 @@ class HistoryRepository : KoinComponent {
createdAt = Date(it.createdAt), createdAt = Date(it.createdAt),
updatedAt = Date(it.updatedAt), updatedAt = Date(it.updatedAt),
chapterId = it.chapterId, 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() val history = repo.getOne(manga) ?: throw EmptyHistoryException()
ReaderState( ReaderState(
MangaProviderFactory.create(manga.source).getDetails(manga), MangaProviderFactory.create(manga.source).getDetails(manga),
history.chapterId, history.page history.chapterId, history.page, history.scroll
) )
} }
viewState.onOpenReader(state) viewState.onOpenReader(state)

@ -195,8 +195,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun saveState(chapterId: Long, page: Int) { override fun saveState(chapterId: Long, page: Int, scroll: Float) {
state = state.copy(chapterId = chapterId, page = page) state = state.copy(chapterId = chapterId, page = page, scroll = scroll)
ReaderPresenter.saveState(state) ReaderPresenter.saveState(state)
} }
@ -293,7 +293,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
override fun onChapterChanged(chapter: MangaChapter) { override fun onChapterChanged(chapter: MangaChapter) {
state = state.copy( state = state.copy(
chapterId = chapter.id, chapterId = chapter.id,
page = 0 page = 0,
scroll = 0f
) )
reader?.updateState(chapterId = chapter.id) reader?.updateState(chapterId = chapter.id)
} }
@ -371,7 +372,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
manga = manga, manga = manga,
chapterId = if (chapterId == -1L) manga.chapters?.firstOrNull()?.id chapterId = if (chapterId == -1L) manga.chapters?.firstOrNull()?.id
?: -1 else chapterId, ?: -1 else chapterId,
page = 0 page = 0,
scroll = 0f
) )
) )
@ -383,7 +385,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
context, ReaderState( context, ReaderState(
manga = manga, manga = manga,
chapterId = history.chapterId, 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 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 package org.koitharu.kotatsu.ui.reader
import android.content.ContentResolver import android.content.ContentResolver
import android.util.Log
import android.webkit.URLUtil import android.webkit.URLUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import moxy.InjectViewState import moxy.InjectViewState
@ -9,7 +8,6 @@ import moxy.presenterScope
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.get import org.koin.core.get
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
@ -103,7 +101,8 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
HistoryRepository().addOrUpdate( HistoryRepository().addOrUpdate(
manga = state.manga, manga = state.manga,
chapterId = state.chapterId, 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( data class ReaderState(
val manga: Manga, val manga: Manga,
val chapterId: Long, val chapterId: Long,
val page: Int val page: Int,
val scroll: Float
) : Parcelable { ) : Parcelable {
@IgnoredOnParcel @IgnoredOnParcel

@ -58,6 +58,9 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
pages.addLast(state.chapterId, it) pages.addLast(state.chapterId, it)
adapter?.notifyDataSetChanged() adapter?.notifyDataSetChanged()
setCurrentItem(state.page, false) 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( ARG_STATE, ReaderState(
manga = manga, manga = manga,
chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return, 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 chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val page = pages.getRelativeIndex(getCurrentItem()) val page = pages.getRelativeIndex(getCurrentItem())
if (page != -1) { if (page != -1) {
readerListener?.saveState(chapterId, page) readerListener?.saveState(chapterId, page, getCurrentPageScroll())
} }
Log.i(TAG, "saveState(chapterId=$chapterId, page=$page)") 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 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 setCurrentItem(position: Int, isSmooth: Boolean)
protected abstract fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter 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 kotlinx.android.synthetic.main.fragment_reader_standard.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage 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.AbstractReader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList 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.doOnPageChanged
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
@ -43,6 +43,10 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard) {
pager.setCurrentItem(position, isSmooth) pager.setCurrentItem(position, isSmooth)
} }
override fun getCurrentPageScroll() = 0f
override fun restorePageScroll(position: Int, scroll: Float) = Unit
companion object { companion object {
fun newInstance(state: ReaderState) = PagerReaderFragment().withArgs(1) { fun newInstance(state: ReaderState) = PagerReaderFragment().withArgs(1) {

@ -20,6 +20,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader { SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader {
private var job: Job? = null private var job: Job? = null
private var scrollToRestore = 0f
init { init {
ssiv.setOnImageEventListener(this) ssiv.setOnImageEventListener(this)
@ -34,6 +35,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
private fun doLoad(data: MangaPage, force: Boolean) { private fun doLoad(data: MangaPage, force: Boolean) {
job?.cancel() job?.cancel()
scrollToRestore = 0f
job = launch { job = launch {
layout_error.isVisible = false layout_error.isVisible = false
progressBar.isVisible = true 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() { override fun onReady() {
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat() ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
@ -59,10 +77,10 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
ssiv.minScale, ssiv.minScale,
PointF( PointF(
ssiv.sWidth / 2f, ssiv.sWidth / 2f,
if (itemView.top < 0) { when {
ssiv.sHeight.toFloat() scrollToRestore != 0f -> scrollToRestore
} else { itemView.top < 0 -> ssiv.sHeight.toFloat()
0f 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.AbstractReader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList 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.doOnCurrentItemChanged
import org.koitharu.kotatsu.utils.ext.findMiddleVisibleItemPosition import org.koitharu.kotatsu.utils.ext.findMiddleVisibleItemPosition
import org.koitharu.kotatsu.utils.ext.firstItem import org.koitharu.kotatsu.utils.ext.firstItem
@ -56,7 +55,23 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
} }
override fun switchPageBy(delta: Int) { 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 { companion object {

Loading…
Cancel
Save