diff --git a/app/build.gradle b/app/build.gradle index bd0f5ce07..0181471fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,7 +84,7 @@ dependencies { implementation 'com.squareup.okio:okio:2.4.3' implementation 'org.jsoup:jsoup:1.12.2' - implementation 'org.koin:koin-android:2.1.1' + implementation 'org.koin:koin-android:2.1.3' implementation 'io.coil-kt:coil:0.9.5' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' implementation 'com.tomclaw.cache:cache:1.0' diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/BaseReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/BaseReaderFragment.kt deleted file mode 100644 index 75ff7838c..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/BaseReaderFragment.kt +++ /dev/null @@ -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>() as Deque> - - protected val lastState - get() = (activity as? ReaderActivity)?.state - - abstract val hasItems: Boolean - - protected abstract val currentPageIndex: Int - - abstract val pages: List - - 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) = Unit - - override fun onDestroyView() { - chaptersMap.clear() - super.onDestroyView() - } - - @CallSuper - override fun onPagesLoaded(chapterId: Long, pages: List, 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? { - 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 - ) - } -} \ 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 2b1ffd889..5e2fd9dd9 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 @@ -9,6 +9,7 @@ import android.os.Bundle import android.view.* import android.widget.Toast import androidx.core.view.isVisible +import androidx.core.view.postDelayed import androidx.core.view.updatePadding import androidx.fragment.app.commit import com.google.android.material.snackbar.Snackbar @@ -24,7 +25,8 @@ import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity -import org.koitharu.kotatsu.ui.reader.standard.StandardReaderFragment +import org.koitharu.kotatsu.ui.reader.base.AbstractReader +import org.koitharu.kotatsu.ui.reader.standard.PagerReaderFragment import org.koitharu.kotatsu.ui.reader.thumbnails.OnPageSelectListener import org.koitharu.kotatsu.ui.reader.thumbnails.PagesThumbnailsSheet import org.koitharu.kotatsu.ui.reader.wetoon.WebtoonReaderFragment @@ -37,7 +39,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback, ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener { - private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance) + private val presenter by moxyPresenter(factory = ::ReaderPresenter) private val settings by inject() lateinit var state: ReaderState @@ -48,7 +50,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh private var isVolumeKeysSwitchEnabled = false private val reader - get() = supportFragmentManager.findFragmentById(R.id.container) as? BaseReaderFragment + get() = supportFragmentManager.findFragmentById(R.id.container) as? AbstractReader override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -81,24 +83,33 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh loadSettings() if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) { - presenter.loadChapter(state.manga, state.chapterId, ReaderAction.REPLACE) + presenter.init(state.manga) } } - override fun onInitReader(mode: ReaderMode) { - if (reader == null) { - setReader(mode) + override fun onInitReader(manga: Manga, mode: ReaderMode) { + val currentReader = reader + when (mode) { + ReaderMode.WEBTOON -> if (currentReader !is WebtoonReaderFragment) { + supportFragmentManager.commit { + replace(R.id.container, WebtoonReaderFragment.newInstance(state)) + } + } + else -> if (currentReader !is PagerReaderFragment) { + supportFragmentManager.commit { + replace(R.id.container, PagerReaderFragment.newInstance(state)) + } + } } - } - - override fun onPagesLoaded(chapterId: Long, pages: List, action: ReaderAction) = Unit - - override fun onPause() { - reader?.let { - state = state.copy(page = it.findCurrentPageIndex(state.chapterId)) - presenter.saveState(state) + toolbar_bottom.menu.findItem(R.id.action_reader_mode).setIcon( + when (mode) { + ReaderMode.WEBTOON -> R.drawable.ic_script + else -> R.drawable.ic_book_page + } + ) + appbar_top.postDelayed(1000) { + setUiIsVisible(false) } - super.onPause() } override fun onDestroy() { @@ -120,7 +131,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh R.id.action_reader_mode -> { ReaderConfigDialog.show( supportFragmentManager, when (reader) { - is StandardReaderFragment -> ReaderMode.STANDARD + is PagerReaderFragment -> ReaderMode.STANDARD is WebtoonReaderFragment -> ReaderMode.WEBTOON else -> ReaderMode.UNKNOWN } @@ -141,7 +152,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh } R.id.action_pages_thumbs -> { if (reader?.hasItems == true) { - val pages = reader?.getPages(state.chapterId) + val pages = reader?.getPages() if (pages != null) { PagesThumbnailsSheet.show( supportFragmentManager, pages, @@ -173,6 +184,11 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh else -> super.onOptionsItemSelected(item) } + override fun saveState(chapterId: Long, page: Int) { + state = state.copy(chapterId = chapterId, page = page) + ReaderPresenter.saveState(state) + } + override fun onLoadingStateChanged(isLoading: Boolean) { val hasPages = reader?.hasItems == true layout_loading.isVisible = isLoading && !hasPages @@ -268,29 +284,16 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh chapterId = chapter.id, page = 0 ) - presenter.loadChapter(state.manga, chapter.id, ReaderAction.REPLACE) + reader?.updateState(chapterId = chapter.id) } override fun onPageSelected(page: MangaPage) { - reader?.let { - val index = it.pages.indexOfFirst { x -> x.id == page.id } - if (index != -1) { - it.setCurrentPage(index, false) - } - } + reader?.updateState(pageId = page.id) } override fun onReaderModeChanged(mode: ReaderMode) { - reader?.let { - state = state.copy(page = it.findCurrentPageIndex(state.chapterId)) - } - presenter.saveState(state, mode) - setReader(mode) - setUiIsVisible(false) - } - - override fun onChaptersLoader(chapters: List) { - state = state.copy(manga = state.manga.copy(chapters = chapters)) + //TODO save state + presenter.setMode(state.manga, mode) } override fun onPageSaved(uri: Uri?) { @@ -305,14 +308,10 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh } override fun onPageChanged(chapter: MangaChapter, page: Int, total: Int) { - if (chapter.id != state.chapterId) { - title = chapter.name - state = state.copy(chapterId = chapter.id) - presenter.saveState(state) - state.manga.chapters?.run { - supportActionBar?.subtitle = - getString(R.string.chapter_d_of_d, chapter.number, size) - } + title = chapter.name + state.manga.chapters?.run { + supportActionBar?.subtitle = + getString(R.string.chapter_d_of_d, chapter.number, size) } } @@ -340,25 +339,6 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh } } - private fun setReader(mode: ReaderMode) { - val currentReader = reader - when (mode) { - ReaderMode.WEBTOON -> if (currentReader !is WebtoonReaderFragment) { - supportFragmentManager.commit { - replace(R.id.container, WebtoonReaderFragment()) - } - } - else -> if (currentReader !is StandardReaderFragment) { - supportFragmentManager.commit { - replace(R.id.container, StandardReaderFragment()) - } - } - } - toolbar_bottom.menu.findItem(R.id.action_reader_mode).setIcon(when(mode) { - ReaderMode.WEBTOON -> R.drawable.ic_script - else -> R.drawable.ic_book_page - }) - } private fun loadSettings() { settings.readerPageSwitch.let { 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 7ff2a1eeb..5ca6b167c 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 @@ -1,8 +1,11 @@ package org.koitharu.kotatsu.ui.reader 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 saveState(chapterId: Long, page: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt index 9fe42952e..51b65577a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderPresenter.kt @@ -8,6 +8,7 @@ import moxy.InjectViewState import moxy.presenterScope import okhttp3.OkHttpClient import okhttp3.Request +import org.koin.core.get import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaPage @@ -25,50 +26,31 @@ import org.koitharu.kotatsu.utils.ext.mimeType @InjectViewState class ReaderPresenter : BasePresenter() { - private var loaderJob: Job? = null - private var isInitialized = false - - fun loadChapter(manga: Manga, chapterId: Long, action: ReaderAction) { - loaderJob?.cancel() - loaderJob = presenterScope.launch { + fun init(manga: Manga) { + presenterScope.launch { viewState.onLoadingStateChanged(isLoading = true) try { - withContext(Dispatchers.IO) { + val mode = withContext(Dispatchers.IO) { val repo = MangaProviderFactory.create(manga.source) - val chapter = (manga.chapters ?: repo.getDetails(manga).chapters?.also { - withContext(Dispatchers.Main) { - viewState.onChaptersLoader(it) + val chapter = + (manga.chapters ?: throw RuntimeException("Chapters is null")).random() + val prefs = MangaDataRepository() + var mode = prefs.getReaderMode(manga.id) + if (mode == null) { + val pages = repo.getPages(chapter) + mode = MangaUtils.determineReaderMode(pages) + if (mode != null) { + prefs.savePreferences( + mangaId = manga.id, + mode = mode + ) } - })?.find { it.id == chapterId } - ?: throw RuntimeException("Chapter ${chapterId} not found") - val pages = repo.getPages(chapter) - if (!isInitialized) { - val prefs = MangaDataRepository() - var mode = prefs.getReaderMode(manga.id) - if (mode == null) { - mode = MangaUtils.determineReaderMode(pages) - if (mode != null) { - prefs.savePreferences( - mangaId = manga.id, - mode = mode - ) - } - } - withContext(Dispatchers.Main) { - viewState.onInitReader(mode ?: ReaderMode.UNKNOWN) - } - isInitialized = true - } - withContext(Dispatchers.Main) { - viewState.onPagesLoaded(chapterId, pages, action) } + mode ?: ReaderMode.UNKNOWN } - } catch (e: CancellationException){ - Log.w(null, "Loader job cancelled", e) - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } + viewState.onInitReader(manga, mode) + } catch (_: CancellationException) { + } catch (e: Throwable) { viewState.onError(e) } finally { viewState.onLoadingStateChanged(isLoading = false) @@ -76,20 +58,14 @@ class ReaderPresenter : BasePresenter() { } } - fun saveState(state: ReaderState, mode: ReaderMode? = null) { + fun setMode(manga: Manga, mode: ReaderMode) { presenterScope.launch(Dispatchers.IO) { - HistoryRepository().addOrUpdate( - manga = state.manga, - chapterId = state.chapterId, - page = state.page + MangaDataRepository().savePreferences( + mangaId = manga.id, + mode = mode ) - if (mode != null) { - MangaDataRepository().savePreferences( - mangaId = state.manga.id, - mode = mode - ) - } } + viewState.onInitReader(manga, mode) } fun savePage(resolver: ContentResolver, page: MangaPage) { @@ -101,7 +77,7 @@ class ReaderPresenter : BasePresenter() { .url(url) .get() .build() - val uri = getKoin().get().newCall(request).await().use { response -> + val uri = get().newCall(request).await().use { response -> val fileName = URLUtil.guessFileName(url, response.contentDisposition, response.mimeType) MediaStoreCompat.insertImage(resolver, fileName) { @@ -120,20 +96,17 @@ class ReaderPresenter : BasePresenter() { } } - override fun onDestroy() { - instance = null - super.onDestroy() - } - companion object { - private var instance: ReaderPresenter? = null - - fun getInstance(): ReaderPresenter = instance ?: synchronized(this) { - ReaderPresenter().also { - instance = it + fun saveState(state: ReaderState) { + GlobalScope.launch(Dispatchers.IO) { + HistoryRepository().addOrUpdate( + manga = state.manga, + chapterId = state.chapterId, + page = state.page + ) } } - } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt index 677461aa1..97b0f1da6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ReaderView.kt @@ -3,21 +3,14 @@ package org.koitharu.kotatsu.ui.reader import android.net.Uri import moxy.viewstate.strategy.alias.AddToEndSingle import moxy.viewstate.strategy.alias.OneExecution -import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.core.model.MangaPage +import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.ui.common.BaseMvpView interface ReaderView : BaseMvpView { @AddToEndSingle - fun onInitReader(mode: ReaderMode) - - @AddToEndSingle - fun onChaptersLoader(chapters: List) - - @AddToEndSingle - fun onPagesLoaded(chapterId: Long, pages: List, action: ReaderAction) + fun onInitReader(manga: Manga, mode: ReaderMode) @OneExecution fun onPageSaved(uri: Uri?) 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 new file mode 100644 index 000000000..a976ed3b0 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/AbstractReader.kt @@ -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 + 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(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(ARG_STATE) + ?: requireArguments().getParcelable(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) -> 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): 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 new file mode 100644 index 000000000..af3846323 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/BaseReaderAdapter.kt @@ -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(private val pages: GroupedList) : + RecyclerView.Adapter>() { + + init { + @Suppress("LeakingThis") + setHasStableIds(true) + } + + override fun onBindViewHolder(holder: BaseViewHolder, 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 { + return onCreateViewHolder(parent).also(this::onViewHolderCreated) + } + + protected abstract fun getExtra(item: MangaPage, position: Int): E + + protected open fun onViewHolderCreated(holder: BaseViewHolder) = Unit + + 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 new file mode 100644 index 000000000..3b11a1a48 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/GroupedList.kt @@ -0,0 +1,229 @@ +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.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? { + 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/OnBoundsScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/OnBoundsScrollListener.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/ui/reader/OnBoundsScrollListener.kt rename to app/src/main/java/org/koitharu/kotatsu/ui/reader/base/OnBoundsScrollListener.kt index df7c683f6..03054e3ef 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/OnBoundsScrollListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/base/OnBoundsScrollListener.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.ui.reader +package org.koitharu.kotatsu.ui.reader.base interface OnBoundsScrollListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt index e3227db94..7bb129036 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerPaginationListener.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.ui.reader.standard import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 -import org.koitharu.kotatsu.ui.reader.OnBoundsScrollListener +import org.koitharu.kotatsu.ui.reader.base.OnBoundsScrollListener class PagerPaginationListener( private val adapter: RecyclerView.Adapter<*>, @@ -10,16 +10,19 @@ class PagerPaginationListener( private val listener: OnBoundsScrollListener ) : ViewPager2.OnPageChangeCallback() { - private var lastItemCountStart = 0 - private var lastItemCountEnd = 0 + private var firstItemId: Long = 0 + private var lastItemId: Long = 0 override fun onPageSelected(position: Int) { val itemCount = adapter.itemCount - if (position <= offset && itemCount != lastItemCountStart) { - lastItemCountStart = itemCount + if (itemCount == 0) { + return + } + if (position <= offset && adapter.getItemId(0) != firstItemId) { + firstItemId = adapter.getItemId(0) listener.onScrolledToStart() - } else if (position >= itemCount - offset && itemCount != lastItemCountEnd) { - lastItemCountEnd = itemCount + } else if (position >= itemCount - offset && adapter.getItemId(itemCount - 1) != lastItemId) { + lastItemId = adapter.getItemId(itemCount - 1) listener.onScrolledToEnd() } } 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 new file mode 100644 index 000000000..62b121e0d --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/PagerReaderFragment.kt @@ -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): 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) + } + } +} \ No newline at end of file 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 88c3aecaa..b7158a61e 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 @@ -2,15 +2,16 @@ package org.koitharu.kotatsu.ui.reader.standard import android.view.ViewGroup import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter +import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter +import org.koitharu.kotatsu.ui.reader.base.GroupedList import org.koitharu.kotatsu.ui.reader.PageLoader -class PagesAdapter(private val loader: PageLoader) : BaseRecyclerAdapter() { +class PagesAdapter( + pages: GroupedList, + private val loader: PageLoader +) : BaseReaderAdapter(pages) { - override fun onCreateViewHolder(parent: ViewGroup) = - PageHolder(parent, loader) - - override fun onGetItemId(item: MangaPage) = item.id + override fun onCreateViewHolder(parent: ViewGroup) = PageHolder(parent, loader) override fun getExtra(item: MangaPage, position: Int) = Unit } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/StandardReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/StandardReaderFragment.kt deleted file mode 100644 index 25ee86857..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/standard/StandardReaderFragment.kt +++ /dev/null @@ -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, 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 - get() = adapter?.items.orEmpty() - - override fun setCurrentPage(index: Int, smooth: Boolean) { - pager.setCurrentItem(index, smooth) - } - - private companion object { - - const val SCROLL_OFFSET = 2 - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt index d6bd3b9b1..23d5ed3d2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/wetoon/ListPaginationListener.kt @@ -2,28 +2,31 @@ package org.koitharu.kotatsu.ui.reader.wetoon import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.ui.reader.OnBoundsScrollListener +import org.koitharu.kotatsu.ui.reader.base.OnBoundsScrollListener class ListPaginationListener( private val offset: Int, private val listener: OnBoundsScrollListener ) : RecyclerView.OnScrollListener() { - private var lastItemCountStart = 0 - private var lastItemCountEnd = 0 + private var firstItemId: Long = 0 + private var lastItemId: Long = 0 override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - val itemCount = recyclerView.adapter?.itemCount ?: return + val adapter = recyclerView.adapter ?: return val layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition() val lastVisiblePosition = layoutManager.findLastVisibleItemPosition() - if (firstVisiblePosition <= offset && itemCount != lastItemCountStart) { - lastItemCountStart = itemCount - listener.onScrolledToStart() - } else if (lastVisiblePosition >= itemCount - offset && itemCount != lastItemCountEnd) { - lastItemCountEnd = itemCount + val itemCount = adapter.itemCount + if (itemCount == 0) { + return + } + if (lastVisiblePosition >= itemCount - offset && adapter.getItemId(itemCount - 1) != lastItemId) { + lastItemId = adapter.getItemId(itemCount - 1) listener.onScrolledToEnd() + } else if (firstVisiblePosition <= offset && adapter.getItemId(0) != firstItemId) { + firstItemId = adapter.getItemId(0) + listener.onScrolledToStart() } } } \ No newline at end of file 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 738fe2031..368363dec 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 @@ -3,18 +3,18 @@ package org.koitharu.kotatsu.ui.reader.wetoon import android.view.Gravity import android.view.ViewGroup import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.ui.common.list.BaseViewHolder +import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter +import org.koitharu.kotatsu.ui.reader.base.GroupedList import org.koitharu.kotatsu.ui.reader.PageLoader -class WebtoonAdapter(private val loader: PageLoader) : BaseRecyclerAdapter() { +class WebtoonAdapter( + pages: GroupedList, + private val loader: PageLoader +) : BaseReaderAdapter(pages) { var pageGravity: Int = Gravity.TOP - override fun onCreateViewHolder(parent: ViewGroup) = - WebtoonHolder(parent, loader) - - override fun onGetItemId(item: MangaPage) = item.id + override fun onCreateViewHolder(parent: ViewGroup) = WebtoonHolder(parent, loader) override fun getExtra(item: MangaPage, position: Int) = pageGravity } \ No newline at end of file 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 8b2409fbf..3201eca8b 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 @@ -3,94 +3,66 @@ package org.koitharu.kotatsu.ui.reader.wetoon import android.os.Bundle import android.view.View import android.view.animation.AccelerateDecelerateInterpolator +import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.fragment_reader_webtoon.* -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.callOnScrollListeners +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.standard.PagerReaderFragment import org.koitharu.kotatsu.utils.ext.doOnCurrentItemChanged +import org.koitharu.kotatsu.utils.ext.findMiddleVisibleItemPosition import org.koitharu.kotatsu.utils.ext.firstItem +import org.koitharu.kotatsu.utils.ext.withArgs -class WebtoonReaderFragment : BaseReaderFragment(R.layout.fragment_reader_webtoon), - OnBoundsScrollListener { +class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) { - private val presenter by moxyPresenter(factory = ReaderPresenter.Companion::getInstance) - - private var adapter: WebtoonAdapter? = null - private lateinit var loader: PageLoader private val scrollInterpolator = AccelerateDecelerateInterpolator() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - loader = PageLoader() - } + protected var paginationListener: ListPaginationListener? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - adapter = WebtoonAdapter(loader) + paginationListener = ListPaginationListener(2, this) recyclerView.setHasFixedSize(true) recyclerView.adapter = adapter - recyclerView.addOnScrollListener(ListPaginationListener(2, this)) + recyclerView.addOnScrollListener(paginationListener!!) recyclerView.doOnCurrentItemChanged(::notifyPageChanged) } - override fun onPagesLoaded(chapterId: Long, pages: List, action: ReaderAction) { - super.onPagesLoaded(chapterId, pages, action) - when(action) { - ReaderAction.REPLACE -> { - adapter?.let { - it.replaceData(pages) - lastState?.let { state -> - if (chapterId == state.chapterId) { - recyclerView.firstItem = state.page - } - } - } - } - ReaderAction.PREPEND -> adapter?.prependData(pages) - ReaderAction.APPEND -> adapter?.appendData(pages) - } - recyclerView.callOnScrollListeners() + override fun onCreateAdapter(dataSet: GroupedList): BaseReaderAdapter<*> { + return WebtoonAdapter(dataSet, loader) } - override fun onScrolledToStart() { - val prevChapterId = getPrevChapterId() - if (prevChapterId != 0L) { - presenter.loadChapter(lastState?.manga ?: return, prevChapterId, ReaderAction.PREPEND) - } + override fun onDestroyView() { + paginationListener = null + super.onDestroyView() } - override fun onScrolledToEnd() { - val nextChapterId = getNextChapterId() - if (nextChapterId != 0L) { - presenter.loadChapter(lastState?.manga ?: return, nextChapterId, ReaderAction.APPEND) - } + override val itemsCount: Int + get() = adapter?.itemCount ?: 0 + + override fun getCurrentItem(): Int { + return (recyclerView.layoutManager as LinearLayoutManager).findMiddleVisibleItemPosition() } - override fun onDestroy() { - loader.dispose() - super.onDestroy() - } - - override val hasItems: Boolean - get() = adapter?.hasItems == true - - override val currentPageIndex: Int - get() = recyclerView.firstItem - - override val pages: List - get() = adapter?.items.orEmpty() - - override fun setCurrentPage(index: Int, smooth: Boolean) { - if (smooth) { - recyclerView.smoothScrollToPosition(index) + override fun setCurrentItem(position: Int, isSmooth: Boolean) { + if (isSmooth) { + recyclerView.smoothScrollToPosition(position) } else { - recyclerView.firstItem = index + recyclerView.firstItem = position } } override fun switchPageBy(delta: Int) { recyclerView.smoothScrollBy(0, (recyclerView.height * 0.9).toInt() * delta, scrollInterpolator) } + + companion object { + + fun newInstance(state: ReaderState) = WebtoonReaderFragment().withArgs(1) { + putParcelable(ARG_STATE, state) + } + } } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 414a16396..396a79d0a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Feb 26 19:30:06 EET 2020 +#Sun Mar 08 18:35:17 EET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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