Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash #7

Open
T8RIN opened this issue Jun 1, 2023 · 18 comments
Open

Crash #7

T8RIN opened this issue Jun 1, 2023 · 18 comments

Comments

@T8RIN
Copy link
Contributor

T8RIN commented Jun 1, 2023

Screenshot_20230601-200121_Image Resizer.png

I have no idea, why this happens, maybe that's because i use newer compose version...

@galaxygoldfish
Copy link
Owner

What compose version are you using in that project?

@T8RIN
Copy link
Contributor Author

T8RIN commented Jun 1, 2023

Latest, 1.5.0-beta01 👀

@galaxygoldfish
Copy link
Owner

It looks like in the waveslider library we use the compose BOM package so you could try that. It was automatically created by Android Studio as version 2023.05.01 (see libs.versions.toml)

@T8RIN
Copy link
Contributor Author

T8RIN commented Jun 2, 2023

The thing that worked, is that I copied the module to the project, and bumped compose to 1.5.0-beta01, and had to downgrade minSdk to 21, have no idea, why 26 used here, cause compose needs 21 only
:(

@galaxygoldfish
Copy link
Owner

Oh so it doesn't crash when minSdk is 21?

@T8RIN
Copy link
Contributor Author

T8RIN commented Jun 2, 2023

No, I meant that there is no point in putting 26 if everything works with 21 👀

@T8RIN
Copy link
Contributor Author

T8RIN commented Jun 2, 2023

And with the latest compose version it also doesn't crash

@galaxygoldfish
Copy link
Owner

Merged PR updating mikSdk, and I found that the latest compose BOM is what is currently in the project so I changed it to individual dependencies, however while the compose version and material versions are updated to the latest one, it breaks the current implementation of the steps because they changed some values to be internal only that we were using to calculate the position of each tick :(

@T8RIN
Copy link
Contributor Author

T8RIN commented Jun 3, 2023

Yes! But i found solution, how to fix it 👀

@T8RIN
Copy link
Contributor Author

T8RIN commented Jun 3, 2023

Just copy private function, which creates step fractions, and that's it :)

@YounesBouhouche
Copy link
Contributor

YounesBouhouche commented Sep 22, 2023

I found the solution !
In the update of androidx.compose.material3:material3-*:1.2.0-alpha08, they made sliderPositions depreacted and sliderPositions.tickFractions internal, so I copied the WaveSlider function then I replaced this :

sliderPositions.tickFractions.groupBy {
                    it > sliderPositions.activeRange.endInclusive ||
                            it < sliderPositions.activeRange.start
                }

with this :

stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }

Changes : Slider.kt · Gerrit Code Review

I found the stepsToTickFractions function : here

Here is the new file, just create new kt file "WaveSlider.kt" and paste this code :

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import com.galaxygoldfish.waveslider.LocalThumbColor
import com.galaxygoldfish.waveslider.PillThumb
import com.galaxygoldfish.waveslider.WaveAnimationOptions
import com.galaxygoldfish.waveslider.WaveOptions
import com.galaxygoldfish.waveslider.WaveSliderColors
import com.galaxygoldfish.waveslider.WaveSliderDefaults
import kotlin.math.sin

private fun stepsToTickFractions(steps: Int): FloatArray {
    return if (steps == 0) floatArrayOf() else FloatArray(steps + 2) { it.toFloat() / (steps + 1) }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WaveSlider(
    value: Float,
    onValueChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
    onValueChangeFinished: (Float) -> Unit = {},
    colors: WaveSliderColors = WaveSliderDefaults.colors(),
    animationOptions: WaveAnimationOptions = WaveSliderDefaults.animationOptions(),
    waveOptions: WaveOptions = WaveSliderDefaults.waveOptions(),
    enabled: Boolean = true,
    thumb: @Composable () -> Unit = { PillThumb() },
    steps: Int = 0
) {
    val amplitude = waveOptions.amplitude
    val frequency = waveOptions.frequency

    var isDragging by remember { mutableStateOf(false) }
    val interactionSource = remember { MutableInteractionSource() }
    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect { interaction ->
            when (interaction) {
                is DragInteraction.Start -> {
                    isDragging = true
                }

                is DragInteraction.Stop -> {
                    isDragging = false
                }
            }
        }
    }
    val infiniteTransition = rememberInfiniteTransition(label = "Wave infinite transition")
    val phaseShiftFloat = infiniteTransition.animateFloat(
        label = "Wave phase shift",
        initialValue = 0F,
        targetValue = 90f,
        animationSpec = infiniteRepeatable(
            animation = keyframes {
                durationMillis = 1000
            },
            repeatMode = RepeatMode.Restart
        )
    ).value
    Slider(
        steps = steps,
        value = value,
        onValueChangeFinished = {
            onValueChangeFinished(value)
        },
        onValueChange = onValueChange,
        interactionSource = interactionSource,
        enabled = enabled,
        modifier = modifier,
        thumb = {
            CompositionLocalProvider(
                LocalThumbColor provides animateColorAsState(
                    targetValue = if (enabled) {
                        colors.thumbColor
                    } else {
                        colors.disabledThumbColor
                    },
                    label = "Thumb color"
                ).value
            ) {
                thumb()
            }
        },
        track = { sliderState ->
            val animatedAmplitude = animateFloatAsState(
                targetValue = if (animationOptions.flatlineOnDrag) {
                    if (animationOptions.reverseFlatline) {
                        if (isDragging) amplitude else 0F
                    } else {
                        if (isDragging) 0F else amplitude
                    }
                } else {
                    amplitude
                },
                label = "Wave amplitude"
            ).value
            Canvas(modifier = Modifier.fillMaxWidth()) {
                val centerY = size.height / 2f
                val startX = 0F
                val endX = size.width * value
                val path = Path()
                for (x in startX.toInt()..endX.toInt()) {
                    var modifiedX = x.toFloat()
                    if (animationOptions.animateWave && enabled) {
                        if (animationOptions.reverseDirection) {
                            modifiedX += phaseShiftFloat
                        } else {
                            modifiedX -= phaseShiftFloat
                        }
                    }
                    val y = (animatedAmplitude * sin(frequency * modifiedX))
                    path.moveTo(x.toFloat(), centerY - y)
                    path.lineTo(x.toFloat(), centerY - y)
                }
                drawPath(
                    path = path,
                    color = if (enabled) {
                        colors.activeTrackColor
                    } else {
                        colors.disabledActiveTrackColor
                    },
                    style = Stroke(width = 8f, cap = StrokeCap.Round)
                )
                drawLine(
                    color = if (enabled) {
                        colors.inactiveTrackColor
                    } else {
                        colors.disabledInactiveTrackColor
                    },
                    strokeWidth = 8F,
                    cap = StrokeCap.Round,
                    start = Offset(endX + 1, centerY),
                    end = Offset(size.width, centerY)
                )
                stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }.forEach { (outsideFraction, list) ->
                    drawPoints(
                        points = list.map {
                            Offset(
                                x = lerp(
                                    start = Offset(startX, centerY),
                                    stop = Offset(size.width, centerY),
                                    fraction = it
                                ).x,
                                y = center.y
                            )
                        },
                        pointMode = PointMode.Points,
                        color = if (outsideFraction) {
                            if (enabled) {
                                colors.inactiveTickColor
                            } else {
                                colors.disabledInactiveTickColor
                            }
                        } else {
                            if (animatedAmplitude == 0F) {
                                if (enabled) {
                                    colors.activeTickColor
                                } else {
                                    colors.disabledActiveTickColor
                                }
                            } else {
                                Color.Transparent
                            }
                        },
                        strokeWidth = 10F,
                        cap = StrokeCap.Round
                    )
                }
            }
        }
    )
}

