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), REVERSED(3),
VERTICAL(4), VERTICAL(4),
WEBTOON(2), WEBTOON(2),
DOUBLE(5),
; ;
companion object { companion object {

View File

@@ -6,7 +6,7 @@ import androidx.fragment.app.commit
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment 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.reversed.ReversedReaderFragment
import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
import org.koitharu.kotatsu.reader.ui.pager.vertical.VerticalReaderFragment import org.koitharu.kotatsu.reader.ui.pager.vertical.VerticalReaderFragment
@@ -23,7 +23,7 @@ class ReaderManager(
init { init {
val isTablet = container.resources.getBoolean(R.bool.is_tablet) val isTablet = container.resources.getBoolean(R.bool.is_tablet)
modeMap[ReaderMode.STANDARD] = if (isTablet) { modeMap[ReaderMode.STANDARD] = if (isTablet) {
DoublePageReaderFragment::class.java DoubleReaderFragment::class.java
} else { } else {
PagerReaderFragment::class.java PagerReaderFragment::class.java
} }

View File

@@ -80,6 +80,7 @@ class ReaderConfigSheet :
binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED
binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON
binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL
binding.buttonDouble.isChecked = mode == ReaderMode.DOUBLE
binding.checkableGroup.addOnButtonCheckedListener(this) binding.checkableGroup.addOnButtonCheckedListener(this)
binding.buttonSavePage.setOnClickListener(this) binding.buttonSavePage.setOnClickListener(this)
@@ -155,6 +156,7 @@ class ReaderConfigSheet :
R.id.button_webtoon -> ReaderMode.WEBTOON R.id.button_webtoon -> ReaderMode.WEBTOON
R.id.button_reversed -> ReaderMode.REVERSED R.id.button_reversed -> ReaderMode.REVERSED
R.id.button_vertical -> ReaderMode.VERTICAL R.id.button_vertical -> ReaderMode.VERTICAL
R.id.button_double -> ReaderMode.DOUBLE
else -> return else -> return
} }
if (newMode == mode) { if (newMode == mode) {

View File

@@ -7,8 +7,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkState 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.parsers.util.toIntUp
import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderState 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.BaseReaderAdapter
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@AndroidEntryPoint @AndroidEntryPoint
class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>() { class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>() {
@Inject @Inject
lateinit var networkState: NetworkState lateinit var networkState: NetworkState
@@ -44,7 +46,7 @@ class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
with(binding.recyclerView) { with(binding.recyclerView) {
adapter = readerAdapter adapter = readerAdapter
addOnScrollListener(PageScrollListener()) addOnScrollListener(PageScrollListener(viewModel))
DoublePageSnapHelper().attachToRecyclerView(this) DoublePageSnapHelper().attachToRecyclerView(this)
} }
} }
@@ -54,28 +56,28 @@ class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>
super.onDestroyView() super.onDestroyView()
} }
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
coroutineScope { val items = launch {
val items = async { requireAdapter().setItems(pages)
requireAdapter().setItems(pages) yield()
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()
}
} }
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( override fun onCreateAdapter() = DoublePagesAdapter(
lifecycleOwner = viewLifecycleOwner, lifecycleOwner = viewLifecycleOwner,
@@ -85,6 +87,16 @@ class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>
exceptionResolver = exceptionResolver, 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) { override fun switchPageBy(delta: Int) {
switchPageTo((requireViewBinding().recyclerView.currentItem() + delta) or 1, delta.absoluteValue > 1) 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 { private fun RecyclerView.currentItem(): Int {
val lm = layoutManager as LinearLayoutManager val lm = layoutManager as LinearLayoutManager
return ((lm.findFirstVisibleItemPosition() + lm.findLastVisibleItemPosition()) / 2f).toIntUp() 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) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val page = recyclerView.currentItem() val lm = recyclerView.layoutManager as? LinearLayoutManager
if (page != lastPage) { if (lm == null) {
lastPage = page firstPos = RecyclerView.NO_POSITION
notifyPageChanged(page) 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" android:text="@string/webtoon"
app:icon="@drawable/ic_script" /> 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> </com.google.android.material.button.MaterialButtonToggleGroup>
<TextView <TextView

View File

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