Skip to content

Commit 8a149c6

Browse files
authored
Merge pull request #576 from namehillsoftware/feature/play-item-next-button
[Feature] Play File Next Button
2 parents d71c3ac + 81f6408 commit 8a149c6

File tree

25 files changed

+629
-263
lines changed

25 files changed

+629
-263
lines changed

design/playlist-inner-plus.svg

Lines changed: 37 additions & 0 deletions
Loading

design/playlist-plus.svg

Lines changed: 7 additions & 0 deletions
Loading

projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/android/ui/components/BoundedScrollConnection.kt

Lines changed: 102 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,22 @@ import androidx.compose.animation.core.AnimationSpec
77
import androidx.compose.animation.core.animate
88
import androidx.compose.animation.core.tween
99
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.LaunchedEffect
1011
import androidx.compose.runtime.asFloatState
1112
import androidx.compose.runtime.derivedStateOf
1213
import androidx.compose.runtime.getValue
1314
import androidx.compose.runtime.mutableFloatStateOf
1415
import androidx.compose.runtime.mutableStateOf
16+
import androidx.compose.runtime.remember
1517
import androidx.compose.runtime.saveable.SaverScope
1618
import androidx.compose.runtime.saveable.rememberSaveable
1719
import androidx.compose.runtime.setValue
20+
import androidx.compose.runtime.snapshotFlow
1821
import androidx.compose.ui.geometry.Offset
1922
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
2023
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
2124
import 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
2725
import com.lasthopesoftware.compilation.DebugFlag
28-
import com.lasthopesoftware.resources.closables.AutoCloseableManager
2926
import kotlinx.parcelize.Parcelize
3027
import kotlin.math.abs
3128

@@ -72,64 +69,62 @@ class MutableAnchoredScrollConnectionState(
7269

7370
@SuppressLint("LongLogTag")
7471
class 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

204187
class 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)

projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/android/ui/components/BrowserHeader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.lasthopesoftware.bluewater.android.ui.theme.Dimensions.viewPaddingUni
1010
import kotlin.math.pow
1111

1212
@Composable
13-
fun rememberTitleStartPadding(progress: State<Float>) = remember {
13+
fun rememberTitleStartPadding(progress: State<Float>) = remember(progress) {
1414
derivedStateOf {
1515
val acceleratedProgress = (1 - progress.value).pow(3).coerceIn(0f, 1f)
1616
val acceleratedInverseProgress = 1 - acceleratedProgress

0 commit comments

Comments
 (0)