• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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  *      https://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 package com.google.accompanist.swiperefresh
18 
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.Stable
21 import androidx.compose.runtime.getValue
22 import androidx.compose.runtime.mutableStateOf
23 import androidx.compose.runtime.remember
24 import androidx.compose.runtime.setValue
25 import kotlin.math.abs
26 import kotlin.math.max
27 import kotlin.math.min
28 import kotlin.math.pow
29 
30 /**
31  * A utility function that calculates various aspects of 'slingshot' behavior.
32  * Adapted from SwipeRefreshLayout#moveSpinner method.
33  *
34  * TODO: Investigate replacing this with a spring.
35  *
36  * @param offsetY The current y offset.
37  * @param maxOffsetY The max y offset.
38  * @param height The height of the item to slingshot.
39  */
40 @Composable
rememberUpdatedSlingshotnull41 internal fun rememberUpdatedSlingshot(
42     offsetY: Float,
43     maxOffsetY: Float,
44     height: Int
45 ): Slingshot {
46     val offsetPercent = min(1f, offsetY / maxOffsetY)
47     val adjustedPercent = max(offsetPercent - 0.4f, 0f) * 5 / 3
48     val extraOffset = abs(offsetY) - maxOffsetY
49 
50     // Can accommodate custom start and slingshot distance here
51     val slingshotDistance = maxOffsetY
52     val tensionSlingshotPercent = max(
53         0f, min(extraOffset, slingshotDistance * 2) / slingshotDistance
54     )
55     val tensionPercent = (
56         (tensionSlingshotPercent / 4) -
57             (tensionSlingshotPercent / 4).pow(2)
58         ) * 2
59     val extraMove = slingshotDistance * tensionPercent * 2
60     val targetY = height + ((slingshotDistance * offsetPercent) + extraMove).toInt()
61     val offset = targetY - height
62     val strokeStart = adjustedPercent * 0.8f
63 
64     val startTrim = 0f
65     val endTrim = strokeStart.coerceAtMost(MaxProgressArc)
66 
67     val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent * 2) * 0.5f
68     val arrowScale = min(1f, adjustedPercent)
69 
70     return remember { Slingshot() }.apply {
71         this.offset = offset
72         this.startTrim = startTrim
73         this.endTrim = endTrim
74         this.rotation = rotation
75         this.arrowScale = arrowScale
76     }
77 }
78 
79 @Stable
80 internal class Slingshot {
81     var offset: Int by mutableStateOf(0)
82     var startTrim: Float by mutableStateOf(0f)
83     var endTrim: Float by mutableStateOf(0f)
84     var rotation: Float by mutableStateOf(0f)
85     var arrowScale: Float by mutableStateOf(0f)
86 }
87 
88 internal const val MaxProgressArc = 0.8f
89