PullRefresh.kt (4994B) - raw


      1 /*
      2  * Copyright 2022 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 @file:Suppress("DEPRECATION")
     18 
     19 package me.rhunk.snapenhance.ui.util.pullrefresh
     20 
     21 import androidx.compose.ui.Modifier
     22 import androidx.compose.ui.geometry.Offset
     23 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
     24 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
     25 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
     26 import androidx.compose.ui.input.nestedscroll.nestedScroll
     27 import androidx.compose.ui.platform.debugInspectorInfo
     28 import androidx.compose.ui.platform.inspectable
     29 import androidx.compose.ui.unit.Velocity
     30 
     31 /**
     32  * A nested scroll modifier that provides scroll events to [state].
     33  *
     34  * Note that this modifier must be added above a scrolling container, such as a lazy column, in
     35  * order to receive scroll events. For example:
     36  *
     37  * @sample androidx.compose.material.samples.PullRefreshSample
     38  *
     39  * @param state The [PullRefreshState] associated with this pull-to-refresh component.
     40  * The state will be updated by this modifier.
     41  * @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
     42  */
     43 // TODO(b/244423199): Move pullRefresh into its own material library similar to material-ripple.
     44 fun Modifier.pullRefresh(
     45     state: PullRefreshState,
     46     enabled: Boolean = true,
     47 ) = inspectable(
     48     inspectorInfo = debugInspectorInfo {
     49         name = "pullRefresh"
     50         properties["state"] = state
     51         properties["enabled"] = enabled
     52     },
     53 ) {
     54     Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
     55 }
     56 
     57 /**
     58  * A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom
     59  * pull refresh components.
     60  *
     61  * Note that this modifier must be added above a scrolling container, such as a lazy column, in
     62  * order to receive scroll events. For example:
     63  *
     64  * @sample androidx.compose.material.samples.CustomPullRefreshSample
     65  *
     66  * @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument.
     67  * Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling
     68  * down despite being at the top of a scrollable component), whereas negative delta (swiping up) is
     69  * dispatched first (in case it is needed to push the indicator back up), and then the unconsumed
     70  * delta is passed on to the child. The callback returns how much delta was consumed.
     71  * @param onRelease Callback for when drag is released, takes float flingVelocity as argument.
     72  * The callback returns how much velocity was consumed - in most cases this should only consume
     73  * velocity if pull refresh has been dragged already and the velocity is positive (the fling is
     74  * downwards), as an upwards fling should typically still scroll a scrollable component beneath the
     75  * pullRefresh. This is invoked before any remaining velocity is passed to the child.
     76  * @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
     77  * [onPull] nor [onRelease] will be invoked.
     78  */
     79 fun Modifier.pullRefresh(
     80     onPull: (pullDelta: Float) -> Float,
     81     onRelease: suspend (flingVelocity: Float) -> Float,
     82     enabled: Boolean = true,
     83 ) = inspectable(
     84     inspectorInfo = debugInspectorInfo {
     85         name = "pullRefresh"
     86         properties["onPull"] = onPull
     87         properties["onRelease"] = onRelease
     88         properties["enabled"] = enabled
     89     },
     90 ) {
     91     Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
     92 }
     93 
     94 private class PullRefreshNestedScrollConnection(
     95     private val onPull: (pullDelta: Float) -> Float,
     96     private val onRelease: suspend (flingVelocity: Float) -> Float,
     97     private val enabled: Boolean,
     98 ) : NestedScrollConnection {
     99 
    100     override fun onPreScroll(
    101         available: Offset,
    102         source: NestedScrollSource,
    103     ): Offset = when {
    104         !enabled -> Offset.Zero
    105         source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
    106         else -> Offset.Zero
    107     }
    108 
    109     override fun onPostScroll(
    110         consumed: Offset,
    111         available: Offset,
    112         source: NestedScrollSource,
    113     ): Offset = when {
    114         !enabled -> Offset.Zero
    115         source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
    116         else -> Offset.Zero
    117     }
    118 
    119     override suspend fun onPreFling(available: Velocity): Velocity {
    120         return Velocity(0f, onRelease(available.y))
    121     }
    122 }