mahozad / wavy-slider

Multiplatform wavy slider/progress bar similar to the one in Android 13
https://mahozad.ir/wavy-slider
Apache License 2.0
130 stars 5 forks source link

[Question] Value range declaration #9

Closed BobbyESP closed 5 months ago

BobbyESP commented 5 months ago

Hi! I found this library searching for the implementation in Jetpack Compose of the M3 wavy seekbar. I'm trying to implement it but I wasn't able to found a parameter to define the range of the bar for when seeking. Could you explain to me a little bit how does this work? (the idea is something like what you have in the GIF of the README)

mahozad commented 5 months ago

Hi Do you mean something like the below image? image

I thought something like this would not be needed. So, there is no RangeWavySlider. Do you need a RangeWavySlider like RangeSlider?

If you want something similar to the GIF of the README, this is how I've done it:

@Composable
fun App() {
    var progress by remember { mutableStateOf(0f) }
    MySlider(
        length = 30.seconds,
        progress = progress,
        onSeek = { progress = it }
    )

    // Simulate (fake) media progress
    LaunchedEffect(Unit) {
        while (true) {
            progress = (progress + 0.001f) % 1f
            delay(30.milliseconds)
        }
    }
}

@Composable
fun MySlider(
    length: Duration,
    progress: Float,
    onSeek: (Float) -> Unit
) {
    var isSeeking by remember { mutableStateOf(false) }
    var seek by remember { mutableFloatStateOf(progress) }
    var time by remember { mutableStateOf(length * progress.toDouble()) }
    LaunchedEffect(isSeeking, progress) {
        while (isSeeking) {
            time = length * seek.toDouble()
            delay(30.milliseconds)
        }
        time = length * progress.toDouble()
    }
    Row(
        verticalAlignment = Alignment.CenterVertically
    ) {
        Timestamp(time = time, textAlign = TextAlign.Start)
        WavySlider(
            value = if (isSeeking) {
                seek
            } else {
                progress
            },
            onValueChange = {
                isSeeking = true
                seek = it
            },
            onValueChangeFinished = {
                isSeeking = false
                // Call onSeek only when the seek has finished (to prevent continuous media seeking)
                onSeek(seek)
            },
            modifier = Modifier.weight(1f)
        )
        Timestamp(time = length, textAlign = TextAlign.End)
    }
}

@Composable
fun Timestamp(
    time: Duration,
    textAlign: TextAlign
) = Text(
    text = time.toString(DurationUnit.SECONDS), // OR format the duration however you want (e.g. mm:ss)
    textAlign = textAlign,
    // The width of the timestamp components is fixed (constant).
    // This is to prevent their width to change when their text changes (because the font is not monospace).
    // If, instead, the dynamic width were used (i.e. not specifying a constant width == the default wrap size),
    // a change in timestamps text (and hence their component width)
    // caused the width of the slider to change as well by a tiny amount
    // because the slider is set to take the remaining width of the parent.
    // This caused problem when dragging the slider thumb with mouse
    // (could not drag the thumb continuously, as it got stuck and did not change anymore).
    // Could also have used a monospace font and then this wouldn't be required anymore.
    modifier = Modifier.width(43.dp)
)
BobbyESP commented 5 months ago

Hi! First of all thank you for the fast response and such a nice example but it is not that. Maybe it was my fault, but the value of the seekbar goes from 0f to 1f, no? Maybe that was the problem. The problem was the next: Screenshot_2024-01-08-07-51-49-013_com.zionhuang.music.debug.jpg

With the original slider implementation of M3, we can define the range of the values we want to change (not just from 0f..1f) Screenshot_2024-01-08-07-56-11-390_com.android.chrome.jpg

mahozad commented 5 months ago

Oh. That range!

OK. This is a valid feature request and the component does not support it yet.

Before/unless that is implemented, you can normalize your values to the 0f..1f range like below:

// Use remember {} and so on if needed...
val myMusicToatlLengthInMilliseconds = 187_000 // OR probably you get it from your media player
val myMusicProgressInMilliseconds = 10_000 // OR probably you get it from your media player
val min = 0 // Minimum desired/allowed value
val max = myMusicToatlLengthInMilliseconds
val value = (myMusicProgressInMilliseconds - min).toFloat() / (max - min)
WavySlider(
    value = value,
    ...
)
BobbyESP commented 5 months ago

Yess, thank you mate! And also for such a fast response!

BobbyESP commented 5 months ago

Nice, thanks for the implementation!