Remove presenter from reader fragments
parent
4a4c7108cb
commit
fec3481d27
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
@ -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 {
|
||||||
|
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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…
Reference in New Issue