I hope the developer implement it as soon as possible

@T8RIN
Copy link
Contributor Author

T8RIN commented Sep 22, 2023

I found the solution !
In the update of androidx.compose.material3:material3-*:1.2.0-alpha08, they made sliderPositions depreacted and sliderPositions.tickFractions internal, so I copied the WaveSlider function then I replaced this :

sliderPositions.tickFractions.groupBy {
                    it > sliderPositions.activeRange.endInclusive ||
                            it < sliderPositions.activeRange.start
                }

with this :

stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }

Changes : Slider.kt · Gerrit Code Review

I found the stepsToTickFractions function : here

Here is the new file, just create new kt file "WaveSlider.kt" and paste this code :

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import com.galaxygoldfish.waveslider.LocalThumbColor
import com.galaxygoldfish.waveslider.PillThumb
import com.galaxygoldfish.waveslider.WaveAnimationOptions
import com.galaxygoldfish.waveslider.WaveOptions
import com.galaxygoldfish.waveslider.WaveSliderColors
import com.galaxygoldfish.waveslider.WaveSliderDefaults
import kotlin.math.sin

private fun stepsToTickFractions(steps: Int): FloatArray {
    return if (steps == 0) floatArrayOf() else FloatArray(steps + 2) { it.toFloat() / (steps + 1) }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WaveSlider(
    value: Float,
    onValueChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
    onValueChangeFinished: (Float) -> Unit = {},
    colors: WaveSliderColors = WaveSliderDefaults.colors(),
    animationOptions: WaveAnimationOptions = WaveSliderDefaults.animationOptions(),
    waveOptions: WaveOptions = WaveSliderDefaults.waveOptions(),
    enabled: Boolean = true,
    thumb: @Composable () -> Unit = { PillThumb() },
    steps: Int = 0
) {
    val amplitude = waveOptions.amplitude
    val frequency = waveOptions.frequency

    var isDragging by remember { mutableStateOf(false) }
    val interactionSource = remember { MutableInteractionSource() }
    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect { interaction ->
            when (interaction) {
                is DragInteraction.Start -> {
                    isDragging = true
                }

                is DragInteraction.Stop -> {
                    isDragging = false
                }
            }
        }
    }
    val infiniteTransition = rememberInfiniteTransition(label = "Wave infinite transition")
    val phaseShiftFloat = infiniteTransition.animateFloat(
        label = "Wave phase shift",
        initialValue = 0F,
        targetValue = 90f,
        animationSpec = infiniteRepeatable(
            animation = keyframes {
                durationMillis = 1000
            },
            repeatMode = RepeatMode.Restart
        )
    ).value
    Slider(
        steps = steps,
        value = value,
        onValueChangeFinished = {
            onValueChangeFinished(value)
        },
        onValueChange = onValueChange,
        interactionSource = interactionSource,
        enabled = enabled,
        modifier = modifier,
        thumb = {
            CompositionLocalProvider(
                LocalThumbColor provides animateColorAsState(
                    targetValue = if (enabled) {
                        colors.thumbColor
                    } else {
                        colors.disabledThumbColor
                    },
                    label = "Thumb color"
                ).value
            ) {
                thumb()
            }
        },
        track = { sliderState ->
            val animatedAmplitude = animateFloatAsState(
                targetValue = if (animationOptions.flatlineOnDrag) {
                    if (animationOptions.reverseFlatline) {
                        if (isDragging) amplitude else 0F
                    } else {
                        if (isDragging) 0F else amplitude
                    }
                } else {
                    amplitude
                },
                label = "Wave amplitude"
            ).value
            Canvas(modifier = Modifier.fillMaxWidth()) {
                val centerY = size.height / 2f
                val startX = 0F
                val endX = size.width * value
                val path = Path()
                for (x in startX.toInt()..endX.toInt()) {
                    var modifiedX = x.toFloat()
                    if (animationOptions.animateWave && enabled) {
                        if (animationOptions.reverseDirection) {
                            modifiedX += phaseShiftFloat
                        } else {
                            modifiedX -= phaseShiftFloat
                        }
                    }
                    val y = (animatedAmplitude * sin(frequency * modifiedX))
                    path.moveTo(x.toFloat(), centerY - y)
                    path.lineTo(x.toFloat(), centerY - y)
                }
                drawPath(
                    path = path,
                    color = if (enabled) {
                        colors.activeTrackColor
                    } else {
                        colors.disabledActiveTrackColor
                    },
                    style = Stroke(width = 8f, cap = StrokeCap.Round)
                )
                drawLine(
                    color = if (enabled) {
                        colors.inactiveTrackColor
                    } else {
                        colors.disabledInactiveTrackColor
                    },
                    strokeWidth = 8F,
                    cap = StrokeCap.Round,
                    start = Offset(endX + 1, centerY),
                    end = Offset(size.width, centerY)
                )
                stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }.forEach { (outsideFraction, list) ->
                    drawPoints(
                        points = list.map {
                            Offset(
                                x = lerp(
                                    start = Offset(startX, centerY),
                                    stop = Offset(size.width, centerY),
                                    fraction = it
                                ).x,
                                y = center.y
                            )
                        },
                        pointMode = PointMode.Points,
                        color = if (outsideFraction) {
                            if (enabled) {
                                colors.inactiveTickColor
                            } else {
                                colors.disabledInactiveTickColor
                            }
                        } else {
                            if (animatedAmplitude == 0F) {
                                if (enabled) {
                                    colors.activeTickColor
                                } else {
                                    colors.disabledActiveTickColor
                                }
                            } else {
                                Color.Transparent
                            }
                        },
                        strokeWidth = 10F,
                        cap = StrokeCap.Round
                    )
                }
            }
        }
    )
}

