Remove presenter from reader fragments

pull/1/head
Koitharu 6 years ago
parent 4a4c7108cb
commit fec3481d27

@ -84,7 +84,7 @@ dependencies {
implementation 'com.squareup.okio:okio:2.4.3' implementation 'com.squareup.okio:okio:2.4.3'
implementation 'org.jsoup:jsoup:1.12.2' implementation 'org.jsoup:jsoup:1.12.2'
implementation 'org.koin:koin-android:2.1.1' implementation 'org.koin:koin-android:2.1.3'
implementation 'io.coil-kt:coil:0.9.5' implementation 'io.coil-kt:coil:0.9.5'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.tomclaw.cache:cache:1.0' implementation 'com.tomclaw.cache:cache:1.0'

@ -1,139 +0,0 @@
package org.koitharu.kotatsu.ui.reader
import android.net.Uri
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.ui.common.BaseFragment
import java.util.*
abstract class BaseReaderFragment(@LayoutRes contentLayoutId: Int) : BaseFragment(contentLayoutId),
ReaderView {
private val chaptersMap = ArrayDeque<Pair<Long, Int>>() as Deque<Pair<Long, Int>>
protected val lastState
get() = (activity as? ReaderActivity)?.state
abstract val hasItems: Boolean
protected abstract val currentPageIndex: Int
abstract val pages: List<MangaPage>
abstract fun setCurrentPage(index: Int, smooth: Boolean)
val currentPage get() = pages.getOrNull(currentPageIndex)
/**
* Handled by activity
*/
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
/**
* Handled by activity
*/
override fun onError(e: Throwable) = Unit
/**
* Handled by activity
*/
override fun onPageSaved(uri: Uri?) = Unit
override fun onInitReader(mode: ReaderMode) = Unit
override fun onChaptersLoader(chapters: List<MangaChapter>) = Unit
override fun onDestroyView() {
chaptersMap.clear()
super.onDestroyView()
}
@CallSuper
override fun onPagesLoaded(chapterId: Long, pages: List<MangaPage>, action: ReaderAction) {
when (action) {
ReaderAction.REPLACE -> {
chaptersMap.clear()
chaptersMap.add(chapterId to pages.size)
}
ReaderAction.PREPEND -> chaptersMap.addFirst(chapterId to pages.size)
ReaderAction.APPEND -> chaptersMap.addLast(chapterId to pages.size)
}
}
open fun switchPageBy(delta: Int) {
setCurrentPage(currentPageIndex + delta, true)
}
fun findCurrentPageIndex(chapterId: Long): Int {
var offset = 0
for ((id, count) in chaptersMap) {
if (id == chapterId) {
return currentPageIndex - offset
}
offset += count
}
return -1
}
fun findChapterOffset(chapterId: Long): Int {
var offset = 0
for ((id, count) in chaptersMap) {
if (id == chapterId) {
return offset
}
offset += count
}
return -1
}
fun getPages(chapterId: Long): List<MangaPage>? {
var offset = 0
for ((id, count) in chaptersMap) {
if (id == chapterId) {
return pages.subList(offset, offset + count - 1)
}
offset += count
}
return null
}
protected fun getNextChapterId(): Long {
val lastChapterId = chaptersMap.peekLast()?.first ?: return 0
val chapters = lastState?.manga?.chapters ?: return 0
val indexOfCurrent = chapters.indexOfLast { x -> x.id == lastChapterId }
return if (indexOfCurrent == -1) {
0
} else {
chapters.getOrNull(indexOfCurrent + 1)?.id ?: 0
}
}
protected fun getPrevChapterId(): Long {
val firstChapterId = chaptersMap.peekFirst()?.first ?: return 0
val chapters = lastState?.manga?.chapters ?: return 0
val indexOfCurrent = chapters.indexOfFirst { x -> x.id == firstChapterId }
return if (indexOfCurrent == -1) {
0
} else {
chapters.getOrNull(indexOfCurrent - 1)?.id ?: 0
}
}
protected fun notifyPageChanged(page: Int) {
var i = page
val chapters = lastState?.manga?.chapters ?: return
val chapter = chaptersMap.firstOrNull { x ->
i -= x.second
i < 0
} ?: return
(activity as? ReaderListener)?.onPageChanged(
chapter = chapters.find { x -> x.id == chapter.first } ?: return,
page = i + chapter.second,
total = chapter.second
)
}
}

@ -9,6 +9,7 @@ import android.os.Bundle
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -24,7 +25,8 @@ import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity
import org.koitharu.kotatsu.ui.reader.standard.StandardReaderFragment import org.koitharu.kotatsu.ui.reader.base.AbstractReader
import org.koitharu.kotatsu.ui.reader.standard.PagerReaderFragment
import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener
import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet
import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment
@ -37,7 +39,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback, GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback,
ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener { ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener {
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance) private val presenter by moxyPresenter(factory = ::ReaderPresenter)
private val settings by inject<AppSettings>() private val settings by inject<AppSettings>()
lateinit var state: ReaderState lateinit var state: ReaderState
@ -48,7 +50,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
private var isVolumeKeysSwitchEnabled = false private var isVolumeKeysSwitchEnabled = false
private val reader private val reader
get() = supportFragmentManager.findFragmentById(R.id.container) as? BaseReaderFragment get() = supportFragmentManager.findFragmentById(R.id.container) as? AbstractReader
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -81,24 +83,33 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
loadSettings() loadSettings()
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
presenter.loadChapter(state.manga, state.chapterId, ReaderAction.REPLACE) presenter.init(state.manga)
} }
} }
override fun onInitReader(mode: ReaderMode) { override fun onInitReader(manga: Manga, mode: ReaderMode) {
if (reader == null) { val currentReader = reader
setReader(mode) when (mode) {
ReaderMode.WEBTOON -> if (currentReader !is WebtoonReaderFragment) {
supportFragmentManager.commit {
replace(R.id.container, WebtoonReaderFragment.newInstance(state))
}
}
else -> if (currentReader !is PagerReaderFragment) {
supportFragmentManager.commit {
replace(R.id.container, PagerReaderFragment.newInstance(state))
}
}
} }
} toolbar_bottom.menu.findItem(R.id.action_reader_mode).setIcon(
when (mode) {
override fun onPagesLoaded(chapterId: Long, pages: List<MangaPage>, action: ReaderAction) = Unit ReaderMode.WEBTOON -> R.drawable.ic_script
else -> R.drawable.ic_book_page
override fun onPause() { }
reader?.let { )
state = state.copy(page = it.findCurrentPageIndex(state.chapterId)) appbar_top.postDelayed(1000) {
presenter.saveState(state) setUiIsVisible(false)
} }
super.onPause()
} }
override fun onDestroy() { override fun onDestroy() {
@ -120,7 +131,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
R.id.action_reader_mode -> { R.id.action_reader_mode -> {
ReaderConfigDialog.show( ReaderConfigDialog.show(
supportFragmentManager, when (reader) { supportFragmentManager, when (reader) {
is StandardReaderFragment -> ReaderMode.STANDARD is PagerReaderFragment -> ReaderMode.STANDARD
is WebtoonReaderFragment -> ReaderMode.WEBTOON is WebtoonReaderFragment -> ReaderMode.WEBTOON
else -> ReaderMode.UNKNOWN else -> ReaderMode.UNKNOWN
} }
@ -141,7 +152,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
} }
R.id.action_pages_thumbs -> { R.id.action_pages_thumbs -> {
if (reader?.hasItems == true) { if (reader?.hasItems == true) {
val pages = reader?.getPages(state.chapterId) val pages = reader?.getPages()
if (pages != null) { if (pages != null) {
PagesThumbnailsSheet.show( PagesThumbnailsSheet.show(
supportFragmentManager, pages, supportFragmentManager, pages,
@ -173,6 +184,11 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun saveState(chapterId: Long, page: Int) {
state = state.copy(chapterId = chapterId, page = page)
ReaderPresenter.saveState(state)
}
override fun onLoadingStateChanged(isLoading: Boolean) { override fun onLoadingStateChanged(isLoading: Boolean) {
val hasPages = reader?.hasItems == true val hasPages = reader?.hasItems == true
layout_loading.isVisible = isLoading && !hasPages layout_loading.isVisible = isLoading && !hasPages
@ -268,29 +284,16 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
chapterId = chapter.id, chapterId = chapter.id,
page = 0 page = 0
) )
presenter.loadChapter(state.manga, chapter.id, ReaderAction.REPLACE) reader?.updateState(chapterId = chapter.id)
} }
override fun onPageSelected(page: MangaPage) { override fun onPageSelected(page: MangaPage) {
reader?.let { reader?.updateState(pageId = page.id)
val index = it.pages.indexOfFirst { x -> x.id == page.id }
if (index != -1) {
it.setCurrentPage(index, false)
}
}
} }
override fun onReaderModeChanged(mode: ReaderMode) { override fun onReaderModeChanged(mode: ReaderMode) {
reader?.let { //TODO save state
state = state.copy(page = it.findCurrentPageIndex(state.chapterId)) presenter.setMode(state.manga, mode)
}
presenter.saveState(state, mode)
setReader(mode)
setUiIsVisible(false)
}
override fun onChaptersLoader(chapters: List<MangaChapter>) {
state = state.copy(manga = state.manga.copy(chapters = chapters))
} }
override fun onPageSaved(uri: Uri?) { override fun onPageSaved(uri: Uri?) {
@ -305,14 +308,10 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
} }
override fun onPageChanged(chapter: MangaChapter, page: Int, total: Int) { override fun onPageChanged(chapter: MangaChapter, page: Int, total: Int) {
if (chapter.id != state.chapterId) { title = chapter.name
title = chapter.name state.manga.chapters?.run {
state = state.copy(chapterId = chapter.id) supportActionBar?.subtitle =
presenter.saveState(state) getString(R.string.chapter_d_of_d, chapter.number, size)
state.manga.chapters?.run {
supportActionBar?.subtitle =
getString(R.string.chapter_d_of_d, chapter.number, size)
}
} }
} }
@ -340,25 +339,6 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
} }
} }
private fun setReader(mode: ReaderMode) {
val currentReader = reader
when (mode) {
ReaderMode.WEBTOON -> if (currentReader !is WebtoonReaderFragment) {
supportFragmentManager.commit {
replace(R.id.container, WebtoonReaderFragment())
}
}
else -> if (currentReader !is StandardReaderFragment) {
supportFragmentManager.commit {
replace(R.id.container, StandardReaderFragment())
}
}
}
toolbar_bottom.menu.findItem(R.id.action_reader_mode).setIcon(when(mode) {
ReaderMode.WEBTOON -> R.drawable.ic_script
else -> R.drawable.ic_book_page
})
}
private fun loadSettings() { private fun loadSettings() {
settings.readerPageSwitch.let { settings.readerPageSwitch.let {

@ -1,8 +1,11 @@
package org.koitharu.kotatsu.ui.reader package org.koitharu.kotatsu.ui.reader
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.ui.common.BaseMvpView
interface ReaderListener { 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)
} }

@ -8,6 +8,7 @@ import moxy.InjectViewState
import moxy.presenterScope import moxy.presenterScope
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.get
import org.koitharu.kotatsu.BuildConfig 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
@ -25,50 +26,31 @@ import org.koitharu.kotatsu.utils.ext.mimeType
@InjectViewState @InjectViewState
class ReaderPresenter : BasePresenter<ReaderView>() { class ReaderPresenter : BasePresenter<ReaderView>() {
private var loaderJob: Job? = null fun init(manga: Manga) {
private var isInitialized = false presenterScope.launch {
fun loadChapter(manga: Manga, chapterId: Long, action: ReaderAction) {
loaderJob?.cancel()
loaderJob = presenterScope.launch {
viewState.onLoadingStateChanged(isLoading = true) viewState.onLoadingStateChanged(isLoading = true)
try { try {
withContext(Dispatchers.IO) { val mode = withContext(Dispatchers.IO) {
val repo = MangaProviderFactory.create(manga.source) val repo = MangaProviderFactory.create(manga.source)
val chapter = (manga.chapters ?: repo.getDetails(manga).chapters?.also { val chapter =
withContext(Dispatchers.Main) { (manga.chapters ?: throw RuntimeException("Chapters is null")).random()
viewState.onChaptersLoader(it) val prefs = MangaDataRepository()
} var mode = prefs.getReaderMode(manga.id)
})?.find { it.id == chapterId } if (mode == null) {
?: throw RuntimeException("Chapter ${chapterId} not found") val pages = repo.getPages(chapter)
val pages = repo.getPages(chapter) mode = MangaUtils.determineReaderMode(pages)
if (!isInitialized) { if (mode != null) {
val prefs = MangaDataRepository() prefs.savePreferences(
var mode = prefs.getReaderMode(manga.id) mangaId = manga.id,
if (mode == null) { mode = mode
mode = MangaUtils.determineReaderMode(pages) )
if (mode != null) {
prefs.savePreferences(
mangaId = manga.id,
mode = mode
)
}
} }
withContext(Dispatchers.Main) {
viewState.onInitReader(mode ?: ReaderMode.UNKNOWN)
}
isInitialized = true
}
withContext(Dispatchers.Main) {
viewState.onPagesLoaded(chapterId, pages, action)
} }
mode ?: ReaderMode.UNKNOWN
} }
} catch (e: CancellationException){ viewState.onInitReader(manga, mode)
Log.w(null, "Loader job cancelled", e) } catch (_: CancellationException) {
} catch (e: Exception) { } catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
viewState.onError(e) viewState.onError(e)
} finally { } finally {
viewState.onLoadingStateChanged(isLoading = false) viewState.onLoadingStateChanged(isLoading = false)
@ -76,20 +58,14 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
} }
} }
fun saveState(state: ReaderState, mode: ReaderMode? = null) { fun setMode(manga: Manga, mode: ReaderMode) {
presenterScope.launch(Dispatchers.IO) { presenterScope.launch(Dispatchers.IO) {
HistoryRepository().addOrUpdate( MangaDataRepository().savePreferences(
manga = state.manga, mangaId = manga.id,
chapterId = state.chapterId, mode = mode
page = state.page
) )
if (mode != null) {
MangaDataRepository().savePreferences(
mangaId = state.manga.id,
mode = mode
)
}
} }
viewState.onInitReader(manga, mode)
} }
fun savePage(resolver: ContentResolver, page: MangaPage) { fun savePage(resolver: ContentResolver, page: MangaPage) {
@ -101,7 +77,7 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
.url(url) .url(url)
.get() .get()
.build() .build()
val uri = getKoin().get<OkHttpClient>().newCall(request).await().use { response -> val uri = get<OkHttpClient>().newCall(request).await().use { response ->
val fileName = val fileName =
URLUtil.guessFileName(url, response.contentDisposition, response.mimeType) URLUtil.guessFileName(url, response.contentDisposition, response.mimeType)
MediaStoreCompat.insertImage(resolver, fileName) { MediaStoreCompat.insertImage(resolver, fileName) {
@ -120,20 +96,17 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
} }
} }
override fun onDestroy() {
instance = null
super.onDestroy()
}
companion object { companion object {
private var instance: ReaderPresenter? = null fun saveState(state: ReaderState) {
GlobalScope.launch(Dispatchers.IO) {
fun getInstance(): ReaderPresenter = instance ?: synchronized(this) { HistoryRepository().addOrUpdate(
ReaderPresenter().also { manga = state.manga,
instance = it chapterId = state.chapterId,
page = state.page
)
} }
} }
}
}
} }

@ -3,21 +3,14 @@ package org.koitharu.kotatsu.ui.reader
import android.net.Uri import android.net.Uri
import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.AddToEndSingle
import moxy.viewstate.strategy.alias.OneExecution import moxy.viewstate.strategy.alias.OneExecution
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.ui.common.BaseMvpView import org.koitharu.kotatsu.ui.common.BaseMvpView
interface ReaderView : BaseMvpView { interface ReaderView : BaseMvpView {
@AddToEndSingle @AddToEndSingle
fun onInitReader(mode: ReaderMode) fun onInitReader(manga: Manga, mode: ReaderMode)
@AddToEndSingle
fun onChaptersLoader(chapters: List<MangaChapter>)
@AddToEndSingle
fun onPagesLoaded(chapterId: Long, pages: List<MangaPage>, action: ReaderAction)
@OneExecution @OneExecution
fun onPageSaved(uri: Uri?) fun onPageSaved(uri: Uri?)

@ -0,0 +1,229 @@
package org.koitharu.kotatsu.ui.reader.base
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.core.view.postDelayed
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaState
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.BaseFragment
import org.koitharu.kotatsu.ui.reader.*
abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayoutId),
OnBoundsScrollListener {
protected lateinit var manga: Manga
protected lateinit var loader: PageLoader
private set
private lateinit var pages: GroupedList<Long, MangaPage>
protected var adapter: BaseReaderAdapter<*>? = null
private set
val hasItems: Boolean
get() = itemsCount != 0
val currentPage: MangaPage?
get() = pages.getOrNull(getCurrentItem())
protected val readerListener: ReaderListener?
get() = activity as? ReaderListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pages = GroupedList()
manga = requireArguments().getParcelable<ReaderState>(ARG_STATE)!!.manga
loader = PageLoader()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = onCreateAdapter(pages)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
@Suppress("RemoveExplicitTypeArguments")
val state = savedInstanceState?.getParcelable<ReaderState>(ARG_STATE)
?: requireArguments().getParcelable<ReaderState>(ARG_STATE)!!
loadChapter(state.chapterId) {
pages.clear()
pages.addLast(state.chapterId, it)
adapter?.notifyDataSetChanged()
setCurrentItem(state.page, false)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(
ARG_STATE, ReaderState(
manga = manga,
chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return,
page = pages.getRelativeIndex(getCurrentItem())
)
)
}
override fun onScrolledToStart() {
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val index = manga.chapters?.indexOfFirst { it.id == chapterId } ?: return
val prevChapterId = manga.chapters!!.getOrNull(index - 1)?.id ?: return
loadChapter(prevChapterId) {
pages.addFirst(prevChapterId, it)
adapter?.notifyItemsPrepended(it.size)
view?.postDelayed(500) {
trimEnd()
}
}
}
override fun onScrolledToEnd() {
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val index = manga.chapters?.indexOfFirst { it.id == chapterId } ?: return
val nextChapterId = manga.chapters!!.getOrNull(index + 1)?.id ?: return
loadChapter(nextChapterId) {
pages.addLast(nextChapterId, it)
adapter?.notifyItemsAppended(it.size)
view?.postDelayed(500) {
trimStart()
}
}
}
override fun onDestroyView() {
adapter = null
super.onDestroyView()
}
override fun onDestroy() {
loader.dispose()
super.onDestroy()
}
fun getPages() = pages.findGroupByIndex(getCurrentItem())?.let {
pages.getGroup(it)
}
override fun onPause() {
saveState()
super.onPause()
}
private fun loadChapter(chapterId: Long, callback: suspend (List<MangaPage>) -> Unit) {
lifecycleScope.launch {
readerListener?.onLoadingStateChanged(isLoading = true)
try {
val pages = withContext(Dispatchers.IO) {
val chapter = manga.chapters?.find { it.id == chapterId }
?: throw RuntimeException("Chapter $chapterId not found")
val repo = MangaProviderFactory.create(manga.source)
repo.getPages(chapter)
}
callback(pages)
} catch (_: CancellationException) {
} catch (e: Throwable) {
readerListener?.onError(e)
} finally {
readerListener?.onLoadingStateChanged(isLoading = false)
}
}
}
private fun trimStart() {
var removed = 0
while (pages.groupCount > 3 && pages.size > 8) {
removed += pages.removeFirst().size
}
if (removed != 0) {
adapter?.notifyItemsRemovedStart(removed)
Log.i(TAG, "Removed $removed pages from start")
}
}
private fun trimEnd() {
var removed = 0
while (pages.groupCount > 3 && pages.size > 8) {
removed += pages.removeLast().size
}
if (removed != 0) {
adapter?.notifyItemsRemovedEnd(removed)
Log.i(TAG, "Removed $removed pages from end")
}
}
protected fun notifyPageChanged(page: Int) {
val chapters = manga.chapters ?: return
val chapterId = pages.findGroupByIndex(page) ?: return
val chapter = chapters.find { it.id == chapterId } ?: return
readerListener?.onPageChanged(
chapter = chapter,
page = page - pages.getGroupOffset(chapterId),
total = pages.getGroup(chapterId)?.size ?: return
)
}
protected fun saveState() {
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val page = pages.getRelativeIndex(getCurrentItem())
if (page != -1) {
readerListener?.saveState(chapterId, page)
}
Log.i(TAG, "saveState(chapterId=$chapterId, page=$page)")
}
open fun switchPageBy(delta: Int) {
setCurrentItem(getCurrentItem() + delta, true)
}
fun updateState(chapterId: Long = 0, pageId: Long = 0) {
val currentChapterId = pages.findGroupByIndex(getCurrentItem())
if (chapterId != 0L && chapterId != currentChapterId) {
pages.clear()
adapter?.notifyDataSetChanged()
loadChapter(chapterId) {
pages.clear()
pages.addLast(chapterId, it)
adapter?.notifyDataSetChanged()
setCurrentItem(
if (pageId == 0L) {
0
} else {
it.indexOfFirst { it.id == pageId }.coerceAtLeast(0)
}, false
)
}
} else {
setCurrentItem(
if (pageId == 0L) {
0
} else {
val chapterPages = pages.getGroup(currentChapterId ?: return) ?: return
chapterPages.indexOfFirst { it.id == pageId }
.coerceAtLeast(0) + pages.getGroupOffset(currentChapterId)
}, false
)
}
}
abstract val itemsCount: Int
protected abstract fun getCurrentItem(): Int
protected abstract fun setCurrentItem(position: Int, isSmooth: Boolean)
protected abstract fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter<*>
protected companion object {
const val ARG_STATE = "state"
private const val TAG = "AbstractReader"
}
}

@ -0,0 +1,52 @@
package org.koitharu.kotatsu.ui.reader.base
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
abstract class BaseReaderAdapter<E>(private val pages: GroupedList<Long, MangaPage>) :
RecyclerView.Adapter<BaseViewHolder<MangaPage, E>>() {
init {
@Suppress("LeakingThis")
setHasStableIds(true)
}
override fun onBindViewHolder(holder: BaseViewHolder<MangaPage, E>, position: Int) {
val item = pages[position]
holder.bind(item, getExtra(item, position))
}
fun getItem(position: Int) = pages[position]
fun notifyItemsAppended(count: Int) {
notifyItemRangeInserted(pages.size - count, count)
}
fun notifyItemsPrepended(count: Int) {
notifyItemRangeInserted(0, count)
}
fun notifyItemsRemovedStart(count: Int) {
notifyItemRangeRemoved(0, count)
}
fun notifyItemsRemovedEnd(count: Int) {
notifyItemRangeRemoved(pages.size - count, count)
}
override fun getItemId(position: Int) = pages[position].id
final override fun getItemCount() = pages.size
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<MangaPage, E> {
return onCreateViewHolder(parent).also(this::onViewHolderCreated)
}
protected abstract fun getExtra(item: MangaPage, position: Int): E
protected open fun onViewHolderCreated(holder: BaseViewHolder<MangaPage, E>) = Unit
protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<MangaPage, E>
}

@ -0,0 +1,229 @@
package org.koitharu.kotatsu.ui.reader.base
import java.util.*
class GroupedList<K, T> {
private val data = LinkedList<Pair<K, List<T>>>()
private var intSize: Int = -1
private var lruGroup: List<T>? = null
private var lruGroupKey: K? = null
private var lruGroupFirstIndex = -1
val size: Int
get() {
if (intSize < 0) {
computeSize()
}
return intSize
}
val groupCount: Int
get() = data.size
val isEmpty: Boolean
get() = size == 0
val isNotEmpty: Boolean
get() = size != 0
operator fun get(index: Int): T {
if (index >= lruGroupFirstIndex) {
val relIndex = index - lruGroupFirstIndex
lruGroup?.let {
if (relIndex in it.indices) {
return it[relIndex]
}
}
}
if (intSize < 0 || index < intSize shr 1) {
var firstIndex = 0
for (entry in data.iterator()) {
if (index < firstIndex + entry.second.size && index >= firstIndex) {
lruGroup = entry.second
lruGroupKey = entry.first
lruGroupFirstIndex = firstIndex
return entry.second[index - firstIndex]
}
firstIndex += entry.second.size
}
} else {
var lastIndex = intSize
for (entry in data.descendingIterator()) {
if (index < lastIndex && index >= lastIndex - entry.second.size) {
lruGroup = entry.second
lruGroupKey = entry.first
lruGroupFirstIndex = lastIndex - entry.second.size
return entry.second.get(index - lruGroupFirstIndex)
}
lastIndex -= entry.second.size
}
}
throw IndexOutOfBoundsException()
}
fun getOrNull(index: Int) = try {
get(index)
} catch (e: IndexOutOfBoundsException) {
null
}
fun getLastKey() = data.peekLast()?.first
fun getFirstKey() = data.peekFirst()?.first
fun getGroup(key: K): List<T>? {
if (key == lruGroupKey && lruGroup != null) {
return lruGroup
} else {
for(entry in data) {
if (entry.first == key) {
return entry.second
}
}
}
return null
}
fun getRelativeIndex(absIndex: Int): Int {
if (absIndex >= lruGroupFirstIndex) {
val relIndex = absIndex - lruGroupFirstIndex
lruGroup?.let {
if (relIndex in it.indices) {
return relIndex
}
}
}
if (intSize < 0 || absIndex < intSize shr 1) {
var firstIndex = 0
for (entry in data.iterator()) {
if (absIndex < firstIndex + entry.second.size && absIndex >= firstIndex) {
return absIndex - firstIndex
}
firstIndex += entry.second.size
}
} else {
var lastIndex = intSize
for (entry in data.descendingIterator()) {
if (absIndex < lastIndex && absIndex >= lastIndex - entry.second.size) {
return absIndex - lruGroupFirstIndex
}
lastIndex -= entry.second.size
}
}
return -1
}
fun findGroupByIndex(absIndex: Int): K? {
if (absIndex >= lruGroupFirstIndex && lruGroupKey != null) {
val relIndex = absIndex - lruGroupFirstIndex
lruGroup?.let {
if (relIndex in it.indices) {
return lruGroupKey
}
}
}
if (intSize < 0 || absIndex < intSize shr 1) {
var firstIndex = 0
for (entry in data.iterator()) {
if (absIndex < firstIndex + entry.second.size && absIndex >= firstIndex) {
return entry.first
}
firstIndex += entry.second.size
}
} else {
var lastIndex = intSize
for (entry in data.descendingIterator()) {
if (absIndex < lastIndex && absIndex >= lastIndex - entry.second.size) {
return entry.first
}
lastIndex -= entry.second.size
}
}
return null
}
fun getGroupOffset(key: K): Int {
if (lruGroupKey == key && lruGroupFirstIndex >= 0) {
return lruGroupFirstIndex
}
var offset = 0
for (entry in data) {
if (entry.first == key) {
return offset
}
offset += entry.second.size
}
return -1
}
fun indexOf(item: T): Int {
var offset = 0
for ((_, list) in data) {
for ((i, x) in list.withIndex()) {
if (x == item) {
return i + offset
}
}
offset += list.size
}
return -1
}
fun addLast(key: K, items: List<T>) {
data.addLast(key to items.toList())
if (intSize < 0) {
computeSize()
} else {
intSize += items.size
}
}
fun addFirst(key: K, items: List<T>) {
data.addFirst(key to items.toList())
if (lruGroupFirstIndex >= 0) {
lruGroupFirstIndex += items.size
}
if (intSize < 0) {
computeSize()
} else {
intSize += items.size
}
}
fun removeLast(): List<T> {
val item = data.removeLast()
if (intSize < 0) {
computeSize()
} else {
intSize -= item.second.size
}
return item.second
}
fun removeFirst(): List<T> {
val item = data.removeFirst()
if (intSize < 0) {
computeSize()
} else {
intSize -= item.second.size
}
if (lruGroupFirstIndex >= 0) {
lruGroupFirstIndex -= item.second.size
}
return item.second
}
fun clear() {
data.clear()
intSize = 0
lruGroupFirstIndex = -1
lruGroup = null
lruGroupKey = null
}
private fun computeSize() {
intSize = data.sumBy { it.second.size }
}
}

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.ui.reader package org.koitharu.kotatsu.ui.reader.base
interface OnBoundsScrollListener { interface OnBoundsScrollListener {

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.ui.reader.standard
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import org.koitharu.kotatsu.ui.reader.OnBoundsScrollListener import org.koitharu.kotatsu.ui.reader.base.OnBoundsScrollListener
class PagerPaginationListener( class PagerPaginationListener(
private val adapter: RecyclerView.Adapter<*>, private val adapter: RecyclerView.Adapter<*>,
@ -10,16 +10,19 @@ class PagerPaginationListener(
private val listener: OnBoundsScrollListener private val listener: OnBoundsScrollListener
) : ViewPager2.OnPageChangeCallback() { ) : ViewPager2.OnPageChangeCallback() {
private var lastItemCountStart = 0 private var firstItemId: Long = 0
private var lastItemCountEnd = 0 private var lastItemId: Long = 0
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
val itemCount = adapter.itemCount val itemCount = adapter.itemCount
if (position <= offset && itemCount != lastItemCountStart) { if (itemCount == 0) {
lastItemCountStart = itemCount return
}
if (position <= offset && adapter.getItemId(0) != firstItemId) {
firstItemId = adapter.getItemId(0)
listener.onScrolledToStart() listener.onScrolledToStart()
} else if (position >= itemCount - offset && itemCount != lastItemCountEnd) { } else if (position >= itemCount - offset && adapter.getItemId(itemCount - 1) != lastItemId) {
lastItemCountEnd = itemCount lastItemId = adapter.getItemId(itemCount - 1)
listener.onScrolledToEnd() listener.onScrolledToEnd()
} }
} }

@ -0,0 +1,52 @@
package org.koitharu.kotatsu.ui.reader.standard
import android.os.Bundle
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.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
class PagerReaderFragment() : AbstractReader(R.layout.fragment_reader_standard) {
private var paginationListener: PagerPaginationListener? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
paginationListener = PagerPaginationListener(adapter!!, 2, this)
pager.adapter = adapter
pager.offscreenPageLimit = 2
pager.registerOnPageChangeCallback(paginationListener!!)
pager.doOnPageChanged(::notifyPageChanged)
}
override fun onDestroyView() {
paginationListener = null
super.onDestroyView()
}
override fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter<*> {
return PagesAdapter(dataSet, loader)
}
override val itemsCount: Int
get() = adapter?.itemCount ?: 0
override fun getCurrentItem() = pager.currentItem
override fun setCurrentItem(position: Int, isSmooth: Boolean) {
pager.setCurrentItem(position, isSmooth)
}
companion object {
fun newInstance(state: ReaderState) = PagerReaderFragment().withArgs(1) {
putParcelable(ARG_STATE, state)
}
}
}

@ -2,15 +2,16 @@ package org.koitharu.kotatsu.ui.reader.standard
import android.view.ViewGroup import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
class PagesAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage, Unit>() { class PagesAdapter(
pages: GroupedList<Long, MangaPage>,
private val loader: PageLoader
) : BaseReaderAdapter<Unit>(pages) {
override fun onCreateViewHolder(parent: ViewGroup) = override fun onCreateViewHolder(parent: ViewGroup) = PageHolder(parent, loader)
PageHolder(parent, loader)
override fun onGetItemId(item: MangaPage) = item.id
override fun getExtra(item: MangaPage, position: Int) = Unit override fun getExtra(item: MangaPage, position: Int) = Unit
} }

@ -1,97 +0,0 @@
package org.koitharu.kotatsu.ui.reader.standard
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.fragment_reader_standard.*
import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.reader.*
import org.koitharu.kotatsu.utils.ext.callOnPageChaneListeners
import org.koitharu.kotatsu.utils.ext.doOnPageChanged
class StandardReaderFragment : BaseReaderFragment(R.layout.fragment_reader_standard),
OnBoundsScrollListener {
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
private var adapter: PagesAdapter? = null
private lateinit var loader: PageLoader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loader = PageLoader()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = PagesAdapter(loader)
pager.adapter = adapter
pager.offscreenPageLimit = 2
pager.registerOnPageChangeCallback(PagerPaginationListener(adapter!!, 2, this))
pager.doOnPageChanged(::notifyPageChanged)
}
override fun onDestroyView() {
adapter = null
super.onDestroyView()
}
override fun onPagesLoaded(chapterId: Long, pages: List<MangaPage>, action: ReaderAction) {
super.onPagesLoaded(chapterId, pages, action)
when (action) {
ReaderAction.REPLACE -> adapter?.let {
it.replaceData(pages)
lastState?.let { state ->
if (chapterId == state.chapterId) {
pager.setCurrentItem(findChapterOffset(chapterId) + state.page, false)
}
}
}
ReaderAction.PREPEND -> adapter?.run {
val pos = pager.currentItem
prependData(pages)
pager.setCurrentItem(pos + pages.size, false)
}
ReaderAction.APPEND -> adapter?.appendData(pages)
}
pager.callOnPageChaneListeners()
}
override fun onDestroy() {
loader.dispose()
super.onDestroy()
}
override fun onScrolledToStart() {
val prevChapterId = getPrevChapterId()
if (prevChapterId != 0L) {
presenter.loadChapter(lastState?.manga ?: return, prevChapterId, ReaderAction.PREPEND)
}
}
override fun onScrolledToEnd() {
val nextChapterId = getNextChapterId()
if (nextChapterId != 0L) {
presenter.loadChapter(lastState?.manga ?: return, nextChapterId, ReaderAction.APPEND)
}
}
override val hasItems: Boolean
get() = adapter?.hasItems == true
override val currentPageIndex: Int
get() = pager.currentItem
override val pages: List<MangaPage>
get() = adapter?.items.orEmpty()
override fun setCurrentPage(index: Int, smooth: Boolean) {
pager.setCurrentItem(index, smooth)
}
private companion object {
const val SCROLL_OFFSET = 2
}
}

@ -2,28 +2,31 @@ package org.koitharu.kotatsu.ui.reader.wetoon
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.ui.reader.OnBoundsScrollListener import org.koitharu.kotatsu.ui.reader.base.OnBoundsScrollListener
class ListPaginationListener( class ListPaginationListener(
private val offset: Int, private val offset: Int,
private val listener: OnBoundsScrollListener private val listener: OnBoundsScrollListener
) : RecyclerView.OnScrollListener() { ) : RecyclerView.OnScrollListener() {
private var lastItemCountStart = 0 private var firstItemId: Long = 0
private var lastItemCountEnd = 0 private var lastItemId: Long = 0
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) val adapter = recyclerView.adapter ?: return
val itemCount = recyclerView.adapter?.itemCount ?: return
val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return
val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition() val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
val lastVisiblePosition = layoutManager.findLastVisibleItemPosition() val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
if (firstVisiblePosition <= offset && itemCount != lastItemCountStart) { val itemCount = adapter.itemCount
lastItemCountStart = itemCount if (itemCount == 0) {
listener.onScrolledToStart() return
} else if (lastVisiblePosition >= itemCount - offset && itemCount != lastItemCountEnd) { }
lastItemCountEnd = itemCount if (lastVisiblePosition >= itemCount - offset && adapter.getItemId(itemCount - 1) != lastItemId) {
lastItemId = adapter.getItemId(itemCount - 1)
listener.onScrolledToEnd() listener.onScrolledToEnd()
} else if (firstVisiblePosition <= offset && adapter.getItemId(0) != firstItemId) {
firstItemId = adapter.getItemId(0)
listener.onScrolledToStart()
} }
} }
} }

@ -3,18 +3,18 @@ package org.koitharu.kotatsu.ui.reader.wetoon
import android.view.Gravity import android.view.Gravity
import android.view.ViewGroup import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
class WebtoonAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage, Int>() { class WebtoonAdapter(
pages: GroupedList<Long, MangaPage>,
private val loader: PageLoader
) : BaseReaderAdapter<Int>(pages) {
var pageGravity: Int = Gravity.TOP var pageGravity: Int = Gravity.TOP
override fun onCreateViewHolder(parent: ViewGroup) = override fun onCreateViewHolder(parent: ViewGroup) = WebtoonHolder(parent, loader)
WebtoonHolder(parent, loader)
override fun onGetItemId(item: MangaPage) = item.id
override fun getExtra(item: MangaPage, position: Int) = pageGravity override fun getExtra(item: MangaPage, position: Int) = pageGravity
} }

@ -3,94 +3,66 @@ package org.koitharu.kotatsu.ui.reader.wetoon
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_reader_webtoon.* import kotlinx.android.synthetic.main.fragment_reader_webtoon.*
import moxy.ktx.moxyPresenter
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.* import org.koitharu.kotatsu.ui.reader.ReaderState
import org.koitharu.kotatsu.utils.ext.callOnScrollListeners 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.doOnCurrentItemChanged
import org.koitharu.kotatsu.utils.ext.findMiddleVisibleItemPosition
import org.koitharu.kotatsu.utils.ext.firstItem import org.koitharu.kotatsu.utils.ext.firstItem
import org.koitharu.kotatsu.utils.ext.withArgs
class WebtoonReaderFragment : BaseReaderFragment(R.layout.fragment_reader_webtoon), class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
OnBoundsScrollListener {
private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance)
private var adapter: WebtoonAdapter? = null
private lateinit var loader: PageLoader
private val scrollInterpolator = AccelerateDecelerateInterpolator() private val scrollInterpolator = AccelerateDecelerateInterpolator()
protected var paginationListener: ListPaginationListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loader = PageLoader()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = WebtoonAdapter(loader) paginationListener = ListPaginationListener(2, this)
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapter recyclerView.adapter = adapter
recyclerView.addOnScrollListener(ListPaginationListener(2, this)) recyclerView.addOnScrollListener(paginationListener!!)
recyclerView.doOnCurrentItemChanged(::notifyPageChanged) recyclerView.doOnCurrentItemChanged(::notifyPageChanged)
} }
override fun onPagesLoaded(chapterId: Long, pages: List<MangaPage>, action: ReaderAction) { override fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter<*> {
super.onPagesLoaded(chapterId, pages, action) return WebtoonAdapter(dataSet, loader)
when(action) {
ReaderAction.REPLACE -> {
adapter?.let {
it.replaceData(pages)
lastState?.let { state ->
if (chapterId == state.chapterId) {
recyclerView.firstItem = state.page
}
}
}
}
ReaderAction.PREPEND -> adapter?.prependData(pages)
ReaderAction.APPEND -> adapter?.appendData(pages)
}
recyclerView.callOnScrollListeners()
} }
override fun onScrolledToStart() { override fun onDestroyView() {
val prevChapterId = getPrevChapterId() paginationListener = null
if (prevChapterId != 0L) { super.onDestroyView()
presenter.loadChapter(lastState?.manga ?: return, prevChapterId, ReaderAction.PREPEND)
}
} }
override fun onScrolledToEnd() { override val itemsCount: Int
val nextChapterId = getNextChapterId() get() = adapter?.itemCount ?: 0
if (nextChapterId != 0L) {
presenter.loadChapter(lastState?.manga ?: return, nextChapterId, ReaderAction.APPEND)
}
}
override fun onDestroy() { override fun getCurrentItem(): Int {
loader.dispose() return (recyclerView.layoutManager as LinearLayoutManager).findMiddleVisibleItemPosition()
super.onDestroy()
} }
override val hasItems: Boolean override fun setCurrentItem(position: Int, isSmooth: Boolean) {
get() = adapter?.hasItems == true if (isSmooth) {
recyclerView.smoothScrollToPosition(position)
override val currentPageIndex: Int
get() = recyclerView.firstItem
override val pages: List<MangaPage>
get() = adapter?.items.orEmpty()
override fun setCurrentPage(index: Int, smooth: Boolean) {
if (smooth) {
recyclerView.smoothScrollToPosition(index)
} else { } else {
recyclerView.firstItem = index recyclerView.firstItem = position
} }
} }
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)
} }
companion object {
fun newInstance(state: ReaderState) = WebtoonReaderFragment().withArgs(1) {
putParcelable(ARG_STATE, state)
}
}
} }

@ -1,6 +1,6 @@
#Wed Feb 26 19:30:06 EET 2020 #Sun Mar 08 18:35:17 EET 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip

Loading…
Cancel
Save