From 6e1cd05fa81ba965b4c47e63239d0d8dc65c4b2c Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 18 Sep 2023 13:25:53 +0300 Subject: [PATCH] Zoom control buttons in reader --- .../kotatsu/core/prefs/AppSettings.kt | 10 +++-- .../kotatsu/core/ui/widgets/ZoomControl.kt | 38 ++++++++++++++++ .../kotatsu/reader/ui/ReaderViewModel.kt | 6 +++ .../reader/ui/config/ReaderSettings.kt | 4 ++ .../kotatsu/reader/ui/pager/BasePageHolder.kt | 2 +- .../reader/ui/pager/standard/PageHolder.kt | 6 +++ .../ui/pager/standard/SsivZoomListener.kt | 28 ++++++++++++ .../ui/pager/webtoon/WebtoonReaderFragment.kt | 5 +++ .../ui/pager/webtoon/WebtoonScalingFrame.kt | 17 ++++++-- app/src/main/res/drawable/ic_zoom_in.xml | 12 ++++++ app/src/main/res/drawable/ic_zoom_out.xml | 12 ++++++ .../res/layout/fragment_reader_webtoon.xml | 16 ++++++- app/src/main/res/layout/item_page.xml | 10 +++++ app/src/main/res/layout/view_zoom.xml | 43 +++++++++++++++++++ app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/xml/pref_reader.xml | 6 +++ 16 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ZoomControl.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/SsivZoomListener.kt create mode 100644 app/src/main/res/drawable/ic_zoom_in.xml create mode 100644 app/src/main/res/drawable/ic_zoom_out.xml create mode 100644 app/src/main/res/layout/view_zoom.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 06dd791d8..9a7f56b55 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -90,6 +90,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { val readerPageSwitch: Set get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS) + val isReaderZoomButtonsEnabled: Boolean + get() = prefs.getBoolean(KEY_READER_ZOOM_BUTTONS, false) + val isReaderTapsAdaptive: Boolean get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false) @@ -161,7 +164,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { get() = prefs.getString(KEY_APP_PASSWORD, null) set(value) = prefs.edit { if (value != null) putString(KEY_APP_PASSWORD, value) else remove( - KEY_APP_PASSWORD + KEY_APP_PASSWORD, ) } @@ -314,7 +317,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { set(@FloatRange(from = 0.0, to = 1.0) value) = prefs.edit { putFloat( KEY_READER_AUTOSCROLL_SPEED, - value + value, ) } @@ -325,7 +328,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { } val policy = NetworkPolicy.from( prefs.getString(KEY_PAGES_PRELOAD, null), - NetworkPolicy.NON_METERED + NetworkPolicy.NON_METERED, ) return policy.isNetworkAllowed(connectivityManager) } @@ -409,6 +412,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_REMOTE_SOURCES = "remote_sources" const val KEY_LOCAL_STORAGE = "local_storage" const val KEY_READER_SWITCHERS = "reader_switchers" + const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons" const val KEY_TRACKER_ENABLED = "tracker_enabled" const val KEY_TRACKER_WIFI_ONLY = "tracker_wifi" const val KEY_TRACK_SOURCES = "track_sources" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ZoomControl.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ZoomControl.kt new file mode 100644 index 000000000..b6f8d222a --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ZoomControl.kt @@ -0,0 +1,38 @@ +package org.koitharu.kotatsu.core.ui.widgets + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.databinding.ViewZoomBinding + +class ZoomControl @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, +) : LinearLayout(context, attrs), View.OnClickListener { + + private val binding = ViewZoomBinding.inflate(LayoutInflater.from(context), this) + + var listener: ZoomControlListener? = null + + init { + binding.buttonZoomIn.setOnClickListener(this) + binding.buttonZoomOut.setOnClickListener(this) + } + + override fun onClick(v: View) { + when (v.id) { + R.id.button_zoom_in -> listener?.onZoomIn() + R.id.button_zoom_out -> listener?.onZoomOut() + } + } + + interface ZoomControlListener { + + fun onZoomIn() + + fun onZoomOut() + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index fb6b72261..2158914a1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -120,6 +120,12 @@ class ReaderViewModel @Inject constructor( valueProducer = { isWebtoonZoomEnable }, ) + val isZoomControlEnabled = settings.observeAsStateFlow( + scope = viewModelScope + Dispatchers.Default, + key = AppSettings.KEY_READER_ZOOM_BUTTONS, + valueProducer = { isReaderZoomButtonsEnabled }, + ) + val readerSettings = ReaderSettings( parentScope = viewModelScope, settings = settings, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt index 4c404680d..354c7e528 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt @@ -32,6 +32,9 @@ class ReaderSettings( val isPagesNumbersEnabled: Boolean get() = settings.isPagesNumbersEnabled + val isZoomControlsEnabled: Boolean + get() = settings.isReaderZoomButtonsEnabled + fun applyBackground(view: View) { val bg = settings.readerBackground view.background = bg.resolve(view.context) @@ -74,6 +77,7 @@ class ReaderSettings( key == AppSettings.KEY_ZOOM_MODE || key == AppSettings.KEY_PAGES_NUMBERS || key == AppSettings.KEY_WEBTOON_ZOOM || + key == AppSettings.KEY_READER_ZOOM_BUTTONS || key == AppSettings.KEY_READER_BACKGROUND ) { notifyChanged() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt index b4b94ffb7..b41dac8ed 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt @@ -13,7 +13,7 @@ import org.koitharu.kotatsu.reader.ui.config.ReaderSettings abstract class BasePageHolder( protected val binding: B, loader: PageLoader, - private val settings: ReaderSettings, + protected val settings: ReaderSettings, networkState: NetworkState, exceptionResolver: ExceptionResolver, ) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt index 9c568fad6..b7ea73e76 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt @@ -40,6 +40,12 @@ open class PageHolder( @Suppress("LeakingThis") bindingInfo.buttonErrorDetails.setOnClickListener(this) binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled + binding.zoomControl.listener = SsivZoomListener(binding.ssiv) + } + + override fun onConfigChanged() { + super.onConfigChanged() + binding.zoomControl.isVisible = settings.isZoomControlsEnabled } @SuppressLint("SetTextI18n") diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/SsivZoomListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/SsivZoomListener.kt new file mode 100644 index 000000000..871442d31 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/SsivZoomListener.kt @@ -0,0 +1,28 @@ +package org.koitharu.kotatsu.reader.ui.pager.standard + +import android.view.animation.DecelerateInterpolator +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView +import org.koitharu.kotatsu.core.ui.widgets.ZoomControl + +class SsivZoomListener( + private val ssiv: SubsamplingScaleImageView, +) : ZoomControl.ZoomControlListener { + + override fun onZoomIn() { + scaleBy(1.2f) + } + + override fun onZoomOut() { + scaleBy(0.8f) + } + + private fun scaleBy(factor: Float) { + val center = ssiv.getCenter() ?: return + val newScale = ssiv.scale * factor + ssiv.animateScaleAndCenter(newScale, center)?.apply { + withDuration(ssiv.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()) + withInterpolator(DecelerateInterpolator()) + start() + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt index 1be25bda9..a83e9c25c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import android.view.animation.DecelerateInterpolator +import androidx.core.view.isVisible import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.async @@ -45,10 +46,14 @@ class WebtoonReaderFragment : BaseReaderFragment() adapter = readerAdapter addOnPageScrollListener(PageScrollListener()) } + binding.zoomControl.listener = binding.frame viewModel.isWebtoonZoomEnabled.observe(viewLifecycleOwner) { binding.frame.isZoomEnable = it } + viewModel.isZoomControlEnabled.observe(viewLifecycleOwner) { + binding.zoomControl.isVisible = it + } } override fun onDestroyView() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt index 648be1a0c..d5ac88df9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt @@ -18,6 +18,7 @@ import android.widget.FrameLayout import android.widget.OverScroller import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewConfigurationCompat +import org.koitharu.kotatsu.core.ui.widgets.ZoomControl import org.koitharu.kotatsu.core.util.ext.getAnimationDuration private const val MAX_SCALE = 2.5f @@ -27,7 +28,9 @@ class WebtoonScalingFrame @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyles: Int = 0, -) : FrameLayout(context, attrs, defStyles), ScaleGestureDetector.OnScaleGestureListener { +) : FrameLayout(context, attrs, defStyles), + ScaleGestureDetector.OnScaleGestureListener, + ZoomControl.ZoomControlListener { private val targetChild by lazy(LazyThreadSafetyMode.NONE) { getChildAt(0) as WebtoonRecyclerView } @@ -110,14 +113,14 @@ class WebtoonScalingFrame @JvmOverloads constructor( KeyEvent.KEYCODE_ZOOM_IN, KeyEvent.KEYCODE_NUMPAD_ADD, KeyEvent.KEYCODE_PLUS -> { - smoothScaleTo(scale * 1.1f) + onZoomIn() true } KeyEvent.KEYCODE_ZOOM_OUT, KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_MINUS -> { - smoothScaleTo(scale * 0.9f) + onZoomOut() true } @@ -151,6 +154,14 @@ class WebtoonScalingFrame @JvmOverloads constructor( halfHeight = h / 2f } + override fun onZoomIn() { + smoothScaleTo(scale * 1.1f) + } + + override fun onZoomOut() { + smoothScaleTo(scale * 0.9f) + } + private fun invalidateTarget() { adjustBounds() targetChild.run { diff --git a/app/src/main/res/drawable/ic_zoom_in.xml b/app/src/main/res/drawable/ic_zoom_in.xml new file mode 100644 index 000000000..ea3011fd6 --- /dev/null +++ b/app/src/main/res/drawable/ic_zoom_in.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_zoom_out.xml b/app/src/main/res/drawable/ic_zoom_out.xml new file mode 100644 index 000000000..be2ee0fbb --- /dev/null +++ b/app/src/main/res/drawable/ic_zoom_out.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/fragment_reader_webtoon.xml b/app/src/main/res/layout/fragment_reader_webtoon.xml index a45ab81de..93afd7664 100644 --- a/app/src/main/res/layout/fragment_reader_webtoon.xml +++ b/app/src/main/res/layout/fragment_reader_webtoon.xml @@ -2,11 +2,12 @@ + android:defaultFocusHighlightEnabled="false" + android:focusable="true"> + + + diff --git a/app/src/main/res/layout/item_page.xml b/app/src/main/res/layout/item_page.xml index 3909ca1bb..d4df40563 100644 --- a/app/src/main/res/layout/item_page.xml +++ b/app/src/main/res/layout/item_page.xml @@ -27,4 +27,14 @@ + + diff --git a/app/src/main/res/layout/view_zoom.xml b/app/src/main/res/layout/view_zoom.xml new file mode 100644 index 000000000..b8297bf85 --- /dev/null +++ b/app/src/main/res/layout/view_zoom.xml @@ -0,0 +1,43 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f08133864..ba8a907a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,4 +485,8 @@ No more items can be added To top Moved to top + Zoom out + Zoom in + Show zoom buttons + Whether to show zoom control buttons in the bottom right corner diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index 6ccd0e7ed..96008698d 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -48,6 +48,12 @@ android:title="@string/pages_animation" app:useSimpleSummaryProvider="true" /> + +