Update reader interface

This commit is contained in:
Koitharu
2024-12-14 09:26:01 +02:00
parent 146ba95af6
commit 25ae23963e
16 changed files with 240 additions and 40 deletions

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@@ -362,8 +362,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isReaderBarEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_BAR, true)
val isReaderSliderEnabled: Boolean
var isReaderSliderEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_SLIDER, true)
set(value) = prefs.edit { putBoolean(KEY_READER_SLIDER, value) }
val isReaderKeepScreenOn: Boolean
get() = prefs.getBoolean(KEY_READER_SCREEN_ON, true)

View File

@@ -20,6 +20,7 @@ import androidx.lifecycle.LifecycleOwner
import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialSplitButton
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.util.ext.findActivity
@@ -30,7 +31,7 @@ import org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment
import org.koitharu.kotatsu.reader.ui.ReaderActivity
class ReadButtonDelegate(
splitButton: MaterialSplitButton,
private val splitButton: MaterialSplitButton,
private val viewModel: DetailsViewModel,
) : View.OnClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
@@ -73,7 +74,10 @@ class ReadButtonDelegate(
fun attach(lifecycleOwner: LifecycleOwner) {
buttonRead.setOnClickListener(this)
buttonMenu.setOnClickListener(this)
viewModel.historyInfo.observe(lifecycleOwner, this::onHistoryChanged)
combine(viewModel.isLoading, viewModel.historyInfo, ::Pair)
.observe(lifecycleOwner) { (isLoading, historyInfo) ->
onHistoryChanged(isLoading, historyInfo)
}
}
private fun showMenu() {
@@ -97,16 +101,15 @@ class ReadButtonDelegate(
}
private fun openReader(isIncognitoMode: Boolean) {
val detailsViewModel = viewModel as? DetailsViewModel ?: return
val manga = viewModel.manga.value ?: return
if (detailsViewModel.historyInfo.value.isChapterMissing) {
if (viewModel.historyInfo.value.isChapterMissing) {
Snackbar.make(buttonRead, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
.show() // TODO
} else {
context.startActivity(
ReaderActivity.IntentBuilder(context)
.manga(manga)
.branch(detailsViewModel.selectedBranchValue)
.branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode)
.build(),
)
@@ -116,9 +119,16 @@ class ReadButtonDelegate(
}
}
private fun onHistoryChanged(info: HistoryInfo) {
buttonRead.setText(if (info.canContinue) R.string._continue else R.string.read)
buttonRead.isEnabled = info.isValid
private fun onHistoryChanged(isLoading: Boolean, info: HistoryInfo) {
buttonRead.setText(
when {
isLoading -> R.string.loading_
info.isIncognitoMode -> R.string.incognito
info.canContinue -> R.string._continue
else -> R.string.read
},
)
splitButton.isEnabled = !isLoading && info.isValid
}
private fun Menu.populateBranchList() {

View File

@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.reader.ui
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.transition.Fade
import android.transition.Slide
@@ -42,7 +41,6 @@ import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
import org.koitharu.kotatsu.core.util.IdlingDetector
import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.hasGlobalPoint
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.isRtl
@@ -127,6 +125,8 @@ class ReaderActivity :
ReaderSliderListener(viewModel, this).attachToSlider(viewBinding.slider)
insetsDelegate.interceptingWindowInsetsListener = this
idlingDetector.bindToLifecycle(this)
viewBinding.buttonPrev.setOnClickListener(controlDelegate)
viewBinding.buttonNext.setOnClickListener(controlDelegate)
viewModel.onError.observeEvent(
this,
@@ -150,10 +150,12 @@ class ReaderActivity :
viewModel.content.observe(this) {
onLoadingStateChanged(viewModel.isLoading.value)
}
viewModel.isSliderVisible.observe(this, this::onSliderVisibilityChanged)
viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn)
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this))
viewModel.isPagesSheetEnabled.observe(this, MenuInvalidator(viewBinding.toolbarBottom))
val bottomMenuInvalidator = MenuInvalidator(viewBinding.toolbarBottom)
viewModel.isBookmarkAdded.observe(this, bottomMenuInvalidator)
viewModel.isPagesSheetEnabled.observe(this, bottomMenuInvalidator)
viewModel.onShowToast.observeEvent(this) { msgId ->
Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT)
.setAnchorView(viewBinding.appbarBottom)
@@ -243,7 +245,7 @@ class ReaderActivity :
false
} else {
val touchables = window.peekDecorView()?.touchables
touchables?.none { it.hasGlobalPoint(rawX, rawY) } ?: true
touchables?.none { it.hasGlobalPoint(rawX, rawY) } != false
}
}
@@ -307,6 +309,9 @@ class ReaderActivity :
.addTransition(Fade().addTarget(viewBinding.infoBar))
viewBinding.appbarBottom?.let { bottomBar ->
transition.addTransition(Slide(Gravity.BOTTOM).addTarget(bottomBar))
transition.addTransition(Slide(Gravity.BOTTOM).addTarget(viewBinding.floatingToolbar))
} ?: run {
transition.addTransition(Slide(Gravity.END).addTarget(viewBinding.floatingToolbar))
}
TransitionManager.beginDelayedTransition(viewBinding.root, transition)
}
@@ -315,10 +320,15 @@ class ReaderActivity :
viewBinding.appbarBottom?.isVisible = isUiVisible
viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value)
viewBinding.infoBar.isTimeVisible = isFullscreen
viewBinding.floatingToolbar.isVisible = isUiVisible && viewModel.isSliderVisible.value
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
}
}
private fun onSliderVisibilityChanged(isSliderVisible: Boolean) {
viewBinding.floatingToolbar.isVisible = isSliderVisible && viewBinding.appbarTop.isVisible
}
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
gestureInsets = insets.getInsets(WindowInsetsCompat.Type.systemGestures())
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
@@ -357,7 +367,7 @@ class ReaderActivity :
}
override fun scrollBy(delta: Int, smooth: Boolean): Boolean {
return readerManager.currentReader?.scrollBy(delta, smooth) ?: false
return readerManager.currentReader?.scrollBy(delta, smooth) == true
}
override fun toggleUiVisibility() {

View File

@@ -28,6 +28,15 @@ class ReaderBottomMenuProvider(
setIcon(if (viewModel.isPagesSheetEnabled.value) R.drawable.ic_grid else R.drawable.ic_list)
}
}
menu.findItem(R.id.action_bookmark)?.let { bookmarkItem ->
val hasPages = viewModel.content.value.pages.isNotEmpty()
bookmarkItem.isEnabled = hasPages
if (hasPages) {
val hasBookmark = viewModel.isBookmarkAdded.value
bookmarkItem.setTitle(if (hasBookmark) R.string.bookmark_remove else R.string.bookmark_add)
bookmarkItem.setIcon(if (hasBookmark) R.drawable.ic_bookmark_added else R.drawable.ic_bookmark)
}
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
@@ -44,6 +53,20 @@ class ReaderBottomMenuProvider(
true
}
R.id.action_slider -> {
viewModel.setSliderVisibility(!viewModel.isSliderVisible.value)
true
}
R.id.action_bookmark -> {
if (viewModel.isBookmarkAdded.value) {
viewModel.removeBookmark()
} else {
viewModel.addBookmark()
}
true
}
else -> false
}
}

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.reader.ui
import android.content.res.Resources
import android.view.KeyEvent
import android.view.View
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode
@@ -14,10 +15,17 @@ class ReaderControlDelegate(
private val settings: AppSettings,
private val tapGridSettings: TapGridSettings,
private val listener: OnInteractionListener,
) {
) : View.OnClickListener {
private var minScrollDelta = resources.getDimensionPixelSize(R.dimen.reader_scroll_delta_min)
override fun onClick(v: View) {
when (v.id) {
R.id.button_prev -> listener.switchChapterBy(-1)
R.id.button_next -> listener.switchChapterBy(1)
}
}
fun onGridTouch(area: TapGridArea): Boolean {
val action = tapGridSettings.getTapAction(
area = area,
@@ -63,7 +71,7 @@ class ReaderControlDelegate(
KeyEvent.KEYCODE_SPACE,
KeyEvent.KEYCODE_PAGE_DOWN,
-> {
-> {
listener.switchPageBy(1)
true
}
@@ -74,7 +82,7 @@ class ReaderControlDelegate(
}
KeyEvent.KEYCODE_PAGE_UP,
-> {
-> {
listener.switchPageBy(-1)
true
}

View File

@@ -131,6 +131,12 @@ class ReaderViewModel @Inject constructor(
valueProducer = { readerAnimation },
)
val isSliderVisible = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_READER_SLIDER,
valueProducer = { isReaderSliderEnabled },
)
val isInfoBarEnabled = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_READER_BAR,
@@ -213,6 +219,10 @@ class ReaderViewModel @Inject constructor(
}
}
fun setSliderVisibility(visible: Boolean) {
settings.isReaderSliderEnabled = visible
}
fun switchMode(newMode: ReaderMode) {
launchJob {
val manga = checkNotNull(getMangaOrNull())

View File

@@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M6,18L14.5,12L6,6M8,9.86L11.03,12L8,14.14M16,6H18V18H16" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M6,6H8V18H6M9.5,12L18,18V6M16,14.14L12.97,12L16,9.86V14.14Z" />
</vector>

View File

@@ -59,23 +59,64 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:menu="@menu/opt_reader_bottom">
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stepSize="1"
android:valueFrom="0"
app:labelBehavior="floating"
app:tickVisible="false" />
</com.google.android.material.appbar.MaterialToolbar>
tools:menu="@menu/opt_reader_bottom" />
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingtoolbar.FloatingToolbarLayout
android:id="@+id/floating_toolbar"
style="@style/Widget.Material3.FloatingToolbar.Vertical.Vibrant"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical">
<RelativeLayout
android:id="@+id/floating_toolbar_child"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="wrap_content"
android:layout_height="240dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:orientation="vertical"
android:stepSize="1.0"
android:valueFrom="0"
app:labelBehavior="floating"
tools:value="6"
tools:valueTo="20" />
<ImageButton
android:id="@+id/button_prev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/slider"
android:layout_centerHorizontal="true"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/prev_chapter"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_prev" />
<ImageButton
android:id="@+id/button_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button_prev"
android:layout_centerHorizontal="true"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/next_chapter"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_next" />
</RelativeLayout>
</com.google.android.material.floatingtoolbar.FloatingToolbarLayout>
<org.koitharu.kotatsu.reader.ui.ReaderToastView
android:id="@+id/toastView"
android:layout_width="wrap_content"

View File

@@ -66,22 +66,61 @@
android:id="@+id/toolbar_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:menu="@menu/opt_reader_bottom">
tools:menu="@menu/opt_reader_bottom" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.floatingtoolbar.FloatingToolbarLayout
android:id="@+id/floating_toolbar"
style="@style/Widget.Material3.FloatingToolbar.Horizontal.Vibrant"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_dodgeInsetEdges="bottom">
<RelativeLayout
android:id="@+id/floating_toolbar_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<ImageButton
android:id="@+id/button_prev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/prev_chapter"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_prev" />
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/button_next"
android:layout_toEndOf="@id/button_prev"
android:stepSize="1.0"
android:valueFrom="0"
app:labelBehavior="floating"
app:tickVisible="true"
tools:value="6"
tools:valueTo="20" />
</com.google.android.material.appbar.MaterialToolbar>
<ImageButton
android:id="@+id/button_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/next_chapter"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_next" />
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</com.google.android.material.floatingtoolbar.FloatingToolbarLayout>
<org.koitharu.kotatsu.reader.ui.ReaderToastView
android:id="@+id/toastView"

View File

@@ -5,6 +5,19 @@
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction">
<item
android:id="@+id/action_slider"
android:icon="@drawable/ic_move_horizontal"
android:title="@string/show_slider"
app:showAsAction="always" />
<item
android:id="@+id/action_bookmark"
android:enabled="false"
android:icon="@drawable/ic_bookmark"
android:title="@string/bookmark_add"
app:showAsAction="always" />
<item
android:id="@+id/action_pages_thumbs"
android:icon="@drawable/ic_grid"

View File

@@ -774,4 +774,7 @@
<string name="source">Source</string>
<string name="translation">Translation</string>
<string name="chapters_time_pattern" translatable="false">%1$s (%2$s)</string>
<string name="show_slider">Show slider</string>
<!-- Button label, should be as short as possible -->
<string name="incognito">Incognito</string>
</resources>

View File

@@ -144,6 +144,12 @@
<item name="shapeAppearance">?shapeAppearanceCornerMedium</item>
</style>
<style name="Widget.Kotatsu.Slider.Wide" parent="Widget.Material3.Slider">
<item name="thumbHeight">48dp</item>
<item name="trackCornerSize">12dp</item>
<item name="trackHeight">40dp</item>
</style>
<style name="Widget.Kotatsu.RecyclerView" parent="">
<item name="android:scrollbarStyle">outsideOverlay</item>
</style>

View File

@@ -121,11 +121,6 @@
android:title="@string/reader_info_bar"
app:allowDividerAbove="true" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reader_slider"
android:title="@string/reader_slider" />
<ListPreference
android:entries="@array/reader_backgrounds"
android:key="reader_background"