diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/recyclerview/AutoStaggeredGridLayoutManager.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/AutoStaggeredGridLayoutManager.kt similarity index 98% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/recyclerview/AutoStaggeredGridLayoutManager.kt rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/AutoStaggeredGridLayoutManager.kt index cdbb7207c..07acaab81 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/recyclerview/AutoStaggeredGridLayoutManager.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/AutoStaggeredGridLayoutManager.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.recyclerview +package com.hippo.ehviewer.ui.legacy import android.view.View import androidx.compose.runtime.Composable diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/ContentLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/ContentLayout.kt index 21ebf2bd6..84385fce5 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/ContentLayout.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/ContentLayout.kt @@ -30,10 +30,8 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import com.hippo.ehviewer.R import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.databinding.WidgetContentLayoutBinding +import com.hippo.ehviewer.ui.legacy.LayoutManagerUtils.OnScrollToPositionListener import com.hippo.ehviewer.ui.legacy.ViewTransition.OnShowViewListener -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.HandlerDrawable -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.LayoutManagerUtils -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.LayoutManagerUtils.OnScrollToPositionListener import com.hippo.ehviewer.util.ExceptionUtils import com.hippo.ehviewer.util.getParcelableCompat import com.hippo.ehviewer.yorozuya.IntIdGenerator diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/EasyRecyclerView.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/EasyRecyclerView.java similarity index 99% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/EasyRecyclerView.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/EasyRecyclerView.java index 34c778202..6227afb01 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/EasyRecyclerView.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/EasyRecyclerView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.content.Context; import android.os.Parcel; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/FastScroller.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/FastScroller.java similarity index 99% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/FastScroller.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/FastScroller.java index c3d30b412..b80b7d04e 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/FastScroller.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/FastScroller.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.animation.Animator; import android.animation.ObjectAnimator; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/HandlerDrawable.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/HandlerDrawable.java similarity index 97% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/HandlerDrawable.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/HandlerDrawable.java index 881df6735..658131c33 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/HandlerDrawable.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/HandlerDrawable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.graphics.Canvas; import android.graphics.Color; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/LayoutManagerUtils.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/LayoutManagerUtils.java similarity index 99% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/LayoutManagerUtils.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/LayoutManagerUtils.java index a07bd3eb8..6f2b9054c 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/LayoutManagerUtils.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/LayoutManagerUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.content.Context; import android.graphics.PointF; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/MarginItemDecoration.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/MarginItemDecoration.java similarity index 97% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/MarginItemDecoration.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/MarginItemDecoration.java index 848914aee..b3a3c15a3 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/MarginItemDecoration.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/MarginItemDecoration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.graphics.Rect; import android.view.View; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt index 23e1866e3..a4391aade 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SearchLayout.kt @@ -44,9 +44,6 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhUtils import com.hippo.ehviewer.client.data.ListUrlBuilder import com.hippo.ehviewer.client.exception.EhException -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.MarginItemDecoration -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.SimpleHolder import com.hippo.ehviewer.yorozuya.NumberUtils import com.hippo.ehviewer.yorozuya.ViewUtils diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/SimpleHolder.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleHolder.java similarity index 93% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/SimpleHolder.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleHolder.java index 5e9ff8335..99eac8717 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/SimpleHolder.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleHolder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.view.View; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/SimpleSmoothScroller.java b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleSmoothScroller.java similarity index 95% rename from app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/SimpleSmoothScroller.java rename to app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleSmoothScroller.java index e669d0e1b..a358fa684 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/easyrecyclerview/SimpleSmoothScroller.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/legacy/SimpleSmoothScroller.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hippo.ehviewer.ui.legacy.easyrecyclerview; +package com.hippo.ehviewer.ui.legacy; import android.content.Context; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefresh.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefresh.kt deleted file mode 100644 index 5bdd8e336..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefresh.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.ui.legacy.pullrefresh - -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.platform.inspectable -import androidx.compose.ui.unit.Velocity - -/** - * A nested scroll modifier that provides scroll events to [state]. - * - * Note that this modifier must be added above a scrolling container, such as a lazy column, in - * order to receive scroll events. For example: - * - * @sample androidx.compose.material.samples.PullRefreshSample - * - * @param state The [PullRefreshState] associated with this pull-to-refresh component. - * The state will be updated by this modifier. - * @param enabled If not enabled, all scroll delta and fling velocity will be ignored. - */ -// TODO(b/244423199): Move pullRefresh into its own material library similar to material-ripple. -fun Modifier.pullRefresh( - state: PullRefreshState, - enabled: Boolean = true, -) = inspectable( - inspectorInfo = debugInspectorInfo { - name = "pullRefresh" - properties["state"] = state - properties["enabled"] = enabled - }, -) { - Modifier.pullRefresh(state::onPull, state::onRelease, enabled) -} - -/** - * A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom - * pull refresh components. - * - * Note that this modifier must be added above a scrolling container, such as a lazy column, in - * order to receive scroll events. For example: - * - * @sample androidx.compose.material.samples.CustomPullRefreshSample - * - * @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument. - * Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling - * down despite being at the top of a scrollable component), whereas negative delta (swiping up) is - * dispatched first (in case it is needed to push the indicator back up), and then the unconsumed - * delta is passed on to the child. The callback returns how much delta was consumed. - * @param onRelease Callback for when drag is released, takes float flingVelocity as argument. - * The callback returns how much velocity was consumed - in most cases this should only consume - * velocity if pull refresh has been dragged already and the velocity is positive (the fling is - * downwards), as an upwards fling should typically still scroll a scrollable component beneath the - * pullRefresh. This is invoked before any remaining velocity is passed to the child. - * @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither - * [onPull] nor [onRelease] will be invoked. - */ -fun Modifier.pullRefresh( - onPull: (pullDelta: Float) -> Float, - onRelease: suspend (flingVelocity: Float) -> Float, - enabled: Boolean = true, -) = inspectable( - inspectorInfo = debugInspectorInfo { - name = "pullRefresh" - properties["onPull"] = onPull - properties["onRelease"] = onRelease - properties["enabled"] = enabled - }, -) { - Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled)) -} - -private class PullRefreshNestedScrollConnection( - private val onPull: (pullDelta: Float) -> Float, - private val onRelease: suspend (flingVelocity: Float) -> Float, - private val enabled: Boolean, -) : NestedScrollConnection { - - override fun onPreScroll( - available: Offset, - source: NestedScrollSource, - ): Offset = when { - !enabled -> Offset.Zero - source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up - else -> Offset.Zero - } - - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource, - ): Offset = when { - !enabled -> Offset.Zero - source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down - else -> Offset.Zero - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return Velocity(0f, onRelease(available.y)) - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshIndicator.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshIndicator.kt deleted file mode 100644 index 76f0675b8..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshIndicator.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.ui.legacy.pullrefresh - -import androidx.compose.animation.Crossfade -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.center -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.PathFillType -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.drawscope.rotate -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min -import kotlin.math.pow - -/** - * The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout. - * - * @sample androidx.compose.material.samples.PullRefreshSample - * - * @param refreshing A boolean representing whether a refresh is occurring. - * @param state The [PullRefreshState] which controls where and how the indicator will be drawn. - * @param modifier Modifiers for the indicator. - * @param backgroundColor The color of the indicator's background. - * @param contentColor The color of the indicator's arc and arrow. - * @param scale A boolean controlling whether the indicator's size scales with pull progress or not. - */ -// TODO(b/244423199): Consider whether the state parameter should be replaced with lambdas to -// enable people to use this indicator with custom pull-to-refresh components. -@Composable -fun PullRefreshIndicator( - refreshing: Boolean, - state: PullRefreshState, - modifier: Modifier = Modifier, - backgroundColor: Color = MaterialTheme.colorScheme.surface, - contentColor: Color = contentColorFor(backgroundColor), - scale: Boolean = false, -) { - val showElevation by remember(refreshing, state) { - derivedStateOf { refreshing || state.position > 0.5f } - } - - Box( - modifier = modifier - .size(IndicatorSize) - .pullRefreshIndicatorTransform(state, scale) - .shadow(if (showElevation) Elevation else 0.dp, SpinnerShape, clip = true) - .background(color = backgroundColor, shape = SpinnerShape), - ) { - Crossfade( - targetState = refreshing, - animationSpec = tween(durationMillis = CrossfadeDurationMs), - label = "", - ) { refreshing -> - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - val spinnerSize = (ArcRadius + StrokeWidth).times(2) - - if (refreshing) { - CircularProgressIndicator( - color = contentColor, - strokeWidth = StrokeWidth, - modifier = Modifier.size(spinnerSize), - ) - } else { - CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize)) - } - } - } - } -} - -/** - * Modifier.size MUST be specified. - */ -@Composable -private fun CircularArrowIndicator( - state: PullRefreshState, - color: Color, - modifier: Modifier, -) { - val path = remember { Path().apply { fillType = PathFillType.EvenOdd } } - - val targetAlpha by remember(state) { - derivedStateOf { - if (state.progress >= 1f) MaxAlpha else MinAlpha - } - } - - val alphaState = animateFloatAsState(targetValue = targetAlpha, animationSpec = AlphaTween) - - // Empty semantics for tests - Canvas(modifier.semantics {}) { - val values = ArrowValues(state.progress) - val alpha = alphaState.value - - rotate(degrees = values.rotation) { - val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f - val arcBounds = Rect( - size.center.x - arcRadius, - size.center.y - arcRadius, - size.center.x + arcRadius, - size.center.y + arcRadius, - ) - drawArc( - color = color, - alpha = alpha, - startAngle = values.startAngle, - sweepAngle = values.endAngle - values.startAngle, - useCenter = false, - topLeft = arcBounds.topLeft, - size = arcBounds.size, - style = Stroke( - width = StrokeWidth.toPx(), - cap = StrokeCap.Square, - ), - ) - drawArrow(path, arcBounds, color, alpha, values) - } - } -} - -@Immutable -private class ArrowValues( - val rotation: Float, - val startAngle: Float, - val endAngle: Float, - val scale: Float, -) - -private fun ArrowValues(progress: Float): ArrowValues { - // Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%. - val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3 - // How far beyond the threshold pull has gone, as a percentage of the threshold. - val overshootPercent = abs(progress) - 1.0f - // Limit the overshoot to 200%. Linear between 0 and 200. - val linearTension = overshootPercent.coerceIn(0f, 2f) - // Non-linear tension. Increases with linearTension, but at a decreasing rate. - val tensionPercent = linearTension - linearTension.pow(2) / 4 - - // Calculations based on SwipeRefreshLayout specification. - val endTrim = adjustedPercent * MaxProgressArc - val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f - val startAngle = rotation * 360 - val endAngle = (rotation + endTrim) * 360 - val scale = min(1f, adjustedPercent) - - return ArrowValues(rotation, startAngle, endAngle, scale) -} - -private fun DrawScope.drawArrow( - arrow: Path, - bounds: Rect, - color: Color, - alpha: Float, - values: ArrowValues, -) { - arrow.reset() - arrow.moveTo(0f, 0f) // Move to left corner - arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner - - // Line to tip of arrow - arrow.lineTo( - x = ArrowWidth.toPx() * values.scale / 2, - y = ArrowHeight.toPx() * values.scale, - ) - - val radius = min(bounds.width, bounds.height) / 2f - val inset = ArrowWidth.toPx() * values.scale / 2f - arrow.translate( - Offset( - x = radius + bounds.center.x - inset, - y = bounds.center.y + StrokeWidth.toPx() / 2f, - ), - ) - arrow.close() - rotate(degrees = values.endAngle) { - drawPath(path = arrow, color = color, alpha = alpha) - } -} - -private const val CrossfadeDurationMs = 100 -private const val MaxProgressArc = 0.8f - -private val IndicatorSize = 40.dp -private val SpinnerShape = CircleShape -private val ArcRadius = 7.5.dp -private val StrokeWidth = 2.5.dp -private val ArrowWidth = 10.dp -private val ArrowHeight = 5.dp -private val Elevation = 6.dp - -// Values taken from SwipeRefreshLayout -private const val MinAlpha = 0.3f -private const val MaxAlpha = 1f -private val AlphaTween = tween(300, easing = LinearEasing) diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshIndicatorTransform.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshIndicatorTransform.kt deleted file mode 100644 index a5ca1518a..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshIndicatorTransform.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.ui.legacy.pullrefresh - -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.drawscope.clipRect -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.platform.inspectable - -/** - * A modifier for translating the position and scaling the size of a pull-to-refresh indicator - * based on the given [PullRefreshState]. - * - * @sample androidx.compose.material.samples.PullRefreshIndicatorTransformSample - * - * @param state The [PullRefreshState] which determines the position of the indicator. - * @param scale A boolean controlling whether the indicator's size scales with pull progress or not. - */ -// TODO: Consider whether the state parameter should be replaced with lambdas. -fun Modifier.pullRefreshIndicatorTransform( - state: PullRefreshState, - scale: Boolean = false, -) = inspectable( - inspectorInfo = debugInspectorInfo { - name = "pullRefreshIndicatorTransform" - properties["state"] = state - properties["scale"] = scale - }, -) { - Modifier - // Essentially we only want to clip the at the top, so the indicator will not appear when - // the position is 0. It is preferable to clip the indicator as opposed to the layout that - // contains the indicator, as this would also end up clipping shadows drawn by items in a - // list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE - // for the other dimensions to allow for more room for elevation / arbitrary indicators - we - // only ever really want to clip at the top edge. - .drawWithContent { - clipRect( - top = 0f, - left = -Float.MAX_VALUE, - right = Float.MAX_VALUE, - bottom = Float.MAX_VALUE, - ) { - this@drawWithContent.drawContent() - } - } - .graphicsLayer { - translationY = state.position - size.height - - if (scale && !state.refreshing) { - val scaleFraction = LinearOutSlowInEasing - .transform(state.position / state.threshold) - .coerceIn(0f, 1f) - scaleX = scaleFraction - scaleY = scaleFraction - } - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshState.kt b/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshState.kt deleted file mode 100644 index a269b69a3..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/legacy/pullrefresh/PullRefreshState.kt +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.ui.legacy.pullrefresh - -import androidx.compose.animation.core.animate -import androidx.compose.foundation.MutatorMutex -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlin.math.abs -import kotlin.math.pow - -/** - * Creates a [PullRefreshState] that is remembered across compositions. - * - * Changes to [refreshing] will result in [PullRefreshState] being updated. - * - * @sample androidx.compose.material.samples.PullRefreshSample - * - * @param refreshing A boolean representing whether a refresh is currently occurring. - * @param onRefresh The function to be called to trigger a refresh. - * @param refreshThreshold The threshold below which, if a release - * occurs, [onRefresh] will be called. - * @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This - * offset corresponds to the position of the bottom of the indicator. - */ -@Composable -fun rememberPullRefreshState( - refreshing: Boolean, - onRefresh: () -> Unit, - refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold, - refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset, -): PullRefreshState { - require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" } - - val scope = rememberCoroutineScope() - val onRefreshState = rememberUpdatedState(onRefresh) - val thresholdPx: Float - val refreshingOffsetPx: Float - - with(LocalDensity.current) { - thresholdPx = refreshThreshold.toPx() - refreshingOffsetPx = refreshingOffset.toPx() - } - - val state = remember(scope) { - PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) - } - - SideEffect { - state.setRefreshing(refreshing) - state.setThreshold(thresholdPx) - state.setRefreshingOffset(refreshingOffsetPx) - } - - return state -} - -/** - * A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh - * behaviour to a scroll component. Based on Android's SwipeRefreshLayout. - * - * Provides [progress], a float representing how far the user has pulled as a percentage of the - * refreshThreshold. Values of one or less indicate that the user has not yet pulled past the - * threshold. Values greater than one indicate how far past the threshold the user has pulled. - * - * Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like - * pull-to-refresh behaviour with a custom indicator. - * - * Should be created using [rememberPullRefreshState]. - */ -class PullRefreshState internal constructor( - private val animationScope: CoroutineScope, - private val onRefreshState: State<() -> Unit>, - refreshingOffset: Float, - threshold: Float, -) { - /** - * A float representing how far the user has pulled as a percentage of the refreshThreshold. - * - * If the component has not been pulled at all, progress is zero. If the pull has reached - * halfway to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has - * gone beyond the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to - * two times the refreshThreshold. - */ - val progress get() = adjustedDistancePulled / threshold - - internal val refreshing get() = _refreshing - internal val position get() = _position - internal val threshold get() = _threshold - - private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier } - - private var _refreshing by mutableStateOf(false) - private var _position by mutableStateOf(0f) - private var distancePulled by mutableStateOf(0f) - private var _threshold by mutableStateOf(threshold) - private var _refreshingOffset by mutableStateOf(refreshingOffset) - - internal fun onPull(pullDelta: Float): Float { - if (_refreshing) return 0f // Already refreshing, do nothing. - - val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f) - val dragConsumed = newOffset - distancePulled - distancePulled = newOffset - _position = calculateIndicatorPosition() - return dragConsumed - } - - internal fun onRelease(velocity: Float): Float { - if (refreshing) return 0f // Already refreshing, do nothing - - if (adjustedDistancePulled > threshold) { - onRefreshState.value() - } - animateIndicatorTo(0f) - val consumed = when { - // We are flinging without having dragged the pull refresh (for example a fling inside - // a list) - don't consume - distancePulled == 0f -> 0f - // If the velocity is negative, the fling is upwards, and we don't want to prevent the - // the list from scrolling - velocity < 0f -> 0f - // We are showing the indicator, and the fling is downwards - consume everything - else -> velocity - } - distancePulled = 0f - return consumed - } - - internal fun setRefreshing(refreshing: Boolean) { - if (_refreshing != refreshing) { - _refreshing = refreshing - distancePulled = 0f - animateIndicatorTo(if (refreshing) _refreshingOffset else 0f) - } - } - - internal fun setThreshold(threshold: Float) { - _threshold = threshold - } - - internal fun setRefreshingOffset(refreshingOffset: Float) { - if (_refreshingOffset != refreshingOffset) { - _refreshingOffset = refreshingOffset - if (refreshing) animateIndicatorTo(refreshingOffset) - } - } - - // Make sure to cancel any existing animations when we launch a new one. We use this instead of - // Animatable as calling snapTo() on every drag delta has a one frame delay, and some extra - // overhead of running through the animation pipeline instead of directly mutating the state. - private val mutatorMutex = MutatorMutex() - - private fun animateIndicatorTo(offset: Float) = animationScope.launch { - mutatorMutex.mutate { - animate(initialValue = _position, targetValue = offset) { value, _ -> - _position = value - } - } - } - - private fun calculateIndicatorPosition(): Float = when { - // If drag hasn't gone past the threshold, the position is the adjustedDistancePulled. - adjustedDistancePulled <= threshold -> adjustedDistancePulled - else -> { - // How far beyond the threshold pull has gone, as a percentage of the threshold. - val overshootPercent = abs(progress) - 1.0f - // Limit the overshoot to 200%. Linear between 0 and 200. - val linearTension = overshootPercent.coerceIn(0f, 2f) - // Non-linear tension. Increases with linearTension, but at a decreasing rate. - val tensionPercent = linearTension - linearTension.pow(2) / 4 - // The additional offset beyond the threshold. - val extraOffset = threshold * tensionPercent - threshold + extraOffset - } - } -} - -/** - * Default parameter values for [rememberPullRefreshState]. - */ -object PullRefreshDefaults { - /** - * If the indicator is below this threshold offset when it is released, a refresh - * will be triggered. - */ - val RefreshThreshold = 80.dp - - /** - * The offset at which the indicator should be rendered whilst a refresh is occurring. - */ - val RefreshingOffset = 56.dp -} - -/** - * The distance pulled is multiplied by this value to give us the adjusted distance pulled, which - * is used in calculating the indicator position (when the adjusted distance pulled is less than - * the refresh threshold, it is the indicator position, otherwise the indicator position is - * derived from the progress). - */ -private const val DragMultiplier = 0.5f diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt index 8a2bfd94b..c0f3be60b 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadsScene.kt @@ -80,19 +80,19 @@ import com.hippo.ehviewer.spider.putToDownloadDir import com.hippo.ehviewer.ui.compose.CropDefaults import com.hippo.ehviewer.ui.compose.data.requestOf import com.hippo.ehviewer.ui.compose.setMD3Content +import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder import com.hippo.ehviewer.ui.legacy.CheckBoxDialogBuilder +import com.hippo.ehviewer.ui.legacy.EasyRecyclerView +import com.hippo.ehviewer.ui.legacy.EasyRecyclerView.CustomChoiceListener import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder import com.hippo.ehviewer.ui.legacy.FabLayout import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener import com.hippo.ehviewer.ui.legacy.FabLayout.OnExpandListener +import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener +import com.hippo.ehviewer.ui.legacy.HandlerDrawable +import com.hippo.ehviewer.ui.legacy.STRATEGY_MIN_SIZE import com.hippo.ehviewer.ui.legacy.ViewTransition -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView.CustomChoiceListener -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.FastScroller.OnDragHandlerListener -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.HandlerDrawable -import com.hippo.ehviewer.ui.legacy.recyclerview.AutoStaggeredGridLayoutManager -import com.hippo.ehviewer.ui.legacy.recyclerview.STRATEGY_MIN_SIZE import com.hippo.ehviewer.ui.navToReader import com.hippo.ehviewer.util.sendTo import com.hippo.ehviewer.yorozuya.FileUtils diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt index 2c268c25e..6a417ab1f 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.kt @@ -53,14 +53,14 @@ import com.hippo.ehviewer.ui.CommonOperations import com.hippo.ehviewer.ui.legacy.AddDeleteDrawable import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder import com.hippo.ehviewer.ui.legacy.ContentLayout +import com.hippo.ehviewer.ui.legacy.EasyRecyclerView +import com.hippo.ehviewer.ui.legacy.EasyRecyclerView.CustomChoiceListener import com.hippo.ehviewer.ui.legacy.FabLayout import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener import com.hippo.ehviewer.ui.legacy.FabLayout.OnExpandListener +import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener import com.hippo.ehviewer.ui.legacy.GalleryInfoContentHelper import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView.CustomChoiceListener -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.FastScroller.OnDragHandlerListener import com.hippo.ehviewer.util.getParcelableCompat import com.hippo.ehviewer.yorozuya.ObjectUtils import com.hippo.ehviewer.yorozuya.SimpleHandler diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryAdapter.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryAdapter.kt index cb7e4b27b..27abd4848 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryAdapter.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryAdapter.kt @@ -28,10 +28,10 @@ import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings.detailSize import com.hippo.ehviewer.Settings.thumbSizeDp import com.hippo.ehviewer.client.data.GalleryInfo -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.MarginItemDecoration -import com.hippo.ehviewer.ui.legacy.recyclerview.AutoStaggeredGridLayoutManager -import com.hippo.ehviewer.ui.legacy.recyclerview.STRATEGY_MIN_SIZE -import com.hippo.ehviewer.ui.legacy.recyclerview.STRATEGY_SUITABLE_SIZE +import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager +import com.hippo.ehviewer.ui.legacy.MarginItemDecoration +import com.hippo.ehviewer.ui.legacy.STRATEGY_MIN_SIZE +import com.hippo.ehviewer.ui.legacy.STRATEGY_SUITABLE_SIZE import com.hippo.ehviewer.yorozuya.LayoutUtils.dp2pix import splitties.init.appCtx diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.kt index 1340d2a24..56c707daf 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.kt @@ -72,6 +72,7 @@ import com.hippo.ehviewer.client.parser.VoteCommentParser import com.hippo.ehviewer.dao.Filter import com.hippo.ehviewer.ui.jumpToReaderByPage import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder +import com.hippo.ehviewer.ui.legacy.EasyRecyclerView import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder import com.hippo.ehviewer.ui.legacy.FabLayout import com.hippo.ehviewer.ui.legacy.LinkifyTextView @@ -79,7 +80,6 @@ import com.hippo.ehviewer.ui.legacy.ObservedTextView import com.hippo.ehviewer.ui.legacy.URLImageGetter import com.hippo.ehviewer.ui.legacy.ViewTransition import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView import com.hippo.ehviewer.ui.navWithUrl import com.hippo.ehviewer.ui.openBrowser import com.hippo.ehviewer.ui.scene.GalleryListScene.Companion.toStartArgs diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt index a17206e75..d8378f009 100755 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt @@ -163,7 +163,7 @@ import com.hippo.ehviewer.ui.legacy.GalleryRatingBar import com.hippo.ehviewer.ui.legacy.GalleryRatingBar.OnUserRateListener import com.hippo.ehviewer.ui.legacy.ObservedTextView import com.hippo.ehviewer.ui.legacy.URLImageGetter -import com.hippo.ehviewer.ui.legacy.recyclerview.calculateSuitableSpanCount +import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount import com.hippo.ehviewer.ui.navToReader import com.hippo.ehviewer.ui.openBrowser import com.hippo.ehviewer.ui.removeFromFavorites diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt index 1aee23f11..697536a6f 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt @@ -84,16 +84,16 @@ import com.hippo.ehviewer.ui.doGalleryInfoAction import com.hippo.ehviewer.ui.legacy.AddDeleteDrawable import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder import com.hippo.ehviewer.ui.legacy.BringOutTransition +import com.hippo.ehviewer.ui.legacy.EasyRecyclerView import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder import com.hippo.ehviewer.ui.legacy.FabLayout import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener import com.hippo.ehviewer.ui.legacy.FabLayout.OnExpandListener +import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener import com.hippo.ehviewer.ui.legacy.GalleryInfoContentHelper import com.hippo.ehviewer.ui.legacy.SearchLayout import com.hippo.ehviewer.ui.legacy.ViewTransition import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.EasyRecyclerView -import com.hippo.ehviewer.ui.legacy.easyrecyclerview.FastScroller.OnDragHandlerListener import com.hippo.ehviewer.util.getParcelableCompat import com.hippo.ehviewer.yorozuya.AnimationUtils import com.hippo.ehviewer.yorozuya.SimpleAnimatorListener diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt index 11665a4a2..35a77c04a 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt @@ -59,7 +59,7 @@ import com.hippo.ehviewer.ui.compose.data.EhPreviewItem import com.hippo.ehviewer.ui.compose.rememberDialogState import com.hippo.ehviewer.ui.compose.rememberMemorized import com.hippo.ehviewer.ui.compose.setMD3Content -import com.hippo.ehviewer.ui.legacy.recyclerview.calculateSuitableSpanCount +import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount import com.hippo.ehviewer.ui.navToReader import com.hippo.ehviewer.util.getParcelableCompat import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/res/layout/dialog_recycler_view.xml b/app/src/main/res/layout/dialog_recycler_view.xml index d36ad6d80..fd122992d 100644 --- a/app/src/main/res/layout/dialog_recycler_view.xml +++ b/app/src/main/res/layout/dialog_recycler_view.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - - diff --git a/app/src/main/res/layout/preference_recyclerview.xml b/app/src/main/res/layout/preference_recyclerview.xml index 8fb270e36..c3de625dd 100644 --- a/app/src/main/res/layout/preference_recyclerview.xml +++ b/app/src/main/res/layout/preference_recyclerview.xml @@ -15,7 +15,7 @@ ~ You should have received a copy of the GNU General Public License along with EhViewer. ~ If not, see . --> - - - - - -