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 }