diff --git a/app/src/main/java/com/rada/ui/navigation/NavGraph.kt b/app/src/main/java/com/rada/ui/navigation/NavGraph.kt index 54976a9..8639975 100644 --- a/app/src/main/java/com/rada/ui/navigation/NavGraph.kt +++ b/app/src/main/java/com/rada/ui/navigation/NavGraph.kt @@ -29,7 +29,7 @@ import com.rada.ui.streams.StreamsScreen fun NavGraph(navController: NavHostController) { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route - val showNowPlayingBar = currentRoute != Screen.Player.route + val showNowPlayingBar = currentRoute != Screen.Player.route && currentRoute != Screen.Tabs.route val reduce = LocalReduceMotion.current Column(modifier = Modifier.fillMaxSize()) { diff --git a/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt b/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt index c7f0834..b5982b0 100644 --- a/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt +++ b/app/src/main/java/com/rada/ui/player/NowPlayingBar.kt @@ -8,6 +8,7 @@ import com.rada.ui.theme.metadataEnter import com.rada.ui.theme.metadataExit import com.rada.ui.theme.LocalReduceMotion import com.rada.ui.theme.barEnter +import com.rada.ui.theme.verticalDragToDismiss import com.rada.ui.theme.barExit import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -36,6 +37,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -62,7 +65,12 @@ fun NowPlayingBar( val isLocal by viewModel.isLocal.collectAsState() val streamMeta by viewModel.streamMeta.collectAsState() val localMedia by viewModel.localMedia.collectAsState() - val isVisible = stationName.isNotEmpty() + // Swipe-down (or quick drag-down) dismisses the bar until a *new* + // source starts playing — a floating thing should answer to gestures. + var dismissedFor by remember { mutableStateOf(null) } + val sourceKey = stationName + if (dismissedFor != null && dismissedFor != sourceKey) dismissedFor = null + val isVisible = stationName.isNotEmpty() && dismissedFor != sourceKey // Spotify-style two-line bar: track (or station) + artist (or station). val title: String @@ -93,6 +101,10 @@ fun NowPlayingBar( Column( modifier = Modifier .fillMaxWidth() + .verticalDragToDismiss( + thresholdFraction = 0.4f, + onDismiss = { dismissedFor = sourceKey }, + ) .padding(horizontal = 12.dp) .clip(RoundedCornerShape(SatsAmpGlass.rCard)) // glass: textSafe 90% overlay + 6% white + highlight border diff --git a/app/src/main/java/com/rada/ui/theme/Motion.kt b/app/src/main/java/com/rada/ui/theme/Motion.kt index eb7f622..709aec3 100644 --- a/app/src/main/java/com/rada/ui/theme/Motion.kt +++ b/app/src/main/java/com/rada/ui/theme/Motion.kt @@ -1,5 +1,6 @@ package com.rada.ui.theme +import androidx.compose.ui.unit.dp import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState @@ -8,6 +9,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.foundation.gestures.detectVerticalDragGestures +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.CubicBezierEasing @@ -142,3 +147,35 @@ fun Modifier.pressDip( ) this.graphicsLayer { scaleX = scale; scaleY = scale } } + +// ── Dismiss-by-drag ─────────────────────────────────────────────────────── +// A floating bar should answer to a downward drag: it follows the finger +// (translationY only) and dismisses past a threshold or on a downward fling; +// otherwise it springs back. Reduce Motion: no follow, threshold-only dismiss. + +fun Modifier.verticalDragToDismiss( + thresholdFraction: Float = 0.5f, + onDismiss: () -> Unit, +): Modifier = composed { + val reduce = LocalReduceMotion.current + var dragPx by remember { mutableFloatStateOf(0f) } + val density = androidx.compose.ui.platform.LocalDensity.current + val maxDrag = with(density) { 120.dp.toPx() } + this + .graphicsLayer { translationY = if (reduce) 0f else dragPx } + .pointerInput(Unit) { + detectVerticalDragGestures( + onDragEnd = { + if (dragPx > size.height * thresholdFraction) onDismiss() + dragPx = 0f + }, + onDragCancel = { dragPx = 0f }, + onVerticalDrag = { change, amount -> + if (amount > 0) { + change.consume() + dragPx = (dragPx + amount).coerceIn(0f, maxDrag) + } + }, + ) + } +}