diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt index 7d9f5fd2f..2b727c6c5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt @@ -6,6 +6,7 @@ enum class ReaderMode(val id: Int) { REVERSED(3), VERTICAL(4), WEBTOON(2), + DOUBLE(5), ; companion object { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderManager.kt index 5ac73ef59..0d6b125fb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderManager.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderManager.kt @@ -6,7 +6,7 @@ import androidx.fragment.app.commit import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment -import org.koitharu.kotatsu.reader.ui.pager.doublepage.DoublePageReaderFragment +import org.koitharu.kotatsu.reader.ui.pager.doublepage.DoubleReaderFragment import org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment import org.koitharu.kotatsu.reader.ui.pager.vertical.VerticalReaderFragment @@ -23,7 +23,7 @@ class ReaderManager( init { val isTablet = container.resources.getBoolean(R.bool.is_tablet) modeMap[ReaderMode.STANDARD] = if (isTablet) { - DoublePageReaderFragment::class.java + DoubleReaderFragment::class.java } else { PagerReaderFragment::class.java } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt index 038deb6bc..2cdbb7e29 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt @@ -80,6 +80,7 @@ class ReaderConfigSheet : binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL + binding.buttonDouble.isChecked = mode == ReaderMode.DOUBLE binding.checkableGroup.addOnButtonCheckedListener(this) binding.buttonSavePage.setOnClickListener(this) @@ -155,6 +156,7 @@ class ReaderConfigSheet : R.id.button_webtoon -> ReaderMode.WEBTOON R.id.button_reversed -> ReaderMode.REVERSED R.id.button_vertical -> ReaderMode.VERTICAL + R.id.button_double -> ReaderMode.DOUBLE else -> return } if (newMode == mode) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoubleReaderFragment.kt similarity index 58% rename from app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageReaderFragment.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoubleReaderFragment.kt index 6afb4d958..05b0e210e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoubleReaderFragment.kt @@ -7,8 +7,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch import kotlinx.coroutines.yield import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.os.NetworkState @@ -17,14 +17,16 @@ import org.koitharu.kotatsu.databinding.FragmentReaderDoubleBinding import org.koitharu.kotatsu.parsers.util.toIntUp import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.reader.ui.ReaderViewModel import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment import org.koitharu.kotatsu.reader.ui.pager.ReaderPage +import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder import javax.inject.Inject import kotlin.math.absoluteValue @AndroidEntryPoint -class DoublePageReaderFragment : BaseReaderFragment() { +class DoubleReaderFragment : BaseReaderFragment() { @Inject lateinit var networkState: NetworkState @@ -44,7 +46,7 @@ class DoublePageReaderFragment : BaseReaderFragment super.onViewBindingCreated(binding, savedInstanceState) with(binding.recyclerView) { adapter = readerAdapter - addOnScrollListener(PageScrollListener()) + addOnScrollListener(PageScrollListener(viewModel)) DoublePageSnapHelper().attachToRecyclerView(this) } } @@ -54,28 +56,28 @@ class DoublePageReaderFragment : BaseReaderFragment super.onDestroyView() } - override suspend fun onPagesChanged(pages: List, pendingState: ReaderState?) = - coroutineScope { - val items = async { - requireAdapter().setItems(pages) - yield() + override suspend fun onPagesChanged(pages: List, pendingState: ReaderState?) = coroutineScope { + val items = launch { + requireAdapter().setItems(pages) + yield() + } + if (pendingState != null) { + var position = pages.indexOfFirst { + it.chapterId == pendingState.chapterId && it.index == pendingState.page } - if (pendingState != null) { - val position = pages.indexOfFirst { - it.chapterId == pendingState.chapterId && it.index == pendingState.page - } - items.await() - if (position != -1) { - requireViewBinding().recyclerView.firstVisibleItemPosition = position or 1 - notifyPageChanged(position) - } else { - Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT) - .show() - } + items.join() + if (position != -1) { + position = position or 1 + requireViewBinding().recyclerView.firstVisibleItemPosition = position + viewModel.onCurrentPageChanged(position, position + 1) } else { - items.await() + Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT) + .show() } + } else { + items.join() } + } override fun onCreateAdapter() = DoublePagesAdapter( lifecycleOwner = viewLifecycleOwner, @@ -85,6 +87,16 @@ class DoublePageReaderFragment : BaseReaderFragment exceptionResolver = exceptionResolver, ) + override fun onZoomIn() { + (viewBinding ?: return).recyclerView.pageHolders() + .forEach { it.onZoomIn() } + } + + override fun onZoomOut() { + (viewBinding ?: return).recyclerView.pageHolders() + .forEach { it.onZoomOut() } + } + override fun switchPageBy(delta: Int) { switchPageTo((requireViewBinding().recyclerView.currentItem() + delta) or 1, delta.absoluteValue > 1) } @@ -103,25 +115,38 @@ class DoublePageReaderFragment : BaseReaderFragment ) } - private fun notifyPageChanged(page: Int) { - viewModel.onCurrentPageChanged(page) - } - private fun RecyclerView.currentItem(): Int { val lm = layoutManager as LinearLayoutManager return ((lm.findFirstVisibleItemPosition() + lm.findLastVisibleItemPosition()) / 2f).toIntUp() } - private inner class PageScrollListener : RecyclerView.OnScrollListener() { + private fun RecyclerView.pageHolders(): Sequence { + val lm = layoutManager as? LinearLayoutManager ?: return emptySequence() + return (lm.findFirstVisibleItemPosition()..lm.findLastVisibleItemPosition()).asSequence() + .mapNotNull { findViewHolderForAdapterPosition(it) as? PageHolder } + } - private var lastPage = RecyclerView.NO_POSITION + private class PageScrollListener( + private val viewModel: ReaderViewModel, + ) : RecyclerView.OnScrollListener() { + + private var firstPos = RecyclerView.NO_POSITION + private var lastPos = RecyclerView.NO_POSITION override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) - val page = recyclerView.currentItem() - if (page != lastPage) { - lastPage = page - notifyPageChanged(page) + val lm = recyclerView.layoutManager as? LinearLayoutManager + if (lm == null) { + firstPos = RecyclerView.NO_POSITION + lastPos = RecyclerView.NO_POSITION + return + } + val newFirstPos = lm.findFirstVisibleItemPosition() + val newLastPos = lm.findLastVisibleItemPosition() + if (newFirstPos != firstPos || newLastPos != lastPos) { + firstPos = newFirstPos + lastPos = newLastPos + viewModel.onCurrentPageChanged(newFirstPos, newLastPos) } } } diff --git a/app/src/main/res/drawable/ic_pager_double_ltr.xml b/app/src/main/res/drawable/ic_pager_double_ltr.xml new file mode 100644 index 000000000..33f6e10ae --- /dev/null +++ b/app/src/main/res/drawable/ic_pager_double_ltr.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/layout/sheet_reader_config.xml b/app/src/main/res/layout/sheet_reader_config.xml index 95473de7c..37165765f 100644 --- a/app/src/main/res/layout/sheet_reader_config.xml +++ b/app/src/main/res/layout/sheet_reader_config.xml @@ -119,6 +119,15 @@ android:text="@string/webtoon" app:icon="@drawable/ic_script" /> + + Your reading progress will not be saved Vertical Last read + Two pages