From 76937eacf9c25cf3d86ec03f98ec080530d47430 Mon Sep 17 00:00:00 2001 From: RLD-JL <106017010+RLD-JL@users.noreply.github.com> Date: Wed, 9 Oct 2024 22:00:23 +0300 Subject: [PATCH] quick fix for float error --- androidApp/build.gradle.kts | 4 +- .../playbar/PlayerBarSheetContent.kt | 2 +- .../components/PlayBarActionsMaximized.kt | 30 +- .../playbar/components/PlayerBottomBar.kt | 23 +- .../android/ui/extensions/ModifiedSlider.kt | 445 ------------------ .../ui/settingsscreen/SettingsScreen.kt | 10 +- settings.gradle.kts | 2 +- shared/build.gradle.kts | 2 +- 8 files changed, 44 insertions(+), 474 deletions(-) delete mode 100644 androidApp/src/main/java/com/rld/justlisten/android/ui/extensions/ModifiedSlider.kt diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 7496812..f6e61e7 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -45,8 +45,8 @@ android { applicationId = "com.rld.justlisten.android" minSdk = 21 targetSdk = 34 - versionCode = 24 - versionName = "1.0.8" + versionCode = 25 + versionName = "1.0.9" vectorDrawables { useSupportLibrary = true } diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/PlayerBarSheetContent.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/PlayerBarSheetContent.kt index 671dfe3..32bb7e8 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/PlayerBarSheetContent.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/PlayerBarSheetContent.kt @@ -97,7 +97,7 @@ fun PlayerBarSheetContent( ) } }, - sheetPeekHeight = (-1).dp + sheetPeekHeight = 0.dp ) { PlayerBottomBar( onCollapsedClicked = onCollapsedClicked, diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayBarActionsMaximized.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayBarActionsMaximized.kt index ee9c3b9..effbeb8 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayBarActionsMaximized.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayBarActionsMaximized.kt @@ -17,10 +17,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.rld.justlisten.android.R import com.rld.justlisten.android.exoplayer.MusicService import com.rld.justlisten.android.exoplayer.MusicServiceConnection -import com.rld.justlisten.android.ui.extensions.ModifiedSlider import com.rld.justlisten.android.ui.utils.offsetX import com.rld.justlisten.android.ui.utils.widthSize import kotlinx.coroutines.InternalCoroutinesApi @@ -44,20 +42,24 @@ fun PlayBarActionsMaximized( is PressInteraction.Press -> { musicServiceConnection.sliderClicked.value = true } + is PressInteraction.Release -> { musicServiceConnection.transportControls.seekTo(musicServiceConnection.songDuration.value) musicServiceConnection.sliderClicked.value = false musicServiceConnection.updateSong() } + is PressInteraction.Cancel -> {} is DragInteraction.Start -> { musicServiceConnection.sliderClicked.value = true } + is DragInteraction.Stop -> { musicServiceConnection.transportControls.seekTo(musicServiceConnection.songDuration.value) musicServiceConnection.sliderClicked.value = false musicServiceConnection.updateSong() } + is DragInteraction.Cancel -> {} } } @@ -65,7 +67,7 @@ fun PlayBarActionsMaximized( if (currentFraction == 1f) { - var sliderPosition by remember { mutableStateOf(0f) } + var sliderPosition by remember { mutableFloatStateOf(0f) } sliderPosition = musicServiceConnection.songDuration.value / MusicService.curSongDuration.toFloat() Column( @@ -79,15 +81,17 @@ fun PlayBarActionsMaximized( text = title, textAlign = TextAlign.Center ) - ModifiedSlider( - interactionSource = interactionSource, - modifier = Modifier - .offset(x = offsetX(currentFraction, maxWidth).dp) - .width(widthSize(currentFraction, maxWidth).dp), - value = sliderPosition, onValueChange = { - musicServiceConnection.songDuration.value = - (it * MusicService.curSongDuration).toLong() - }) + if (!sliderPosition.isNaN()) { + Slider( + interactionSource = interactionSource, + modifier = Modifier + .offset(x = offsetX(currentFraction, maxWidth).dp) + .width(widthSize(currentFraction, maxWidth).dp), + value = sliderPosition, onValueChange = { + musicServiceConnection.songDuration.value = + (it * MusicService.curSongDuration).toLong() + }) + } Row( Modifier.height(IntrinsicSize.Max) @@ -184,6 +188,7 @@ fun PlayBarActionsMaximized( painter = painterResource(id = com.google.android.exoplayer2.ui.R.drawable.exo_controls_repeat_off), contentDescription = null, ) + REPEAT_MODE_ONE -> Icon( modifier = Modifier .size(40.dp) @@ -196,6 +201,7 @@ fun PlayBarActionsMaximized( painter = painterResource(id = com.google.android.exoplayer2.ui.R.drawable.exo_controls_repeat_one), contentDescription = null, ) + REPEAT_MODE_ALL -> Icon( modifier = Modifier .size(40.dp) diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayerBottomBar.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayerBottomBar.kt index ae6966e..3348f3b 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayerBottomBar.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/bottombars/playbar/components/PlayerBottomBar.kt @@ -64,15 +64,20 @@ fun PlayerBottomBar( }, playBarMinimizedClicked = playBarMinimizedClicked ) - LinearProgressIndicator( - progress = musicServiceConnection.songDuration.value / curSongDuration.toFloat(), - Modifier - .fillMaxWidth() - .height(1.dp) - .graphicsLayer { - alpha = if (currentFraction > 0.001) 0f else 1f - } - ) + var progress by remember { mutableFloatStateOf(0f) } + progress = musicServiceConnection.songDuration.value / curSongDuration.toFloat() + if (!progress.isNaN()) { + LinearProgressIndicator( + progress = progress, + Modifier + .fillMaxWidth() + .height(1.dp) + .graphicsLayer { + alpha = if (currentFraction > 0.001) 0f else 1f + } + ) + } + PlayBarActionsMaximized( bottomPadding, currentFraction, diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/extensions/ModifiedSlider.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/extensions/ModifiedSlider.kt deleted file mode 100644 index ebc165b..0000000 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/extensions/ModifiedSlider.kt +++ /dev/null @@ -1,445 +0,0 @@ -package com.rld.justlisten.android.ui.extensions - -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.TweenSpec -import androidx.compose.foundation.* -import androidx.compose.foundation.gestures.* -import androidx.compose.foundation.interaction.DragInteraction -import androidx.compose.foundation.interaction.Interaction -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.PressInteraction -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.PointMode -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import com.rld.justlisten.android.ui.utils.lerp -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlin.math.abs - -/** - * Material Design slider. - * - * Sliders allow users to make selections from a range of values. - * - * Sliders reflect a range of values along a bar, from which users may select a single value. - * They are ideal for adjusting settings such as volume, brightness, or applying image filters. - * - * ![Sliders image](https://developer.android.com/images/reference/androidx/compose/material/sliders.png) - * - * Use continuous sliders to allow users to make meaningful selections that don’t - * require a specific value: - * - * @sample androidx.compose.material.samples.SliderSample - * - * You can allow the user to choose only between predefined set of values by specifying the amount - * of steps between min and max values: - * - * @sample androidx.compose.material.samples.StepsSliderSample - * - * @param value current value of the Slider. If outside of [valueRange] provided, value will be - * coerced to this range. - * @param onValueChange lambda in which value should be updated - * @param modifier modifiers for the Slider layout - * @param enabled whether or not component is enabled and can be interacted with or not - * @param valueRange range of values that Slider value can take. Passed [value] will be coerced to - * this range - * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed - * between across the whole value range. If 0, slider will behave as a continuous slider and allow - * to choose any value from the range specified. Must not be negative. - * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback - * shouldn't be used to update the slider value (use [onValueChange] for that), but rather to - * know when the user has completed selecting a new value by ending a drag or a click. - * @param interactionSource the [MutableInteractionSource] representing the stream of - * [Interaction]s for this Slider. You can create and pass in your own remembered - * [MutableInteractionSource] if you want to observe [Interaction]s and customize the - * appearance / behavior of this Slider in different [Interaction]s. - * @param colors [SliderColors] that will be used to determine the color of the Slider parts in - * different state. See [SliderDefaults.colors] to customize. - */ -@Composable -fun ModifiedSlider( - value: Float, - onValueChange: (Float) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - valueRange: ClosedFloatingPointRange = 0f..1f, - /*@IntRange(from = 0)*/ - steps: Int = 0, - onValueChangeFinished: (() -> Unit)? = null, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - colors: SliderColors = SliderDefaults.colors(), - thumbRadius: Dp = 5.dp -) { - require(steps >= 0) { "steps should be >= 0" } - val onValueChangeState = rememberUpdatedState(onValueChange) - val tickFractions = remember(steps) { - stepsToTickFractions(steps) - } - BoxWithConstraints( - modifier - .minimumTouchTargetSize() - .requiredSizeIn(minWidth = thumbRadius * 2, minHeight = thumbRadius * 2) - .sliderSemantics(value, tickFractions, enabled, onValueChange, valueRange, steps) - .focusable(enabled, interactionSource) - ) { - val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl - val maxPx = constraints.maxWidth.toFloat() - val minPx = 0f - - fun scaleToUserValue(offset: Float) = - scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive) - - fun scaleToOffset(userValue: Float) = - scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx) - - val scope = rememberCoroutineScope() - val rawOffset = remember { mutableStateOf(scaleToOffset(value)) } - val draggableState = remember(minPx, maxPx, valueRange) { - SliderDraggableState { - rawOffset.value = (rawOffset.value + it).coerceIn(minPx, maxPx) - onValueChangeState.value.invoke(scaleToUserValue(rawOffset.value)) - } - } - - CorrectValueSideEffect(::scaleToOffset, valueRange, rawOffset, value) - - val gestureEndAction = rememberUpdatedState<(Float) -> Unit> { velocity: Float -> - val current = rawOffset.value - val target = snapValueToTick(current, tickFractions, minPx, maxPx) - if (current != target) { - scope.launch { - animateToTarget(draggableState, current, target, velocity) - onValueChangeFinished?.invoke() - } - } else if (!draggableState.isDragging) { - // check ifDragging in case the change is still in progress (touch -> drag case) - onValueChangeFinished?.invoke() - } - } - - val press = Modifier.sliderPressModifier( - draggableState, interactionSource, maxPx, isRtl, rawOffset, gestureEndAction, enabled - ) - - val drag = Modifier.draggable( - orientation = Orientation.Horizontal, - reverseDirection = isRtl, - enabled = enabled, - interactionSource = interactionSource, - onDragStopped = { velocity -> gestureEndAction.value.invoke(velocity) }, - startDragImmediately = draggableState.isDragging, - state = draggableState - ) - - val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive) - val fraction = calcFraction(valueRange.start, valueRange.endInclusive, coerced) - SliderImpl( - enabled, - fraction, - tickFractions, - colors, - maxPx, - interactionSource, - modifier = press.then(drag) - ) - } -} - -private fun stepsToTickFractions(steps: Int): List { - return if (steps == 0) emptyList() else List(steps + 2) { it.toFloat() / (steps + 1) } -} - - - -// Scale x1 from a1..b1 range to a2..b2 range -private fun scale(a1: Float, b1: Float, x1: Float, a2: Float, b2: Float) = - lerp(a2, b2, calcFraction(a1, b1, x1)) - -// Calculate the 0..1 fraction that `pos` value represents between `a` and `b` -private fun calcFraction(a: Float, b: Float, pos: Float) = - (if (b - a == 0f) 0f else (pos - a) / (b - a)).coerceIn(0f, 1f) - -private fun snapValueToTick( - current: Float, - tickFractions: List, - minPx: Float, - maxPx: Float -): Float { - // target is a closest anchor to the `current`, if exists - return tickFractions - .minByOrNull { abs(lerp(minPx, maxPx, it) - current) } - ?.run { lerp(minPx, maxPx, this) } - ?: current -} - -private val SliderToTickAnimation = TweenSpec(durationMillis = 100) - -private suspend fun animateToTarget( - draggableState: DraggableState, - current: Float, - target: Float, - velocity: Float -) { - draggableState.drag { - var latestValue = current - Animatable(initialValue = current).animateTo(target, SliderToTickAnimation, velocity) { - dragBy(this.value - latestValue) - latestValue = this.value - } - } -} - -@Composable -private fun CorrectValueSideEffect( - scaleToOffset: (Float) -> Float, - valueRange: ClosedFloatingPointRange, - valueState: MutableState, - value: Float -) { - SideEffect { - val error = (valueRange.endInclusive - valueRange.start) / 1000 - val newOffset = scaleToOffset(value) - if (abs(newOffset - valueState.value) > error) - valueState.value = newOffset - } -} - - -private class SliderDraggableState( - val onDelta: (Float) -> Unit -) : DraggableState { - - var isDragging by mutableStateOf(false) - private set - - private val dragScope: DragScope = object : DragScope { - override fun dragBy(pixels: Float): Unit = onDelta(pixels) - } - - private val scrollMutex = MutatorMutex() - - override suspend fun drag( - dragPriority: MutatePriority, - block: suspend DragScope.() -> Unit - ): Unit = coroutineScope { - isDragging = true - scrollMutex.mutateWith(dragScope, dragPriority, block) - isDragging = false - } - - override fun dispatchRawDelta(delta: Float) { - return onDelta(delta) - } -} - -private fun Modifier.sliderPressModifier( - draggableState: DraggableState, - interactionSource: MutableInteractionSource, - maxPx: Float, - isRtl: Boolean, - rawOffset: State, - gestureEndAction: State<(Float) -> Unit>, - enabled: Boolean -): Modifier = - if (enabled) { - pointerInput(draggableState, interactionSource, maxPx, isRtl) { - detectTapGestures( - onPress = { pos -> - draggableState.drag(MutatePriority.UserInput) { - val to = if (isRtl) maxPx - pos.x else pos.x - dragBy(to - rawOffset.value) - } - val interaction = PressInteraction.Press(pos) - interactionSource.emit(interaction) - val finishInteraction = - try { - val success = tryAwaitRelease() - gestureEndAction.value.invoke(0f) - if (success) { - PressInteraction.Release(interaction) - } else { - PressInteraction.Cancel(interaction) - } - } catch (c: CancellationException) { - PressInteraction.Cancel(interaction) - } - interactionSource.emit(finishInteraction) - } - ) - } - } else { - this - } - - - -private val SliderHeight = 48.dp -private val SliderMinWidth = 144.dp - -private val DefaultSliderConstraints = - Modifier.widthIn(min = SliderMinWidth) - .heightIn(max = SliderHeight) - -internal val TrackHeight = 4.dp -internal val ThumbRadius = 5.dp - -@Composable -private fun SliderImpl( - enabled: Boolean, - positionFraction: Float, - tickFractions: List, - colors: SliderColors, - width: Float, - interactionSource: MutableInteractionSource, - modifier: Modifier -) { - Box(modifier.then(DefaultSliderConstraints)) { - val trackStrokeWidth: Float - val thumbPx: Float - val widthDp: Dp - with(LocalDensity.current) { - trackStrokeWidth = TrackHeight.toPx() - thumbPx = ThumbRadius.toPx() - widthDp = width.toDp() - } - - val thumbSize = ThumbRadius * 2 - val offset = (widthDp - thumbSize) * positionFraction - val center = Modifier.align(Alignment.CenterStart) - - Track( - center.fillMaxSize(), - colors, - enabled, - 0f, - positionFraction, - tickFractions, - thumbPx, - trackStrokeWidth - ) - SliderThumb(center, offset, interactionSource, colors, enabled, thumbSize) - } -} - -// Internal to be referred to in tests -private val ThumbRippleRadius = 14.dp -private val ThumbDefaultElevation = 1.dp -private val ThumbPressedElevation = 6.dp - -@Composable -private fun SliderThumb( - modifier: Modifier, - offset: Dp, - interactionSource: MutableInteractionSource, - colors: SliderColors, - enabled: Boolean, - thumbSize: Dp -) { - Box(modifier.padding(start = offset)) { - val interactions = remember { mutableStateListOf() } - LaunchedEffect(interactionSource) { - interactionSource.interactions.collect { interaction -> - when (interaction) { - is PressInteraction.Press -> interactions.add(interaction) - is PressInteraction.Release -> interactions.remove(interaction.press) - is PressInteraction.Cancel -> interactions.remove(interaction.press) - is DragInteraction.Start -> interactions.add(interaction) - is DragInteraction.Stop -> interactions.remove(interaction.start) - is DragInteraction.Cancel -> interactions.remove(interaction.start) - } - } - } - - val elevation = if (interactions.isNotEmpty()) { - ThumbPressedElevation - } else { - ThumbDefaultElevation - } - Spacer( - Modifier - .size(thumbSize, thumbSize) - .indication( - interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = ThumbRippleRadius) - ) - .hoverable(interactionSource = interactionSource) - .shadow(if (enabled) elevation else 0.dp, CircleShape, clip = false) - .background(colors.thumbColor(enabled).value, CircleShape) - ) - } -} - -@Composable -private fun Track( - modifier: Modifier, - colors: SliderColors, - enabled: Boolean, - positionFractionStart: Float, - positionFractionEnd: Float, - tickFractions: List, - thumbPx: Float, - trackStrokeWidth: Float -) { - val inactiveTrackColor = colors.trackColor(enabled, active = false) - val activeTrackColor = colors.trackColor(enabled, active = true) - val inactiveTickColor = colors.tickColor(enabled, active = false) - val activeTickColor = colors.tickColor(enabled, active = true) - Canvas(modifier) { - val isRtl = layoutDirection == LayoutDirection.Rtl - val sliderLeft = Offset(thumbPx, center.y) - val sliderRight = Offset(size.width - thumbPx, center.y) - val sliderStart = if (isRtl) sliderRight else sliderLeft - val sliderEnd = if (isRtl) sliderLeft else sliderRight - drawLine( - inactiveTrackColor.value, - sliderStart, - sliderEnd, - trackStrokeWidth, - StrokeCap.Round - ) - val sliderValueEnd = Offset( - sliderStart.x + (sliderEnd.x - sliderStart.x) * positionFractionEnd, - center.y - ) - - val sliderValueStart = Offset( - sliderStart.x + (sliderEnd.x - sliderStart.x) * positionFractionStart, - center.y - ) - - drawLine( - activeTrackColor.value, - sliderValueStart, - sliderValueEnd, - trackStrokeWidth, - StrokeCap.Round - ) - tickFractions.groupBy { it > positionFractionEnd }.forEach { (afterFraction, list) -> - drawPoints( - list.map { - Offset(androidx.compose.ui.geometry.lerp(sliderStart, sliderEnd, it).x, center.y) - }, - PointMode.Points, - (if (afterFraction) inactiveTickColor else activeTickColor).value, - trackStrokeWidth, - StrokeCap.Round - ) - } - } -} \ No newline at end of file diff --git a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt index 307d204..0ba286c 100644 --- a/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt +++ b/androidApp/src/main/java/com/rld/justlisten/android/ui/settingsscreen/SettingsScreen.kt @@ -94,10 +94,14 @@ fun SettingsScreen( } } } - Row(modifier = Modifier.fillMaxWidth().weight(1f, false), - horizontalArrangement = Arrangement.Center) + Row( + modifier = Modifier + .fillMaxWidth() + .weight(1f, false), + horizontalArrangement = Arrangement.Center + ) { - Text(text ="App version:1.0.8") + Text(text = "App version:1.0.9") } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1ef406b..33bc114 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,7 +27,7 @@ dependencyResolutionManagement { ) ) - version("composeVersion", "1.5.4") + version("composeVersion", "1.7.0") library("compose-ui-util", "androidx.compose.ui", "ui-util").versionRef("composeVersion") library("compose-ui", "androidx.compose.ui", "ui").versionRef("composeVersion") library("compose-ui-preview", "androidx.compose.ui", "ui-tooling-preview").versionRef("composeVersion") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index a9adeea..681b699 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -54,7 +54,7 @@ kotlin { val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-ios:${libs.versions.ktorVersion.get()}") - implementation("com.squareup.sqldelight:native-driver:1.5.3") + implementation("com.squareup.sqldelight:native-driver:2.0.2") } } val iosTest by getting