I hope the developer implement it as soon as possible

Wow, thanks for that, i'll be waiting too, maybe you can open PR?

@YounesBouhouche
Copy link
Contributor

YounesBouhouche commented Sep 22, 2023

I found the solution !
In the update of androidx.compose.material3:material3-*:1.2.0-alpha08, they made sliderPositions depreacted and sliderPositions.tickFractions internal, so I copied the WaveSlider function then I replaced this :

sliderPositions.tickFractions.groupBy {
                    it > sliderPositions.activeRange.endInclusive ||
                            it < sliderPositions.activeRange.start
                }

with this :

stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }

Changes : Slider.kt · Gerrit Code Review
I found the stepsToTickFractions function : here
Here is the new file, just create new kt file "WaveSlider.kt" and paste this code :

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import com.galaxygoldfish.waveslider.LocalThumbColor
import com.galaxygoldfish.waveslider.PillThumb
import com.galaxygoldfish.waveslider.WaveAnimationOptions
import com.galaxygoldfish.waveslider.WaveOptions
import com.galaxygoldfish.waveslider.WaveSliderColors
import com.galaxygoldfish.waveslider.WaveSliderDefaults
import kotlin.math.sin

private fun stepsToTickFractions(steps: Int): FloatArray {
    return if (steps == 0) floatArrayOf() else FloatArray(steps + 2) { it.toFloat() / (steps + 1) }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WaveSlider(
    value: Float,
    onValueChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
    onValueChangeFinished: (Float) -> Unit = {},
    colors: WaveSliderColors = WaveSliderDefaults.colors(),
    animationOptions: WaveAnimationOptions = WaveSliderDefaults.animationOptions(),
    waveOptions: WaveOptions = WaveSliderDefaults.waveOptions(),
    enabled: Boolean = true,
    thumb: @Composable () -> Unit = { PillThumb() },
    steps: Int = 0
) {
    val amplitude = waveOptions.amplitude
    val frequency = waveOptions.frequency

    var isDragging by remember { mutableStateOf(false) }
    val interactionSource = remember { MutableInteractionSource() }
    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect { interaction ->
            when (interaction) {
                is DragInteraction.Start -> {
                    isDragging = true
                }

                is DragInteraction.Stop -> {
                    isDragging = false
                }
            }
        }
    }
    val infiniteTransition = rememberInfiniteTransition(label = "Wave infinite transition")
    val phaseShiftFloat = infiniteTransition.animateFloat(
        label = "Wave phase shift",
        initialValue = 0F,
        targetValue = 90f,
        animationSpec = infiniteRepeatable(
            animation = keyframes {
                durationMillis = 1000
            },
            repeatMode = RepeatMode.Restart
        )
    ).value
    Slider(
        steps = steps,
        value = value,
        onValueChangeFinished = {
            onValueChangeFinished(value)
        },
        onValueChange = onValueChange,
        interactionSource = interactionSource,
        enabled = enabled,
        modifier = modifier,
        thumb = {
            CompositionLocalProvider(
                LocalThumbColor provides animateColorAsState(
                    targetValue = if (enabled) {
                        colors.thumbColor
                    } else {
                        colors.disabledThumbColor
                    },
                    label = "Thumb color"
                ).value
            ) {
                thumb()
            }
        },
        track = { sliderState ->
            val animatedAmplitude = animateFloatAsState(
                targetValue = if (animationOptions.flatlineOnDrag) {
                    if (animationOptions.reverseFlatline) {
                        if (isDragging) amplitude else 0F
                    } else {
                        if (isDragging) 0F else amplitude
                    }
                } else {
                    amplitude
                },
                label = "Wave amplitude"
            ).value
            Canvas(modifier = Modifier.fillMaxWidth()) {
                val centerY = size.height / 2f
                val startX = 0F
                val endX = size.width * value
                val path = Path()
                for (x in startX.toInt()..endX.toInt()) {
                    var modifiedX = x.toFloat()
                    if (animationOptions.animateWave && enabled) {
                        if (animationOptions.reverseDirection) {
                            modifiedX += phaseShiftFloat
                        } else {
                            modifiedX -= phaseShiftFloat
                        }
                    }
                    val y = (animatedAmplitude * sin(frequency * modifiedX))
                    path.moveTo(x.toFloat(), centerY - y)
                    path.lineTo(x.toFloat(), centerY - y)
                }
                drawPath(
                    path = path,
                    color = if (enabled) {
                        colors.activeTrackColor
                    } else {
                        colors.disabledActiveTrackColor
                    },
                    style = Stroke(width = 8f, cap = StrokeCap.Round)
                )
                drawLine(
                    color = if (enabled) {
                        colors.inactiveTrackColor
                    } else {
                        colors.disabledInactiveTrackColor
                    },
                    strokeWidth = 8F,
                    cap = StrokeCap.Round,
                    start = Offset(endX + 1, centerY),
                    end = Offset(size.width, centerY)
                )
                stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }.forEach { (outsideFraction, list) ->
                    drawPoints(
                        points = list.map {
                            Offset(
                                x = lerp(
                                    start = Offset(startX, centerY),
                                    stop = Offset(size.width, centerY),
                                    fraction = it
                                ).x,
                                y = center.y
                            )
                        },
                        pointMode = PointMode.Points,
                        color = if (outsideFraction) {
                            if (enabled) {
                                colors.inactiveTickColor
                            } else {
                                colors.disabledInactiveTickColor
                            }
                        } else {
                            if (animatedAmplitude == 0F) {
                                if (enabled) {
                                    colors.activeTickColor
                                } else {
                                    colors.disabledActiveTickColor
                                }
                            } else {
                                Color.Transparent
                            }
                        },
                        strokeWidth = 10F,
                        cap = StrokeCap.Round
                    )
                }
            }
        }
    )
}

