Double reader integration

This commit is contained in:
Koitharu
2024-01-27 11:59:00 +02:00
parent f881cc439a
commit 72187e7da0
7 changed files with 98 additions and 36 deletions

View File

@@ -6,6 +6,7 @@ enum class ReaderMode(val id: Int) {
REVERSED(3),
VERTICAL(4),
WEBTOON(2),
DOUBLE(5),
;
companion object {

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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<FragmentReaderDoubleBinding>() {
class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>() {
@Inject
lateinit var networkState: NetworkState
@@ -44,7 +46,7 @@ class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>
super.onViewBindingCreated(binding, savedInstanceState)
with(binding.recyclerView) {
adapter = readerAdapter
addOnScrollListener(PageScrollListener())
addOnScrollListener(PageScrollListener(viewModel))
DoublePageSnapHelper().attachToRecyclerView(this)
}
}
@@ -54,28 +56,28 @@ class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>
super.onDestroyView()
}
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) =
coroutineScope {
val items = async {
requireAdapter().setItems(pages)
yield()
}
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()
}
} else {
items.await()
}
override suspend fun onPagesChanged(pages: List<ReaderPage>, 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
}
items.join()
if (position != -1) {
position = position or 1
requireViewBinding().recyclerView.firstVisibleItemPosition = position
viewModel.onCurrentPageChanged(position, position + 1)
} else {
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<FragmentReaderDoubleBinding>
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<FragmentReaderDoubleBinding>
)
}
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<PageHolder> {
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)
}
}
}

View File

@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4,4C2.89,4 2,4.89 2,6L2,18C2,19.11 2.89,20 4,20L9,20L9,18L8,18L6,18L4,18L4,6L6,6L8,6L9,6L9,4L4,4zM15,4L15,6L16,6L18,6L20,6L20,18L18,18L16,18L15,18L15,20L20,20C21.1,20 22,19.11 22,18L22,6C22,4.89 21.1,4 20,4L15,4z"/>
<path
android:fillColor="#FF000000"
android:pathData="m11,22.43v-3h2v3z"/>
<path
android:fillColor="#FF000000"
android:pathData="m13,1.57v3h-2v-3h2"/>
<path
android:fillColor="#FF000000"
android:pathData="m11,17.43v-3h2v3h-2"/>
<path
android:fillColor="#FF000000"
android:pathData="m11,9.57v-3h2v3h-2"/>
<path
android:fillColor="#FF000000"
android:pathData="m14.5,15v-2h-7v-2h7V9l4,3 -4,3"/>
</vector>

View File

@@ -119,6 +119,15 @@
android:text="@string/webtoon"
app:icon="@drawable/ic_script" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_double"
style="@style/Widget.Kotatsu.ToggleButton.Vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/two_pages"
app:icon="@drawable/ic_pager_double_ltr" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<TextView

View File

@@ -567,4 +567,5 @@
<string name="incognito_mode_hint">Your reading progress will not be saved</string>
<string name="vertical">Vertical</string>
<string name="last_read">Last read</string>
<string name="two_pages">Two pages</string>
</resources>