From 93a40f9d156c6b7245a07ffb8d7219df7d634b3a Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Mon, 29 Aug 2022 20:07:52 +0200 Subject: [PATCH] Tweak sleep timer dialog UI --- .../vfsfitvnm/vimusic/ui/components/Pager.kt | 318 ------------------ .../ui/components/themed/MediaItemMenu.kt | 89 ++--- 2 files changed, 50 insertions(+), 357 deletions(-) delete mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/Pager.kt diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/Pager.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/Pager.kt deleted file mode 100644 index 6577e20..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/Pager.kt +++ /dev/null @@ -1,318 +0,0 @@ -package it.vfsfitvnm.vimusic.ui.components - -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.calculateTargetValue -import androidx.compose.animation.splineBasedDecay -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.detectHorizontalDragGestures -import androidx.compose.foundation.gestures.detectVerticalDragGestures -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.graphics.GraphicsLayerScope -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.util.VelocityTracker -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.center -import androidx.compose.ui.util.lerp -import kotlin.math.absoluteValue -import kotlinx.coroutines.launch - -@Composable -fun Pager( - selectedIndex: Int, - onSelectedIndex: (Int) -> Unit, - modifier: Modifier = Modifier, - orientation: Orientation = Orientation.Horizontal, - alignment: Alignment = Alignment.Center, - transformer: PagerTransformer = PagerTransformer.Default, - content: @Composable () -> Unit -) { - val coroutineScope = rememberCoroutineScope() - - val velocityTracker = remember { - VelocityTracker() - } - - val state = remember { - Animatable(0f) - } - - var steps by remember { - mutableStateOf(emptyList()) - } - - Layout( - modifier = modifier - .clipToBounds() - .pointerInput(Unit) { - val function = when (orientation) { - Orientation.Vertical -> ::detectVerticalDragGestures - Orientation.Horizontal -> ::detectHorizontalDragGestures - } - - function( - {}, - { - val velocity = -velocityTracker.calculateVelocity().x - val initialTargetValue = splineBasedDecay(this).calculateTargetValue( - state.value, - velocity - ) - - velocityTracker.resetTracking() - - val (targetValue, newSelectedIndex) = run { - for (i in 1..steps.lastIndex) { - val current = steps[i] - val previous = steps[i - 1] - - val currentDelta = current - initialTargetValue - val previousDelta = initialTargetValue - previous - - return@run when { - currentDelta >= 0 && previousDelta > 0 -> if (currentDelta < previousDelta) { - current to i - } else { - previous to i - 1 - } - previousDelta <= 0 -> previous to i - 1 - else -> continue - } - } - - steps.last() to steps.lastIndex - } - - coroutineScope.launch { - state.animateTo( - targetValue = targetValue.toFloat(), - initialVelocity = velocity, - ) - } - - onSelectedIndex(newSelectedIndex) - }, - {}, - { change, dragAmount -> - coroutineScope.launch { - state.snapTo(state.value - dragAmount) - } - - velocityTracker.addPosition(change.uptimeMillis, change.position) - change.consume() - }, - ) - }, - content = content - ) { measurables, constraints -> - val childConstraints = constraints.copy(minWidth = 0, minHeight = 0) - val placeables = measurables.map { - it.measure(childConstraints) - } - - var acc = 0 - steps = placeables.map { - val dim = when (orientation) { - Orientation.Horizontal -> it.width - Orientation.Vertical -> it.height - } - val step = acc + dim / 2 - acc += dim - step - }.also { - if (steps.isEmpty()) { - coroutineScope.launch { - state.animateTo(it[selectedIndex].toFloat()) - } - } - } - - state.updateBounds( - lowerBound = steps.first().toFloat(), - upperBound = steps.last().toFloat() - ) - - val layoutDimension = IntSize( - width = if (constraints.minWidth > 0 || placeables.isEmpty()) { - constraints.minWidth - } else { - placeables.maxOf { - it.width - } - }, - height = if (constraints.minHeight > 0 || placeables.isEmpty()) { - constraints.minHeight - } else { - placeables.maxOf { - it.height - } - } - ) - - val center = when (orientation) { - Orientation.Horizontal -> layoutDimension.center.x - Orientation.Vertical -> layoutDimension.center.y - } - - layout( - width = layoutDimension.width, - height = layoutDimension.height - ) { - var position = center - state.value.toInt() - - for (placeable in placeables) { - val otherPosition = alignment.align( - size = IntSize( - width = placeable.width, - height = placeable.height - ), - space = layoutDimension, - layoutDirection = layoutDirection - ).let { - when (orientation) { - Orientation.Horizontal -> it.y - Orientation.Vertical -> it.x - } - } - - val placeablePosition = when (orientation) { - Orientation.Horizontal -> IntOffset(position, otherPosition) - Orientation.Vertical -> IntOffset(otherPosition, position) - } - - placeable.placeWithLayer(position = placeablePosition) { - with(transformer) { - val size = when (orientation) { - Orientation.Horizontal -> placeable.width - Orientation.Vertical -> placeable.height - }.toFloat() - val offset = (center - (position + size / 2)).absoluteValue / size - apply(distance = offset) - } - } - - position += when (orientation) { - Orientation.Horizontal -> placeable.width - Orientation.Vertical -> placeable.height - } - } - } - } -} - -// Cannot inline: https://issuetracker.google.com/issues/204897513 -@Composable -fun ItemPager( - items: List, - selectedIndex: Int, - onSelectedIndex: (Int) -> Unit, - modifier: Modifier = Modifier, - orientation: Orientation = Orientation.Horizontal, - alignment: Alignment = Alignment.Center, - transformer: PagerTransformer = PagerTransformer.Default, - content: @Composable (item: T) -> Unit -) { - Pager( - modifier = modifier, - selectedIndex = selectedIndex, - onSelectedIndex = onSelectedIndex, - orientation = orientation, - alignment = alignment, - transformer = transformer, - ) { - for (item in items) { - content(item) - } - } -} - -// Cannot inline: https://issuetracker.google.com/issues/204897513 -@Composable -fun ItemPager( - items: List, - selectedValue: T, - onSelectedValue: (T) -> Unit, - modifier: Modifier = Modifier, - orientation: Orientation = Orientation.Horizontal, - alignment: Alignment = Alignment.Center, - transformer: PagerTransformer = PagerTransformer.Default, - content: @Composable (item: T) -> Unit -) { - Pager( - modifier = modifier, - selectedIndex = items.indexOf(selectedValue).coerceAtLeast(0), - onSelectedIndex = { - onSelectedValue(items[it]) - }, - orientation = orientation, - alignment = alignment, - transformer = transformer, - ) { - for (item in items) { - content(item) - } - } -} - -// Cannot inline: https://issuetracker.google.com/issues/204897513 -@Composable -fun > EnumPager( - value: T, - onSelectedValue: (T) -> Unit, - modifier: Modifier = Modifier, - orientation: Orientation = Orientation.Horizontal, - alignment: Alignment = Alignment.Center, - transformer: PagerTransformer = PagerTransformer.Default, - content: @Composable (item: T) -> Unit -) { - val items = remember { - value.declaringJavaClass.enumConstants!! - } - - Pager( - modifier = modifier, - selectedIndex = value.ordinal, - onSelectedIndex = { - onSelectedValue(items[it]) - }, - orientation = orientation, - alignment = alignment, - transformer = transformer, - ) { - for (item in items) { - content(item) - } - } -} - -@Immutable -fun interface PagerTransformer { - fun GraphicsLayerScope.apply(distance: Float) - - companion object { - @Stable - val Empty = PagerTransformer {} - - @Stable - val Default = PagerTransformer { - val value = 1f - it.coerceIn(0f, 1f) - lerp(start = 0.85f, stop = 1f, fraction = value).also { scale -> - scaleX = scale - scaleY = scale - } - - alpha = lerp(start = 0.5f, stop = 1f, fraction = value) - } - } -} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt index 92aa38e..3c80429 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt @@ -4,14 +4,17 @@ import android.text.format.DateUtils import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.with -import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable @@ -23,6 +26,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext @@ -42,7 +47,6 @@ import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.transaction import it.vfsfitvnm.vimusic.ui.components.ChunkyButton import it.vfsfitvnm.vimusic.ui.components.LocalMenuState -import it.vfsfitvnm.vimusic.ui.components.Pager import it.vfsfitvnm.vimusic.ui.screens.albumRoute import it.vfsfitvnm.vimusic.ui.screens.artistRoute import it.vfsfitvnm.vimusic.ui.screens.viewPlaylistsRoute @@ -465,12 +469,8 @@ fun MediaItemMenu( isShowingSleepTimerDialog = false } ) { - var hours by remember { - mutableStateOf(0) - } - - var minutes by remember { - mutableStateOf(0) + var amount by remember { + mutableStateOf(1) } BasicText( @@ -482,43 +482,54 @@ fun MediaItemMenu( Row( verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy( + space = 16.dp, + alignment = Alignment.CenterHorizontally + ), modifier = Modifier .padding(vertical = 16.dp) ) { - Pager( - selectedIndex = hours, - onSelectedIndex = { - hours = it - }, - orientation = Orientation.Vertical, + Box( + contentAlignment = Alignment.Center, modifier = Modifier - .padding(horizontal = 8.dp) - .height(92.dp) + .alpha(if (amount <= 1) 0.5f else 1f) + .clip(CircleShape) + .clickable(enabled = amount > 1) { amount-- } + .size(48.dp) + .background(colorPalette.background0) ) { - repeat(12) { - BasicText( - text = "$it h", - style = typography.xs.semiBold - ) - } + BasicText( + text = "-", + style = typography.xs.semiBold + ) } - Pager( - selectedIndex = minutes, - onSelectedIndex = { - minutes = it - }, - orientation = Orientation.Vertical, + Box(contentAlignment = Alignment.Center) { + BasicText( + text = "88h 88m", + style = typography.s.semiBold, + modifier = Modifier + .alpha(0f) + ) + BasicText( + text = "${amount / 6}h ${(amount % 6) * 10}m", + style = typography.s.semiBold + ) + } + + Box( + contentAlignment = Alignment.Center, modifier = Modifier - .padding(horizontal = 8.dp) - .height(72.dp) + .alpha(if (amount >= 60) 0.5f else 1f) + .clip(CircleShape) + .clickable(enabled = amount < 60) { amount++ } + .size(48.dp) + .background(colorPalette.background0) ) { - repeat(4) { - BasicText( - text = "${it * 15} m", - style = typography.xs.semiBold - ) - } + BasicText( + text = "+", + style = typography.xs.semiBold + ) } } @@ -540,9 +551,9 @@ fun MediaItemMenu( text = "Set", textStyle = typography.xs.semiBold.color(colorPalette.onAccent), shape = RoundedCornerShape(36.dp), - isEnabled = hours > 0 || minutes > 0, + isEnabled = amount > 0, onClick = { - binder?.startSleepTimer((hours * 60 + minutes * 15) * 60 * 1000L) + binder?.startSleepTimer(amount * 10 * 60 * 1000L) isShowingSleepTimerDialog = false } )