I hope the developer implement it as soon as possible

Wow, thanks for that, i'll be waiting too, maybe you can open PR?

Do you mean pull request ?
I know it's silly question but I'm new in Github

@T8RIN
Copy link
Contributor Author

T8RIN commented Sep 22, 2023

@YounesBouhouche did you noticed Layout Node not attached to owner exception after updating to 1.2.0-06 or 07-08 ?

@T8RIN
Copy link
Contributor Author

T8RIN commented Sep 22, 2023

I found the solution !
In the update of androidx.compose.material3:material3-*:1.2.0-alpha08, they made sliderPositions depreacted and sliderPositions.tickFractions internal, so I copied the WaveSlider function then I replaced this :

sliderPositions.tickFractions.groupBy {
                    it > sliderPositions.activeRange.endInclusive ||
                            it < sliderPositions.activeRange.start
                }

with this :

stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }

Changes : Slider.kt · Gerrit Code Review
I found the stepsToTickFractions function : here
Here is the new file, just create new kt file "WaveSlider.kt" and paste this code :

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import com.galaxygoldfish.waveslider.LocalThumbColor
import com.galaxygoldfish.waveslider.PillThumb
import com.galaxygoldfish.waveslider.WaveAnimationOptions
import com.galaxygoldfish.waveslider.WaveOptions
import com.galaxygoldfish.waveslider.WaveSliderColors
import com.galaxygoldfish.waveslider.WaveSliderDefaults
import kotlin.math.sin

