@@ -7,25 +7,22 @@ import androidx.compose.animation.core.AnimationSpec
77import androidx.compose.animation.core.animate
88import androidx.compose.animation.core.tween
99import androidx.compose.runtime.Composable
10+ import androidx.compose.runtime.LaunchedEffect
1011import androidx.compose.runtime.asFloatState
1112import androidx.compose.runtime.derivedStateOf
1213import androidx.compose.runtime.getValue
1314import androidx.compose.runtime.mutableFloatStateOf
1415import androidx.compose.runtime.mutableStateOf
16+ import androidx.compose.runtime.remember
1517import androidx.compose.runtime.saveable.SaverScope
1618import androidx.compose.runtime.saveable.rememberSaveable
1719import androidx.compose.runtime.setValue
20+ import androidx.compose.runtime.snapshotFlow
1821import androidx.compose.ui.geometry.Offset
1922import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
2023import androidx.compose.ui.input.nestedscroll.NestedScrollSource
2124import androidx.compose.ui.unit.Velocity
22- import com.lasthopesoftware.bluewater.android.ui.rememberAutoCloseable
23- import com.lasthopesoftware.bluewater.shared.observables.LiftedInteractionState
24- import com.lasthopesoftware.bluewater.shared.observables.MutableInteractionState
25- import com.lasthopesoftware.bluewater.shared.observables.mapNotNull
26- import com.lasthopesoftware.bluewater.shared.observables.toCloseable
2725import com.lasthopesoftware.compilation.DebugFlag
28- import com.lasthopesoftware.resources.closables.AutoCloseableManager
2926import kotlinx.parcelize.Parcelize
3027import kotlin.math.abs
3128
@@ -72,64 +69,62 @@ class MutableAnchoredScrollConnectionState(
7269
7370@SuppressLint(" LongLogTag" )
7471class AnchoredProgressScrollConnectionDispatcher (
75- private val state : MutableAnchoredScrollConnectionState ,
72+ state : AnchoredScrollConnectionState ,
7673 private val fullDistance : Float ,
7774 private val inner : BoundedScrollConnection = NoOpBoundedScrollConnection ,
78- ) : BoundedScrollConnection by inner, AutoCloseable {
75+ ) : BoundedScrollConnection by inner {
7976
8077 companion object {
8178 private const val logTag = " AnchoredProgressScrollConnectionDispatcher"
82- }
83-
84- private val autoCloseableManager = AutoCloseableManager ()
85-
86- private val selectedProgressState = MutableInteractionState (state.selectedProgress)
87-
88- private val relativeAnchors = state.progressAnchors
89-
90- private val totalDistanceTraveled = MutableInteractionState (state.progress * fullDistance)
9179
92- init {
93- autoCloseableManager.manage(
94- selectedProgressState
95- .subscribe {
96- if (DebugFlag .isDebugCompilation) {
97- Log .d(logTag, " selectedProgress: $it " )
98- }
99- state.selectedProgress = it.value
80+ @Composable
81+ fun remember (state : AnchoredScrollConnectionState , inner : BoundedScrollConnection = NoOpBoundedScrollConnection , fullDistance : Float = Float .MAX_VALUE ): AnchoredProgressScrollConnectionDispatcher {
82+ val dispatcher = remember(state, fullDistance, inner) {
83+ AnchoredProgressScrollConnectionDispatcher (
84+ state,
85+ fullDistance,
86+ inner
87+ )
88+ }
89+
90+ LaunchedEffect (dispatcher) {
91+ if (state is MutableAnchoredScrollConnectionState ) {
92+ snapshotFlow { dispatcher.selectedProgressState }
93+ .collect { state.selectedProgress = it }
10094 }
101- .toCloseable()
102- )
103-
104- if (fullDistance != 0f ) {
105- autoCloseableManager.manage(
106- totalDistanceTraveled
107- .mapNotNull()
108- .subscribe {
109- state.progress = (it / fullDistance).coerceIn(0f , 1f )
110- if (DebugFlag .isDebugCompilation) {
111- Log .d(logTag, " state.progress: ${state.progress} " )
112- }
95+ }
96+
97+ LaunchedEffect (dispatcher) {
98+ if (state is MutableAnchoredScrollConnectionState ) {
99+ if (fullDistance != 0f ) {
100+ snapshotFlow { dispatcher.totalDistanceTraveled }
101+ .collect {
102+ state.progress = (it / fullDistance).coerceIn(0f , 1f )
103+ }
104+ } else {
105+ state.progress = 0f
113106 }
114- .toCloseable()
115- )
116- } else {
117- state.progress = 0f
107+ }
108+ }
109+
110+ return dispatcher
118111 }
119112 }
120113
121- override fun close () {
122- autoCloseableManager.close()
123- }
114+ private val relativeAnchors = state.progressAnchors
115+
116+ private var selectedProgressState by mutableStateOf(state.selectedProgress)
117+
118+ private var totalDistanceTraveled by mutableFloatStateOf(state.progress * fullDistance)
124119
125120 @SuppressLint(" LongLogTag" )
126121 override fun onPreScroll (available : Offset , source : NestedScrollSource ): Offset {
127122 // try to consume before LazyColumn to collapse toolbar if needed, hence pre-scroll
128- selectedProgressState.value = null
129- totalDistanceTraveled.value + = available.y
123+ selectedProgressState = null
124+ totalDistanceTraveled + = available.y
130125
131126 if (DebugFlag .isDebugCompilation) {
132- Log .d(logTag, " totalDistanceTraveled: ${ totalDistanceTraveled.value} " )
127+ Log .d(logTag, " totalDistanceTraveled: $totalDistanceTraveled " )
133128 }
134129
135130 return inner.onPreScroll(available, source)
@@ -141,10 +136,10 @@ class AnchoredProgressScrollConnectionDispatcher(
141136
142137 val available = available - innerConsumed
143138
144- totalDistanceTraveled.value - = available.y
139+ totalDistanceTraveled - = available.y
145140
146141 if (DebugFlag .isDebugCompilation) {
147- Log .d(logTag, " totalDistanceTraveled: ${ totalDistanceTraveled.value} " )
142+ Log .d(logTag, " totalDistanceTraveled: $totalDistanceTraveled " )
148143 Log .d(logTag, " consumed: ${consumed.y} " )
149144 Log .d(logTag, " available: ${available.y} " )
150145 }
@@ -162,98 +157,89 @@ class AnchoredProgressScrollConnectionDispatcher(
162157
163158 fun progressTo (progress : Float ) {
164159 val offset = progress * fullDistance
165- val distanceToTravel = offset - totalDistanceTraveled.value
160+ val distanceToTravel = offset - totalDistanceTraveled
166161 val fakeAvailable = Offset (x= 0f , y = distanceToTravel)
167162 val consumed = onPreScroll(fakeAvailable, NestedScrollSource .UserInput )
168- selectedProgressState.value = progress
163+ selectedProgressState = progress
169164 val adjustedConsumed = fakeAvailable - consumed
170165 onPostScroll(adjustedConsumed, fakeAvailable - adjustedConsumed, NestedScrollSource .UserInput )
171166 }
172167}
173168
174- @Parcelize
175- data class MutableScalerState (
176- val min : Float ,
177- val max : Float ,
178- var totalDistanceTraveled : Float = 0f ,
179- ) : Parcelable
180-
181- @Composable
182- fun rememberFullScreenScrollConnectedScalerState (min : Float , max : Float ) = rememberSaveable(max, min) {
183- MutableScalerState (min, max)
169+ interface ScalerState {
170+ val min: Float
171+ val max: Float
172+ val totalDistanceTraveled: Float
184173}
185174
186- @Composable
187- fun rememberFullScreenScrollConnectedScaler ( scalerState : MutableScalerState , maxTravelDistance : Float = Float . MAX_VALUE ): FullScreenScrollConnectedScaler {
188- return rememberAutoCloseable(scalerState, maxTravelDistance) {
189- FullScreenScrollConnectedScaler (scalerState, maxTravelDistance)
190- }
191- }
175+ @Parcelize
176+ private data class MutableScalerState (
177+ override val min : Float ,
178+ override val max : Float ,
179+ override var totalDistanceTraveled : Float = 0f ,
180+ ) : Parcelable, ScalerState
192181
193- /* *
194- * Will scale a value based on a nested **vertical** scroll, sourced from Android examples on this page:
195- * https://developer.android.com/reference/kotlin/androidx/compose/ui/input/nestedscroll/package-summary#extension-functions
196- */
197182@Composable
198- fun rememberFullScreenScrollConnectedScaler (max : Float , min : Float , maxTravelDistance : Float = Float .MAX_VALUE ): FullScreenScrollConnectedScaler {
199- val scalerState = rememberFullScreenScrollConnectedScalerState(min, max)
200-
201- return rememberFullScreenScrollConnectedScaler(scalerState, maxTravelDistance)
183+ fun rememberFullScreenScrollConnectedScalerState (min : Float , max : Float ): ScalerState = rememberSaveable(max, min) {
184+ MutableScalerState (min, max)
202185}
203186
204187class FullScreenScrollConnectedScaler (
205- private val state : MutableScalerState ,
206- private val maxTravelDistance : Float = Float .MAX_VALUE ,
207- ) : BoundedScrollConnection, AutoCloseable {
188+ private val state : ScalerState ,
189+ maxTravelDistance : Float = Float .MAX_VALUE ,
190+ ) : BoundedScrollConnection {
208191
209192 companion object {
210193 private const val logTag = " FullScreenScrollConnectedScaler"
211- }
212194
213- private val autoCloseableManager = AutoCloseableManager ()
195+ /* *
196+ * Will scale a value based on a nested **vertical** scroll, sourced from Android examples on this page:
197+ * https://developer.android.com/reference/kotlin/androidx/compose/ui/input/nestedscroll/package-summary#extension-functions
198+ */
199+ @Composable
200+ fun remember (min : Float , max : Float , maxTravelDistance : Float = Float .MAX_VALUE ): FullScreenScrollConnectedScaler {
201+ val scalerState = rememberFullScreenScrollConnectedScalerState(min, max)
202+
203+ return remember(scalerState, maxTravelDistance)
204+ }
205+
206+ @Composable
207+ fun remember (scalerState : ScalerState , maxTravelDistance : Float = Float .MAX_VALUE ): FullScreenScrollConnectedScaler {
208+ val scaler = remember(scalerState, maxTravelDistance) {
209+ FullScreenScrollConnectedScaler (scalerState, maxTravelDistance)
210+ }
211+
212+ LaunchedEffect (scaler) {
213+ if (scalerState is MutableScalerState ) {
214+ snapshotFlow { scaler.totalDistanceTraveled }
215+ .collect {
216+ scalerState.totalDistanceTraveled = it
217+ }
218+ }
219+ }
220+
221+ return scaler
222+ }
223+ }
214224
225+ private val absoluteMax = abs(maxTravelDistance)
215226 private val fullDistance = state.max - state.min
216227
217- private val totalDistanceTraveled = MutableInteractionState (keepWithinMaxTravelDistance(state.totalDistanceTraveled))
228+ private var totalDistanceTraveled by mutableFloatStateOf (keepWithinMaxTravelDistance(state.totalDistanceTraveled))
218229
219230 val valueState by lazy {
220- autoCloseableManager.manage(
221- LiftedInteractionState (
222- totalDistanceTraveled.mapNotNull().map { (state.max + it).coerceIn(state.min, state.max) },
223- (state.max + state.totalDistanceTraveled).coerceIn(state.min, state.max)
224- )
225- )
226- }
227-
228- val progressState by lazy {
229- autoCloseableManager.manage(
230- LiftedInteractionState (
231- valueState.mapNotNull().map(::calculateProgress),
232- calculateProgress(valueState.value)
233- )
234- )
235- }
236-
237- init {
238- autoCloseableManager.manage(
239- totalDistanceTraveled
240- .mapNotNull()
241- .subscribe {
242- state.totalDistanceTraveled = it
243- }
244- .toCloseable()
245- )
231+ derivedStateOf {
232+ (state.max + totalDistanceTraveled).coerceIn(state.min, state.max)
233+ }
246234 }
247235
248- override fun close () {
249- autoCloseableManager.close()
250- }
236+ val progressState by lazy { derivedStateOf { calculateProgress(valueState.value) } }
251237
252238 @SuppressLint(" LongLogTag" )
253239 override fun onPreScroll (available : Offset , source : NestedScrollSource ): Offset {
254240 // try to consume before LazyColumn to collapse toolbar if needed, hence pre-scroll
255241 val originalValue = valueState.value
256- totalDistanceTraveled.value + = keepWithinMaxTravelDistance(available.y)
242+ totalDistanceTraveled = keepWithinMaxTravelDistance(totalDistanceTraveled + available.y)
257243
258244 if (DebugFlag .isDebugCompilation) {
259245 Log .d(logTag, " totalDistanceTraveled: $totalDistanceTraveled " )
@@ -266,7 +252,7 @@ class FullScreenScrollConnectedScaler(
266252 @SuppressLint(" LongLogTag" )
267253 override fun onPostScroll (consumed : Offset , available : Offset , source : NestedScrollSource ): Offset {
268254 val originalValue = valueState.value
269- totalDistanceTraveled.value - = keepWithinMaxTravelDistance(available.y)
255+ totalDistanceTraveled = keepWithinMaxTravelDistance(totalDistanceTraveled - available.y)
270256
271257 if (DebugFlag .isDebugCompilation) {
272258 Log .d(logTag, " totalDistanceTraveled: $totalDistanceTraveled " )
@@ -278,19 +264,16 @@ class FullScreenScrollConnectedScaler(
278264 }
279265
280266 override fun goToMax () {
281- totalDistanceTraveled.value = keepWithinMaxTravelDistance(0f )
267+ totalDistanceTraveled = keepWithinMaxTravelDistance(0f )
282268 }
283269
284270 override fun goToMin () {
285- totalDistanceTraveled.value = keepWithinMaxTravelDistance(- fullDistance)
271+ totalDistanceTraveled = keepWithinMaxTravelDistance(- fullDistance)
286272 }
287273
288274 private fun calculateProgress (value : Float ) = if (fullDistance == 0f ) 1f else (state.max - value) / fullDistance
289275
290- private fun keepWithinMaxTravelDistance (value : Float ): Float {
291- val absoluteMax = abs(maxTravelDistance)
292- return value.coerceIn(- absoluteMax, absoluteMax)
293- }
276+ private fun keepWithinMaxTravelDistance (value : Float ): Float = value.coerceIn(- absoluteMax, absoluteMax)
294277}
295278
296279/* *
@@ -448,5 +431,5 @@ class ConsumedOffsetErasingNestedScrollConnection(private val inner: BoundedScro
448431 }
449432}
450433
451- fun BoundedScrollConnection.ignoreOffsetConsumption () =
434+ fun BoundedScrollConnection.ignoreConsumedOffset () =
452435 ConsumedOffsetErasingNestedScrollConnection (this )
0 commit comments