From 72fdc7796ff25be81bfa94a1cfcf454651997c90 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 28 Oct 2020 07:56:16 +0200 Subject: [PATCH] Use ArrayDeque in reader --- app/build.gradle | 4 +- .../ui/details/MangaDetailsFragment.kt | 3 +- .../koitharu/kotatsu/ui/reader/PageLoader.kt | 4 +- .../kotatsu/ui/reader/ReaderActivity.kt | 39 +-- .../kotatsu/ui/reader/ReaderListener.kt | 2 +- .../kotatsu/ui/reader/base/AbstractReader.kt | 138 ++++++----- .../ui/reader/base/BaseReaderAdapter.kt | 15 +- .../kotatsu/ui/reader/base/GroupedList.kt | 229 ------------------ .../kotatsu/ui/reader/base/ReaderPage.kt | 36 +++ .../reader/reversed/ReversedPagesAdapter.kt | 9 +- .../reader/reversed/ReversedReaderFragment.kt | 12 +- .../kotatsu/ui/reader/standard/PageHolder.kt | 10 +- .../ui/reader/standard/PagerReaderFragment.kt | 8 +- .../ui/reader/standard/PagesAdapter.kt | 7 +- .../ui/reader/wetoon/WebtoonAdapter.kt | 5 +- .../kotatsu/ui/reader/wetoon/WebtoonHolder.kt | 10 +- .../ui/reader/wetoon/WebtoonReaderFragment.kt | 8 +- .../kotatsu/utils/ext/CollectionExt.kt | 12 +- 18 files changed, 192 insertions(+), 359 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/reader/base/GroupedList.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt diff --git a/app/build.gradle b/app/build.gradle index 12b0e51f5..788959cbb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt index 9f774a84c..5bc0a6832 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/details/MangaDetailsFragment.kt @@ -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() } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt index 37f9c2640..d369c5618 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/PageLoader.kt @@ -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() } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt index ec9fc5cda..d4b9c0a15 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderActivity.kt @@ -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 { + ActivityResultCallback, OnApplyWindowInsetsListener { private val presenter by moxyPresenter(factory = ::ReaderPresenter) private val settings by inject() @@ -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() { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt index 943f15444..6a98189fc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderListener.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt index e3a0c02c4..446488e40 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt @@ -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 + private lateinit var chapters: LongSparseArray + protected val loader by lazy(LazyThreadSafetyMode.NONE) { + PageLoader() + } + protected val pages = ArrayDeque() 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(ARG_STATE)!!.manga - loader = PageLoader() + manga = requireNotNull(requireArguments().getParcelable(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(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? { + 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) -> 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): BaseReaderAdapter + protected abstract fun onCreateAdapter(dataSet: List): BaseReaderAdapter protected companion object { const val ARG_STATE = "state" - private const val TAG = "AbstractReader" - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt index fd1bff66e..b90e752ca 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt @@ -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) : - RecyclerView.Adapter>() { +abstract class BaseReaderAdapter(protected val pages: List) : + RecyclerView.Adapter>() { init { @Suppress("LeakingThis") setHasStableIds(true) } - override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { val item = pages[position] holder.bind(item, Unit) } @@ -36,18 +35,18 @@ abstract class BaseReaderAdapter(protected val pages: GroupedList { + ): BaseViewHolder { return onCreateViewHolder(parent).also(this::onViewHolderCreated) } - protected open fun onViewHolderCreated(holder: BaseViewHolder) = Unit + protected open fun onViewHolderCreated(holder: BaseViewHolder) = Unit - protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder + protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/GroupedList.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/GroupedList.kt deleted file mode 100644 index fea6700b3..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/GroupedList.kt +++ /dev/null @@ -1,229 +0,0 @@ -package org.koitharu.kotatsu.ui.reader.base - -import java.util.* - -class GroupedList { - - private val data = LinkedList>>() - - private var intSize: Int = -1 - private var lruGroup: List? = 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? { - 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) { - data.addLast(key to items.toList()) - if (intSize < 0) { - computeSize() - } else { - intSize += items.size - } - } - - fun addFirst(key: K, items: List) { - data.addFirst(key to items.toList()) - if (lruGroupFirstIndex >= 0) { - lruGroupFirstIndex += items.size - } - if (intSize < 0) { - computeSize() - } else { - intSize += items.size - } - } - - fun removeLast(): List { - val item = data.removeLast() - if (intSize < 0) { - computeSize() - } else { - intSize -= item.second.size - } - return item.second - } - - fun removeFirst(): List { - 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 } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt new file mode 100644 index 000000000..5f1807f68 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/ReaderPage.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt index 86259e78c..f7021e9a1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedPagesAdapter.kt @@ -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, + pages: List, private val loader: PageLoader ) : BaseReaderAdapter(pages) { override fun onCreateViewHolder(parent: ViewGroup) = PageHolder(parent, loader) - override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { super.onBindViewHolder(holder, reversed(position)) } - override fun getItem(position: Int): MangaPage { + override fun getItem(position: Int): ReaderPage { return super.getItem(reversed(position)) } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt index 400f59913..7244f953d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/reversed/ReversedReaderFragment.kt @@ -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): BaseReaderAdapter { + override fun onCreateAdapter(dataSet: List): 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 { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt index 57e71b9e7..aae73b011 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PageHolder.kt @@ -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(parent, R.layout.item_page), + BaseViewHolder(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) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt index 029178d02..21912c967 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt @@ -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): BaseReaderAdapter { + override fun onCreateAdapter(dataSet: List): 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) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt index 6793af341..084f057c6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagesAdapter.kt @@ -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, + pages: List, private val loader: PageLoader ) : BaseReaderAdapter(pages) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt index 32ef3ee79..7972b9d8c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonAdapter.kt @@ -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, + pages: List, private val loader: PageLoader ) : BaseReaderAdapter(pages) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt index 044ce4732..9dc235b98 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonHolder.kt @@ -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(parent, R.layout.item_page_webtoon), + BaseViewHolder(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) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt index ed2fca011..29fd10751 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/WebtoonReaderFragment.kt @@ -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): BaseReaderAdapter { + override fun onCreateAdapter(dataSet: List): 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() } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index c4ea177d9..f1e895bf0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -1,6 +1,8 @@ package org.koitharu.kotatsu.utils.ext +import androidx.collection.ArrayMap import androidx.collection.ArraySet +import androidx.collection.LongSparseArray fun MutableCollection.replaceWith(subject: Iterable) { clear() @@ -56,4 +58,12 @@ fun LongArray.toArraySet(): Set { } } -fun List>.toMutableMap(): MutableMap = toMap(HashMap(size)) \ No newline at end of file +fun List>.toMutableMap(): MutableMap = toMap(ArrayMap(size)) + +inline fun Collection.associateByLong(selector: (T) -> Long): LongSparseArray { + val result = LongSparseArray(size) + for (item in this) { + result.put(selector(item), item) + } + return result +} \ No newline at end of file