private fun stepsToTickFractions(steps: Int): FloatArray {
    return if (steps == 0) floatArrayOf() else FloatArray(steps + 2) { it.toFloat() / (steps + 1) }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WaveSlider(
    value: Float,
    onValueChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
    onValueChangeFinished: (Float) -> Unit = {},
    colors: WaveSliderColors = WaveSliderDefaults.colors(),
    animationOptions: WaveAnimationOptions = WaveSliderDefaults.animationOptions(),
    waveOptions: WaveOptions = WaveSliderDefaults.waveOptions(),
    enabled: Boolean = true,
    thumb: @Composable () -> Unit = { PillThumb() },
    steps: Int = 0
) {
    val amplitude = waveOptions.amplitude
    val frequency = waveOptions.frequency

    var isDragging by remember { mutableStateOf(false) }
    val interactionSource = remember { MutableInteractionSource() }
    LaunchedEffect(interactionSource) {
        interactionSource.interactions.collect { interaction ->
            when (interaction) {
                is DragInteraction.Start -> {
                    isDragging = true
                }

                is DragInteraction.Stop -> {
                    isDragging = false
                }
            }
        }
    }
    val infiniteTransition = rememberInfiniteTransition(label = "Wave infinite transition")
    val phaseShiftFloat = infiniteTransition.animateFloat(
        label = "Wave phase shift",
        initialValue = 0F,
        targetValue = 90f,
        animationSpec = infiniteRepeatable(
            animation = keyframes {
                durationMillis = 1000
            },
            repeatMode = RepeatMode.Restart
        )
    ).value
    Slider(
        steps = steps,
        value = value,
        onValueChangeFinished = {
            onValueChangeFinished(value)
        },
        onValueChange = onValueChange,
        interactionSource = interactionSource,
        enabled = enabled,
        modifier = modifier,
        thumb = {
            CompositionLocalProvider(
                LocalThumbColor provides animateColorAsState(
                    targetValue = if (enabled) {
                        colors.thumbColor
                    } else {
                        colors.disabledThumbColor
                    },
                    label = "Thumb color"
                ).value
            ) {
                thumb()
            }
        },
        track = { sliderState ->
            val animatedAmplitude = animateFloatAsState(
                targetValue = if (animationOptions.flatlineOnDrag) {
                    if (animationOptions.reverseFlatline) {
                        if (isDragging) amplitude else 0F
                    } else {
                        if (isDragging) 0F else amplitude
                    }
                } else {
                    amplitude
                },
                label = "Wave amplitude"
            ).value
            Canvas(modifier = Modifier.fillMaxWidth()) {
                val centerY = size.height / 2f
                val startX = 0F
                val endX = size.width * value
                val path = Path()
                for (x in startX.toInt()..endX.toInt()) {
                    var modifiedX = x.toFloat()
                    if (animationOptions.animateWave && enabled) {
                        if (animationOptions.reverseDirection) {
                            modifiedX += phaseShiftFloat
                        } else {
                            modifiedX -= phaseShiftFloat
                        }
                    }
                    val y = (animatedAmplitude * sin(frequency * modifiedX))
                    path.moveTo(x.toFloat(), centerY - y)
                    path.lineTo(x.toFloat(), centerY - y)
                }
                drawPath(
                    path = path,
                    color = if (enabled) {
                        colors.activeTrackColor
                    } else {
                        colors.disabledActiveTrackColor
                    },
                    style = Stroke(width = 8f, cap = StrokeCap.Round)
                )
                drawLine(
                    color = if (enabled) {
                        colors.inactiveTrackColor
                    } else {
                        colors.disabledInactiveTrackColor
                    },
                    strokeWidth = 8F,
                    cap = StrokeCap.Round,
                    start = Offset(endX + 1, centerY),
                    end = Offset(size.width, centerY)
                )
                stepsToTickFractions(sliderState.steps).groupBy {
                    it > sliderState.valueRange.endInclusive ||
                            it < sliderState.valueRange.start
                }.forEach { (outsideFraction, list) ->
                    drawPoints(
                        points = list.map {
                            Offset(
                                x = lerp(
                                    start = Offset(startX, centerY),
                                    stop = Offset(size.width, centerY),
                                    fraction = it
                                ).x,
                                y = center.y
                            )
                        },
                        pointMode = PointMode.Points,
                        color = if (outsideFraction) {
                            if (enabled) {
                                colors.inactiveTickColor
                            } else {
                                colors.disabledInactiveTickColor
                            }
                        } else {
                            if (animatedAmplitude == 0F) {
                                if (enabled) {
                                    colors.activeTickColor
                                } else {
                                    colors.disabledActiveTickColor
                                }
                            } else {
                                Color.Transparent
                            }
                        },
                        strokeWidth = 10F,
                        cap = StrokeCap.Round
                    )
                }
            }
        }
    )
}

I hope the developer implement it as soon as possible

Wow, thanks for that, i'll be waiting too, maybe you can open PR?

Me too, but what's PR? how to open it?

You need to fork the project and then apply your changes in your fork, and then open pull request on GitHub

@YounesBouhouche
Copy link
Contributor

@YounesBouhouche did you noticed Layout Node not attached to owner exception after updating to 1.2.0-06 or 07-08 ?

No I didn't

@T8RIN
Copy link
Contributor Author

T8RIN commented Sep 22, 2023

@YounesBouhouche did you noticed Layout Node not attached to owner exception after updating to 1.2.0-06 or 07-08 ?

No I didn't

:(

@galaxygoldfish
Copy link
Owner

galaxygoldfish commented Oct 13, 2024

Hi all, got back to maintaining this project and I have updated WaveSlider.kt with the fix in this commit [cbab52b]

@T8RIN @YounesBouhouche Does this address the issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants