make the mini player progress bar unrounded and tie expansion to the drag

This commit is contained in:
austinried
2026-10-04 00:29:54 +00:00
parent 5907ebcb5c
commit ad1bb1fc8e
2 changed files with 61 additions and 19 deletions
@@ -3,6 +3,7 @@ package com.subtracks.ui
import android.net.Uri import android.net.Uri
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@@ -19,10 +20,14 @@ import androidx.compose.foundation.layout.navigationBars
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -49,6 +54,7 @@ import com.subtracks.ui.theme.rememberArtworkColors
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.compose.koinInject import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@@ -100,8 +106,12 @@ private fun MainNavigation() {
val navController = rememberNavController() val navController = rememberNavController()
val playbackController = koinInject<PlaybackController>() val playbackController = koinInject<PlaybackController>()
val playback by playbackController.state.collectAsStateWithLifecycle() val playback by playbackController.state.collectAsStateWithLifecycle()
var showingNowPlaying by rememberSaveable { mutableStateOf(false) }
var showingQueue by rememberSaveable { mutableStateOf(false) } var showingQueue by rememberSaveable { mutableStateOf(false) }
var draggingExpand by remember { mutableStateOf(false) }
var dragFraction by remember { mutableFloatStateOf(0f) }
val expandProgress = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
val nowPlayingProgress = if (draggingExpand) dragFraction else expandProgress.value
LaunchedEffect(Unit) { playbackController.connect() } LaunchedEffect(Unit) { playbackController.connect() }
val playerVisible = playback.item != null val playerVisible = playback.item != null
@@ -221,24 +231,48 @@ private fun MainNavigation() {
state = playback, state = playback,
coverArt = miniArt, coverArt = miniArt,
artwork = rememberArtworkColors(miniArt), artwork = rememberArtworkColors(miniArt),
onExpand = { showingNowPlaying = true }, onExpand = {
draggingExpand = false
scope.launch { expandProgress.animateTo(1f) }
},
onPlayPause = playbackController::togglePlayPause, onPlayPause = playbackController::togglePlayPause,
onNext = playbackController::next, onNext = playbackController::next,
onExpandProgress = {
draggingExpand = true
dragFraction = it
},
onExpandCommit = {
draggingExpand = false
scope.launch {
expandProgress.snapTo(dragFraction)
expandProgress.animateTo(1f)
}
},
onExpandCancel = {
draggingExpand = false
scope.launch {
expandProgress.snapTo(dragFraction)
expandProgress.animateTo(0f)
}
},
) )
} }
} }
AnimatedVisibility( if (nowPlayingProgress > 0f) {
visible = showingNowPlaying, Box(
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(tween(NOW_PLAYING_DURATION_MS)), modifier =
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(tween(NOW_PLAYING_DURATION_MS)), Modifier
.fillMaxSize()
.graphicsLayer { translationY = (1f - nowPlayingProgress) * size.height },
) { ) {
NowPlayingRoute( NowPlayingRoute(
onBack = { showingNowPlaying = false }, onBack = { scope.launch { expandProgress.animateTo(0f) } },
onQueue = { showingQueue = true }, onQueue = { showingQueue = true },
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) )
} }
}
AnimatedVisibility( AnimatedVisibility(
visible = showingQueue, visible = showingQueue,
@@ -253,5 +287,7 @@ private fun MainNavigation() {
} }
BackHandler(enabled = showingQueue) { showingQueue = false } BackHandler(enabled = showingQueue) { showingQueue = false }
BackHandler(enabled = showingNowPlaying && !showingQueue) { showingNowPlaying = false } BackHandler(enabled = nowPlayingProgress > 0f && !showingQueue) {
scope.launch { expandProgress.animateTo(0f) }
}
} }
@@ -34,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -43,7 +44,7 @@ import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.ArtworkColors
import com.subtracks.ui.theme.ArtworkTheme import com.subtracks.ui.theme.ArtworkTheme
private val EXPAND_DRAG = 48.dp private val EXPAND_DRAG = 240.dp
@Composable @Composable
fun MiniPlayer( fun MiniPlayer(
@@ -53,6 +54,9 @@ fun MiniPlayer(
onExpand: () -> Unit, onExpand: () -> Unit,
onPlayPause: () -> Unit, onPlayPause: () -> Unit,
onNext: () -> Unit, onNext: () -> Unit,
onExpandProgress: (Float) -> Unit = {},
onExpandCommit: () -> Unit = {},
onExpandCancel: () -> Unit = {},
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val item = state.item ?: return val item = state.item ?: return
@@ -75,10 +79,11 @@ fun MiniPlayer(
state = state =
rememberDraggableState { delta -> rememberDraggableState { delta ->
dragUpPx = (dragUpPx - delta).coerceAtLeast(0f) dragUpPx = (dragUpPx - delta).coerceAtLeast(0f)
if (dragUpPx >= expandThresholdPx) { onExpandProgress((dragUpPx / expandThresholdPx).coerceIn(0f, 1f))
},
onDragStopped = {
if (dragUpPx >= expandThresholdPx / 2f) onExpandCommit() else onExpandCancel()
dragUpPx = 0f dragUpPx = 0f
onExpand()
}
}, },
).clickable( ).clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
@@ -89,9 +94,10 @@ fun MiniPlayer(
Column { Column {
LinearProgressIndicator( LinearProgressIndicator(
progress = { progress }, progress = { progress },
modifier = Modifier.fillMaxWidth().height(2.dp), modifier = Modifier.fillMaxWidth().height(4.dp),
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.24f), trackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.24f),
strokeCap = StrokeCap.Butt,
gapSize = 0.dp, gapSize = 0.dp,
drawStopIndicator = {}, drawStopIndicator = {},
) )