Add sleep timer

This commit is contained in:
vfsfitvnm
2022-06-15 14:35:51 +02:00
parent 92963642a2
commit 3aeeb6c601
7 changed files with 567 additions and 38 deletions

View File

@@ -35,6 +35,7 @@ fun ChunkyButton(
@DrawableRes icon: Int? = null,
shape: Shape = RoundedCornerShape(16.dp),
colorFilter: ColorFilter = ColorFilter.tint(rippleColor),
isEnabled: Boolean = true,
onMore: (() -> Unit)? = null
) {
Row(
@@ -46,6 +47,7 @@ fun ChunkyButton(
.clickable(
indication = rememberRipple(bounded = true, color = rippleColor),
interactionSource = remember { MutableInteractionSource() },
enabled = isEnabled,
onClick = onClick
)
.padding(horizontal = 24.dp, vertical = 16.dp)

View File

@@ -0,0 +1,306 @@
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.detectVerticalDragGestures
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.runtime.*
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 kotlinx.coroutines.launch
import kotlin.math.absoluteValue
@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<Int>())
}
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<Float>(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 <T> ItemPager(
items: List<T>,
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 <T> ItemPager(
items: List<T>,
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 <T : Enum<T>> 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.declaringClass.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)
}
}
}