Use ArrayDeque in reader

This commit is contained in:
Koitharu
2020-10-28 07:56:16 +02:00
parent a885709ba9
commit 72fdc7796f
18 changed files with 192 additions and 359 deletions

View File

@@ -63,8 +63,8 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0'
implementation 'androidx.core:core-ktx:1.5.0-alpha04'
implementation 'androidx.activity:activity-ktx:1.2.0-beta01'

View File

@@ -5,7 +5,6 @@ import android.view.View
import androidx.core.net.toUri
import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.coroutines.Dispatchers
@@ -73,7 +72,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
)
}
manga.url.toUri().toFileOrNull()?.let { f ->
lifecycleScope.launch {
viewLifecycleScope.launch {
val size = withContext(Dispatchers.IO) {
f.length()
}

View File

@@ -27,7 +27,7 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle {
private val convertLock = Mutex()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main.immediate + job
get() = job + Dispatchers.Main.immediate
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun loadFile(url: String, force: Boolean): File {
@@ -88,7 +88,7 @@ class PageLoader : KoinComponent, CoroutineScope, DisposableHandle {
}
override fun dispose() {
coroutineContext.cancel()
job.cancelChildren()
tasks.clear()
}
}

View File

@@ -14,14 +14,14 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.core.view.updatePadding
import androidx.core.graphics.Insets
import androidx.core.view.*
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -53,7 +53,7 @@ import org.koitharu.kotatsu.utils.ext.*
class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnChapterChangeListener,
GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback,
ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener,
View.OnApplyWindowInsetsListener, ActivityResultCallback<Boolean> {
ActivityResultCallback<Boolean>, OnApplyWindowInsetsListener {
private val presenter by moxyPresenter(factory = ::ReaderPresenter)
private val settings by inject<AppSettings>()
@@ -93,7 +93,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
getString(R.string.chapter_d_of_d, state.chapter?.number ?: 0, size)
}
rootLayout.setOnApplyWindowInsetsListener(this)
ViewCompat.setOnApplyWindowInsetsListener(rootLayout, this)
settings.subscribe(this)
loadSettings()
@@ -105,10 +105,8 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
presenter.init(state.manga)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
GlobalScope.launch {
safe {
MangaShortcut(state.manga).addAppShortcut(applicationContext)
}
GlobalScope.launch(Dispatchers.Main + IgnoreErrors) {
MangaShortcut(state.manga).addAppShortcut(applicationContext)
}
}
}
@@ -191,7 +189,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
R.id.action_pages_thumbs -> {
if (reader?.hasItems == true) {
val pages = reader?.getPages()
if (pages != null) {
if (!pages.isNullOrEmpty()) {
PagesThumbnailsSheet.show(
supportFragmentManager, pages,
state.chapter?.name ?: title?.toString().orEmpty()
@@ -363,7 +361,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
}
}
override fun onPageChanged(chapter: MangaChapter, page: Int, total: Int) {
override fun onPageChanged(chapter: MangaChapter, page: Int) {
title = chapter.name
state.manga.chapters?.run {
supportActionBar?.subtitle =
@@ -395,18 +393,21 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
}
}
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
appbar_top.updatePadding(
top = insets.systemWindowInsetTop,
right = insets.systemWindowInsetRight,
left = insets.systemWindowInsetLeft
top = systemBars.top,
right = systemBars.right,
left = systemBars.left
)
appbar_bottom.updatePadding(
bottom = insets.systemWindowInsetBottom,
right = insets.systemWindowInsetRight,
left = insets.systemWindowInsetLeft
bottom = systemBars.bottom,
right = systemBars.right,
left = systemBars.left
)
return insets.consumeSystemWindowInsets()
return WindowInsetsCompat.Builder(insets)
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)
.build()
}
private fun loadSettings() {

View File

@@ -5,7 +5,7 @@ import org.koitharu.kotatsu.ui.base.BaseMvpView
interface ReaderListener : BaseMvpView {
fun onPageChanged(chapter: MangaChapter, page: Int, total: Int)
fun onPageChanged(chapter: MangaChapter, page: Int)
fun saveState(chapterId: Long, page: Int, scroll: Int)
}

View File

@@ -1,45 +1,52 @@
package org.koitharu.kotatsu.ui.reader.base
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.collection.LongSparseArray
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.MangaChapter
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.base.BaseFragment
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.ReaderListener
import org.koitharu.kotatsu.ui.reader.ReaderState
import org.koitharu.kotatsu.utils.ext.associateByLong
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
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>
private lateinit var chapters: LongSparseArray<MangaChapter>
protected val loader by lazy(LazyThreadSafetyMode.NONE) {
PageLoader()
}
protected val pages = ArrayDeque<ReaderPage>()
protected var adapter: BaseReaderAdapter? = null
private set
val itemsCount: Int
get() = adapter?.itemCount ?: 0
val hasItems: Boolean
get() = itemsCount != 0
val currentPage: MangaPage?
get() = pages.getOrNull(getCurrentItem())
get() = pages.getOrNull(getCurrentItem())?.toMangaPage()
protected val readerListener: ReaderListener?
get() = activity as? ReaderListener
private var readerListener: ReaderListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pages = GroupedList()
manga = requireArguments().getParcelable<ReaderState>(ARG_STATE)!!.manga
loader = PageLoader()
manga = requireNotNull(requireArguments().getParcelable<ReaderState>(ARG_STATE)).manga
chapters = requireNotNull(manga.chapters).associateByLong { it.id }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -50,7 +57,9 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
?: requireArguments().getParcelable<ReaderState>(ARG_STATE)!!
loadChapter(state.chapterId) {
pages.clear()
pages.addLast(state.chapterId, it)
it.mapIndexedTo(pages) { i, p ->
ReaderPage.from(p, i, state.chapterId)
}
adapter?.notifyDataSetChanged()
setCurrentItem(state.page, false)
if (state.scroll != 0) {
@@ -59,24 +68,37 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
readerListener = activity as? ReaderListener
}
override fun onDetach() {
readerListener = null
super.onDetach()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val page = pages.getOrNull(getCurrentItem()) ?: return
outState.putParcelable(
ARG_STATE, ReaderState(
manga = manga,
chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return,
page = pages.getRelativeIndex(getCurrentItem()),
chapterId = page.chapterId,
page = page.index,
scroll = getCurrentPageScroll()
)
)
}
override fun onScrolledToStart() {
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val chapterId = getFirstPage()?.chapterId ?: 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)
pages.addAll(0, it.mapIndexed { i, p ->
ReaderPage.from(p, i, prevChapterId)
})
adapter?.notifyItemsPrepended(it.size)
view?.postDelayed(500) {
trimEnd()
@@ -85,11 +107,13 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
}
override fun onScrolledToEnd() {
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val index = manga.chapters?.indexOfFirst { it.id == chapterId } ?: return
val chapterId = getLastPage()?.chapterId ?: return
val index = manga.chapters?.indexOfLast { it.id == chapterId } ?: return
val nextChapterId = manga.chapters!!.getOrNull(index + 1)?.id ?: return
loadChapter(nextChapterId) {
pages.addLast(nextChapterId, it)
pages.addAll(it.mapIndexed { i, p ->
ReaderPage.from(p, i, nextChapterId)
})
adapter?.notifyItemsAppended(it.size)
view?.postDelayed(500) {
trimStart()
@@ -107,8 +131,10 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
super.onDestroy()
}
fun getPages() = pages.findGroupByIndex(getCurrentItem())?.let {
pages.getGroup(it)
fun getPages(): List<MangaPage>? {
val chapterId = (pages.getOrNull(getCurrentItem()) ?: return null).chapterId
// TODO optimize
return pages.filter { it.chapterId == chapterId }.map { it.toMangaPage() }
}
override fun onPause() {
@@ -117,11 +143,11 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
}
private fun loadChapter(chapterId: Long, callback: suspend (List<MangaPage>) -> Unit) {
lifecycleScope.launch {
viewLifecycleScope.launch {
readerListener?.onLoadingStateChanged(isLoading = true)
try {
val pages = withContext(Dispatchers.IO) {
val chapter = manga.chapters?.find { it.id == chapterId }
val pages = withContext(Dispatchers.Default) {
val chapter = chapters.get(chapterId)
?: throw RuntimeException("Chapter $chapterId not found")
val repo = manga.source.repository
repo.getPages(chapter)
@@ -137,45 +163,39 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
}
private fun trimStart() {
var removed = 0
/*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
/*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
protected fun notifyPageChanged(position: Int) {
val page = pages.getOrNull(position) ?: return
val chapter = chapters.get(page.chapterId) ?: return
readerListener?.onPageChanged(
chapter = chapter,
page = page - pages.getGroupOffset(chapterId),
total = pages.getGroup(chapterId)?.size ?: return
page = page.index
)
}
protected fun saveState() {
val chapterId = pages.findGroupByIndex(getCurrentItem()) ?: return
val page = pages.getRelativeIndex(getCurrentItem())
if (page != -1) {
readerListener?.saveState(chapterId, page, getCurrentPageScroll())
}
Log.i(TAG, "saveState(chapterId=$chapterId, page=$page)")
private fun saveState() {
val page = pages.getOrNull(getCurrentItem()) ?: return
readerListener?.saveState(page.chapterId, page.index, getCurrentPageScroll())
}
open fun switchPageBy(delta: Int) {
@@ -183,13 +203,15 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
}
fun updateState(chapterId: Long = 0, pageId: Long = 0) {
val currentChapterId = pages.findGroupByIndex(getCurrentItem())
val currentChapterId = pages.getOrNull(getCurrentItem())?.chapterId ?: 0L
if (chapterId != 0L && chapterId != currentChapterId) {
pages.clear()
adapter?.notifyDataSetChanged()
loadChapter(chapterId) {
pages.clear()
pages.addLast(chapterId, it)
it.mapIndexedTo(pages) { i, p ->
ReaderPage.from(p, i, chapterId)
}
adapter?.notifyDataSetChanged()
setCurrentItem(
if (pageId == 0L) {
@@ -200,19 +222,27 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
)
}
} 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
)
var index = 0
if (pageId != 0L) {
index = pages.indexOfFirst {
it.chapterId == currentChapterId && it.id == pageId
}
if (index == -1) { // try to find chapter at least
index = pages.indexOfFirst {
it.chapterId == currentChapterId
}
}
if (index == -1) {
index = 0
}
}
setCurrentItem(index, false)
}
}
abstract val itemsCount: Int
protected open fun getLastPage() = pages.lastOrNull()
protected open fun getFirstPage() = pages.firstOrNull()
protected abstract fun getCurrentItem(): Int
@@ -222,12 +252,10 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
protected abstract fun setCurrentItem(position: Int, isSmooth: Boolean)
protected abstract fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter
protected abstract fun onCreateAdapter(dataSet: List<ReaderPage>): BaseReaderAdapter
protected companion object {
const val ARG_STATE = "state"
private const val TAG = "AbstractReader"
}
}

View File

@@ -2,18 +2,17 @@ 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.base.list.BaseViewHolder
abstract class BaseReaderAdapter(protected val pages: GroupedList<Long, MangaPage>) :
RecyclerView.Adapter<BaseViewHolder<MangaPage, Unit>>() {
abstract class BaseReaderAdapter(protected val pages: List<ReaderPage>) :
RecyclerView.Adapter<BaseViewHolder<ReaderPage, Unit>>() {
init {
@Suppress("LeakingThis")
setHasStableIds(true)
}
override fun onBindViewHolder(holder: BaseViewHolder<MangaPage, Unit>, position: Int) {
override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit>, position: Int) {
val item = pages[position]
holder.bind(item, Unit)
}
@@ -36,18 +35,18 @@ abstract class BaseReaderAdapter(protected val pages: GroupedList<Long, MangaPag
notifyItemRangeRemoved(pages.size - count, count)
}
open override fun getItemId(position: Int) = pages[position].id
override fun getItemId(position: Int) = pages[position].id
final override fun getItemCount() = pages.size
final override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BaseViewHolder<MangaPage, Unit> {
): BaseViewHolder<ReaderPage, Unit> {
return onCreateViewHolder(parent).also(this::onViewHolderCreated)
}
protected open fun onViewHolderCreated(holder: BaseViewHolder<MangaPage, Unit>) = Unit
protected open fun onViewHolderCreated(holder: BaseViewHolder<ReaderPage, Unit>) = Unit
protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<MangaPage, Unit>
protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<ReaderPage, Unit>
}

View File

@@ -1,229 +0,0 @@
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[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 }
}
}

View File

@@ -0,0 +1,36 @@
package org.koitharu.kotatsu.ui.reader.base
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaSource
@Parcelize
data class ReaderPage(
val id: Long,
val url: String,
val preview: String?,
val chapterId: Long,
val index: Int,
val source: MangaSource
) : Parcelable {
fun toMangaPage() = MangaPage(
id = id,
url = url,
preview = preview,
source = source
)
companion object {
fun from(page: MangaPage, index: Int, chapterId: Long) = ReaderPage(
id = page.id,
url = page.url,
preview = page.preview,
chapterId = chapterId,
index = index,
source = page.source
)
}
}

View File

@@ -1,25 +1,24 @@
package org.koitharu.kotatsu.ui.reader.reversed
import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.ui.reader.standard.PageHolder
class ReversedPagesAdapter(
pages: GroupedList<Long, MangaPage>,
pages: List<ReaderPage>,
private val loader: PageLoader
) : BaseReaderAdapter(pages) {
override fun onCreateViewHolder(parent: ViewGroup) = PageHolder(parent, loader)
override fun onBindViewHolder(holder: BaseViewHolder<MangaPage, Unit>, position: Int) {
override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit>, position: Int) {
super.onBindViewHolder(holder, reversed(position))
}
override fun getItem(position: Int): MangaPage {
override fun getItem(position: Int): ReaderPage {
return super.getItem(reversed(position))
}

View File

@@ -7,12 +7,11 @@ import android.view.View
import kotlinx.android.synthetic.main.fragment_reader_standard.*
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.ui.reader.ReaderState
import org.koitharu.kotatsu.ui.reader.base.AbstractReader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.ui.reader.standard.PageAnimTransformer
import org.koitharu.kotatsu.ui.reader.standard.PagerPaginationListener
import org.koitharu.kotatsu.utils.ext.doOnPageChanged
@@ -53,13 +52,10 @@ class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard)
super.onDestroyView()
}
override fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter {
override fun onCreateAdapter(dataSet: List<ReaderPage>): BaseReaderAdapter {
return ReversedPagesAdapter(dataSet, loader)
}
override val itemsCount: Int
get() = adapter?.itemCount ?: 0
override fun getCurrentItem() = reversed(pager.currentItem)
override fun setCurrentItem(position: Int, isSmooth: Boolean) {
@@ -82,6 +78,10 @@ class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard)
}
}
override fun getLastPage() = pages.firstOrNull()
override fun getFirstPage() = pages.lastOrNull()
private fun reversed(position: Int) = (itemsCount - position - 1).coerceAtLeast(0)
companion object {

View File

@@ -7,14 +7,14 @@ import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource
import kotlinx.android.synthetic.main.item_page.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class PageHolder(parent: ViewGroup, loader: PageLoader) :
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page),
BaseViewHolder<ReaderPage, Unit>(parent, R.layout.item_page),
PageHolderDelegate.Callback, View.OnClickListener {
private val delegate = PageHolderDelegate(loader, this)
@@ -24,8 +24,8 @@ class PageHolder(parent: ViewGroup, loader: PageLoader) :
button_retry.setOnClickListener(this)
}
override fun onBind(data: MangaPage, extra: Unit) {
delegate.onBind(data)
override fun onBind(data: ReaderPage, extra: Unit) {
delegate.onBind(data.toMangaPage())
}
override fun onRecycled() {
@@ -57,7 +57,7 @@ class PageHolder(parent: ViewGroup, loader: PageLoader) :
override fun onClick(v: View) {
when (v.id) {
R.id.button_retry -> delegate.retry(boundData ?: return)
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return)
}
}

View File

@@ -7,12 +7,11 @@ import android.view.View
import kotlinx.android.synthetic.main.fragment_reader_standard.*
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.ui.reader.ReaderState
import org.koitharu.kotatsu.ui.reader.base.AbstractReader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.doOnPageChanged
import org.koitharu.kotatsu.utils.ext.withArgs
@@ -49,13 +48,10 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
super.onDestroyView()
}
override fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter {
override fun onCreateAdapter(dataSet: List<ReaderPage>): 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) {

View File

@@ -1,13 +1,12 @@
package org.koitharu.kotatsu.ui.reader.standard
import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaPage
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.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
class PagesAdapter(
pages: GroupedList<Long, MangaPage>,
pages: List<ReaderPage>,
private val loader: PageLoader
) : BaseReaderAdapter(pages) {

View File

@@ -1,13 +1,12 @@
package org.koitharu.kotatsu.ui.reader.wetoon
import android.view.ViewGroup
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
class WebtoonAdapter(
pages: GroupedList<Long, MangaPage>,
pages: List<ReaderPage>,
private val loader: PageLoader
) : BaseReaderAdapter(pages) {

View File

@@ -8,15 +8,15 @@ import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page_webtoon.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page_webtoon),
BaseViewHolder<ReaderPage, Unit>(parent, R.layout.item_page_webtoon),
PageHolderDelegate.Callback, View.OnClickListener {
private val delegate = PageHolderDelegate(loader, this)
@@ -27,8 +27,8 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
button_retry.setOnClickListener(this)
}
override fun onBind(data: MangaPage, extra: Unit) {
delegate.onBind(data)
override fun onBind(data: ReaderPage, extra: Unit) {
delegate.onBind(data.toMangaPage())
}
override fun onRecycled() {
@@ -65,7 +65,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
override fun onClick(v: View) {
when (v.id) {
R.id.button_retry -> delegate.retry(boundData ?: return)
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return)
}
}

View File

@@ -5,11 +5,10 @@ import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import kotlinx.android.synthetic.main.fragment_reader_webtoon.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.reader.ReaderState
import org.koitharu.kotatsu.ui.reader.base.AbstractReader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.GroupedList
import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged
import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
import org.koitharu.kotatsu.utils.ext.firstItem
@@ -29,7 +28,7 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
recyclerView.doOnCurrentItemChanged(::notifyPageChanged)
}
override fun onCreateAdapter(dataSet: GroupedList<Long, MangaPage>): BaseReaderAdapter {
override fun onCreateAdapter(dataSet: List<ReaderPage>): BaseReaderAdapter {
return WebtoonAdapter(dataSet, loader)
}
@@ -38,9 +37,6 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
super.onDestroyView()
}
override val itemsCount: Int
get() = adapter?.itemCount ?: 0
override fun getCurrentItem(): Int {
return recyclerView.findCenterViewPosition()
}

View File

@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.utils.ext
import androidx.collection.ArrayMap
import androidx.collection.ArraySet
import androidx.collection.LongSparseArray
fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
clear()
@@ -56,4 +58,12 @@ fun LongArray.toArraySet(): Set<Long> {
}
}
fun <K, V> List<Pair<K, V>>.toMutableMap(): MutableMap<K, V> = toMap(HashMap<K, V>(size))
fun <K, V> List<Pair<K, V>>.toMutableMap(): MutableMap<K, V> = toMap(ArrayMap(size))
inline fun <T> Collection<T>.associateByLong(selector: (T) -> Long): LongSparseArray<T> {
val result = LongSparseArray<T>(size)
for (item in this) {
result.put(selector(item), item)
}
return result
}