diff --git a/.idea/.gitignore b/.idea/.gitignore index 5506c50c7..1a4d8f151 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -3,3 +3,5 @@ /workspace.xml /migrations.xml /runConfigurations.xml +/appInsightsSettings.xml +/kotlinCodeInsightSettings.xml 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 9d22f3292..725f6aaf6 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 @@ -28,7 +28,8 @@ import javax.inject.Inject @AndroidEntryPoint class WebtoonReaderFragment : BaseReaderFragment(), - WebtoonRecyclerView.OnWebtoonScrollListener { + WebtoonRecyclerView.OnWebtoonScrollListener, + WebtoonRecyclerView.OnPullGestureListener { @Inject lateinit var networkState: NetworkState @@ -39,6 +40,8 @@ class WebtoonReaderFragment : BaseReaderFragment() private val scrollInterpolator = DecelerateInterpolator() private var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null + private var canGoPrev = true + private var canGoNext = true override fun onCreateViewBinding( inflater: LayoutInflater, @@ -47,8 +50,6 @@ class WebtoonReaderFragment : BaseReaderFragment() override fun onViewBindingCreated(binding: FragmentReaderWebtoonBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) - var canGoPrev = true - var canGoNext = true viewModel.readerUiTopOffset.observe(viewLifecycleOwner) { top -> binding.feedbackTop.translationY = top.toFloat() } @@ -62,40 +63,7 @@ class WebtoonReaderFragment : BaseReaderFragment() recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also { addOnScrollListener(it) } - setOnPullGestureListener(object : WebtoonRecyclerView.OnPullGestureListener { - override fun onPullProgressTop(progress: Float) { - if (canGoPrev) { - setFeedbackText(binding.feedbackTop, getString(R.string.pull_to_prev_chapter)) - } else { - setFeedbackText(binding.feedbackTop, getString(R.string.pull_top_no_prev)) - } - updateFeedback(binding.feedbackTop, progress) - } - override fun onPullProgressBottom(progress: Float) { - if (canGoNext) { - setFeedbackText(binding.feedbackBottom, getString(R.string.pull_to_next_chapter)) - } else { - setFeedbackText(binding.feedbackBottom, getString(R.string.pull_bottom_no_next)) - } - updateFeedback(binding.feedbackBottom, progress) - } - override fun onPullTriggeredTop() { - fadeOut(binding.feedbackTop) - if (canGoPrev) { - viewModel.switchChapterBy(-1) - } - } - override fun onPullTriggeredBottom() { - fadeOut(binding.feedbackBottom) - if (canGoNext) { - viewModel.switchChapterBy(1) - } - } - override fun onPullCancelled() { - fadeOut(binding.feedbackTop) - fadeOut(binding.feedbackBottom) - } - }) + setOnPullGestureListener(this@WebtoonReaderFragment) } viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) { binding.frame.isZoomEnable = it @@ -113,11 +81,8 @@ class WebtoonReaderFragment : BaseReaderFragment() viewModel.readerSettingsProducer.observe(viewLifecycleOwner) { it.applyBackground(binding.root) } - viewModel.readerMode.observe(viewLifecycleOwner) { mode -> - binding.recyclerView.isPullGestureEnabled = (mode == org.koitharu.kotatsu.core.prefs.ReaderMode.WEBTOON) && viewModel.isWebtoonPullGestureEnabled.value - } viewModel.isWebtoonPullGestureEnabled.observe(viewLifecycleOwner) { enabled -> - binding.recyclerView.isPullGestureEnabled = (viewModel.readerMode.value == org.koitharu.kotatsu.core.prefs.ReaderMode.WEBTOON) && enabled + binding.recyclerView.isPullGestureEnabled = enabled } viewModel.uiState.observe(viewLifecycleOwner) { state -> if (state != null) { @@ -226,6 +191,47 @@ class WebtoonReaderFragment : BaseReaderFragment() return true } + override fun onPullProgressTop(progress: Float) { + val binding = viewBinding ?: return + if (canGoPrev) { + binding.feedbackTop.setFeedbackText(getString(R.string.pull_to_prev_chapter)) + } else { + binding.feedbackTop.setFeedbackText(getString(R.string.pull_top_no_prev)) + } + binding.feedbackTop.updateFeedback(progress) + } + + override fun onPullProgressBottom(progress: Float) { + val binding = viewBinding ?: return + if (canGoNext) { + binding.feedbackBottom.setFeedbackText(getString(R.string.pull_to_next_chapter)) + } else { + binding.feedbackBottom.setFeedbackText(getString(R.string.pull_bottom_no_next)) + } + binding.feedbackBottom.updateFeedback(progress) + } + + override fun onPullTriggeredTop() { + (viewBinding ?: return).feedbackTop.fadeOut() + if (canGoPrev) { + viewModel.switchChapterBy(-1) + } + } + + override fun onPullTriggeredBottom() { + (viewBinding ?: return).feedbackBottom.fadeOut() + if (canGoNext) { + viewModel.switchChapterBy(1) + } + } + + override fun onPullCancelled() { + viewBinding?.apply { + feedbackTop.fadeOut() + feedbackBottom.fadeOut() + } + } + private fun RecyclerView.findCurrentPagePosition(): Int { val centerX = width / 2f val centerY = height - resources.getDimension(R.dimen.webtoon_pages_gap) @@ -235,25 +241,25 @@ class WebtoonReaderFragment : BaseReaderFragment() val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION return getChildAdapterPosition(view) } -} -private fun updateFeedback(tv: TextView, progress: Float) { - val clamped = progress.coerceIn(0f, 1.2f) - tv.alpha = clamped.coerceAtMost(1f) - tv.scaleX = 0.9f + 0.1f * clamped.coerceAtMost(1f) - tv.scaleY = tv.scaleX -} + private fun TextView.updateFeedback(progress: Float) { + val clamped = progress.coerceIn(0f, 1.2f) + this.alpha = clamped.coerceAtMost(1f) + this.scaleX = 0.9f + 0.1f * clamped.coerceAtMost(1f) + this.scaleY = this.scaleX + } -private fun fadeOut(tv: TextView) { - tv.animate().alpha(0f).setDuration(150L).start() -} + private fun TextView.fadeOut() { + animate().alpha(0f).setDuration(150L).start() + } -private fun setFeedbackText(tv: TextView, text: CharSequence) { - if (tv.alpha <= 0f && text.isNotEmpty()) { - tv.alpha = 0f - tv.text = text - tv.animate().alpha(1f).setDuration(120L).start() - } else { - tv.text = text + private fun TextView.setFeedbackText(text: CharSequence) { + if (this.alpha <= 0f && text.isNotEmpty()) { + this.alpha = 0f + this.text = text + animate().alpha(1f).setDuration(120L).start() + } else { + this.text = text + } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt index baf0bddc6..6653f8c47 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt @@ -12,6 +12,8 @@ import androidx.core.view.isNotEmpty import androidx.core.view.iterator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_BOTTOM +import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_TOP import java.util.Collections import java.util.LinkedList import java.util.WeakHashMap @@ -27,63 +29,10 @@ class WebtoonRecyclerView @JvmOverloads constructor( var isPullGestureEnabled: Boolean = false var pullThreshold: Float = 0.3f - private var pullProgressTop: Float = 0f - private var pullProgressBottom: Float = 0f private var pullListener: OnPullGestureListener? = null init { - setEdgeEffectFactory(object : EdgeEffectFactory() { - override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect { - return object : EdgeEffect(view.context) { - override fun onPull(deltaDistance: Float) { - val sign = if (direction == DIRECTION_TOP) 1f else if (direction == DIRECTION_BOTTOM) 1f else 0f - if (sign != 0f) onPull(deltaDistance, 0.5f) - } - - override fun onPull(deltaDistance: Float, displacement: Float) { - if (!isPullGestureEnabled) return - if (direction == DIRECTION_TOP) { - pullProgressTop = (pullProgressTop + deltaDistance).coerceAtLeast(0f) - pullListener?.onPullProgressTop(pullProgressTop / pullThreshold) - } else if (direction == DIRECTION_BOTTOM) { - pullProgressBottom = (pullProgressBottom + deltaDistance).coerceAtLeast(0f) - pullListener?.onPullProgressBottom(pullProgressBottom / pullThreshold) - } - } - - override fun onRelease() { - if (!isPullGestureEnabled) { - pullProgressTop = 0f - pullProgressBottom = 0f - return - } - var triggered = false - if (direction == DIRECTION_TOP) { - if (pullProgressTop >= pullThreshold) { - pullListener?.onPullTriggeredTop() - triggered = true - } - pullProgressTop = 0f - pullListener?.onPullProgressTop(0f) - } else if (direction == DIRECTION_BOTTOM) { - if (pullProgressBottom >= pullThreshold) { - pullListener?.onPullTriggeredBottom() - triggered = true - } - pullProgressBottom = 0f - pullListener?.onPullProgressBottom(0f) - } - if (!triggered) { - pullListener?.onPullCancelled() - } - } - - override fun draw(canvas: Canvas?): Boolean { - return false - } - } - } - }) + setEdgeEffectFactory(PullEffect.Factory()) } fun setOnPullGestureListener(listener: OnPullGestureListener?) { @@ -246,6 +195,68 @@ class WebtoonRecyclerView @JvmOverloads constructor( } } + private class PullEffect( + view: RecyclerView, + private val direction: Int, + private val pullThreshold: Float, + private val pullListener: OnPullGestureListener, + ) : EdgeEffect(view.context) { + + private var pullProgressTop: Float = 0f + private var pullProgressBottom: Float = 0f + + override fun onPull(deltaDistance: Float) { + val sign = if (direction == DIRECTION_TOP) 1f else if (direction == DIRECTION_BOTTOM) 1f else 0f + if (sign != 0f) onPull(deltaDistance, 0.5f) + } + + override fun onPull(deltaDistance: Float, displacement: Float) { + if (direction == DIRECTION_TOP) { + pullProgressTop = (pullProgressTop + deltaDistance).coerceAtLeast(0f) + pullListener.onPullProgressTop(pullProgressTop / pullThreshold) + } else if (direction == DIRECTION_BOTTOM) { + pullProgressBottom = (pullProgressBottom + deltaDistance).coerceAtLeast(0f) + pullListener.onPullProgressBottom(pullProgressBottom / pullThreshold) + } + } + + override fun onRelease() { + var triggered = false + if (direction == DIRECTION_TOP) { + if (pullProgressTop >= pullThreshold) { + pullListener.onPullTriggeredTop() + triggered = true + } + pullProgressTop = 0f + pullListener.onPullProgressTop(0f) + } else if (direction == DIRECTION_BOTTOM) { + if (pullProgressBottom >= pullThreshold) { + pullListener.onPullTriggeredBottom() + triggered = true + } + pullProgressBottom = 0f + pullListener.onPullProgressBottom(0f) + } + if (!triggered) { + pullListener.onPullCancelled() + } + } + + override fun draw(canvas: Canvas?): Boolean = false + + class Factory : EdgeEffectFactory() { + + override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect { + val pullListener = (view as? WebtoonRecyclerView)?.pullListener + return if (pullListener != null && view.isPullGestureEnabled) { + PullEffect(view, direction, view.pullThreshold, pullListener) + } else { + super.createEdgeEffect(view, direction) + } + } + } + } + interface OnWebtoonScrollListener { fun onScrollChanged( diff --git a/app/src/main/res/layout/fragment_reader_webtoon.xml b/app/src/main/res/layout/fragment_reader_webtoon.xml index 766bb9063..a580cf75c 100644 --- a/app/src/main/res/layout/fragment_reader_webtoon.xml +++ b/app/src/main/res/layout/fragment_reader_webtoon.xml @@ -2,6 +2,7 @@ - + android:theme="@style/ThemeOverlay.Material3.Dark" + tools:alpha="1" /> - + android:theme="@style/ThemeOverlay.Material3.Dark" + tools:alpha="1" />