mirror of
https://github.com/FluxaMedia/fluxa.git
synced 2026-07-26 20:02:14 +00:00
Polish player sidebar open/close and unify speed menu
The sidebar previously entered with a fade plus a 44px nudge and disappeared instantly on close. It now slides the full distance from its edge (right for side sheets, bottom for bottom sheets) with the scrim fading in sync, and close requests play the exit animation before the panel is removed, including swipe-to-dismiss. Panel height changes between tabs are animated instead of snapping. The mobile speed menu drops its bordered chip grid in favor of the same checkmarked rows used by the audio, subtitle, and source lists.
This commit is contained in:
parent
86c169de48
commit
7f93efbd41
2 changed files with 40 additions and 73 deletions
|
|
@ -7,7 +7,6 @@ import com.fluxa.app.ui.catalog.FluxaIcons
|
|||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -205,42 +204,19 @@ fun UniversalSettingsSidebar(
|
|||
}
|
||||
else -> {
|
||||
val speeds = listOf(0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f)
|
||||
if (deviceType == DeviceType.Mobile) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
speeds.chunked(3).forEach { row ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
row.forEach { speed ->
|
||||
SpeedChip(
|
||||
label = formatSpeedLabel(speed),
|
||||
isSelected = playbackSpeed == speed,
|
||||
onClick = { onSpeedChange(speed) },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier.fillMaxWidth().heightIn(max = listMaxHeight)) {
|
||||
items(speeds, key = { it }) { speed ->
|
||||
TrackItem(
|
||||
title = formatSpeedLabel(speed),
|
||||
isSelected = playbackSpeed == speed,
|
||||
onClick = { onSpeedChange(speed) },
|
||||
subtitle = when {
|
||||
speed == 1.0f -> AppStrings.t(lang, "player.speed_standard")
|
||||
speed < 1.0f -> AppStrings.t(lang, "player.speed_slower")
|
||||
else -> AppStrings.t(lang, "player.speed_faster")
|
||||
},
|
||||
deviceType = deviceType
|
||||
)
|
||||
}
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier.fillMaxWidth().heightIn(max = listMaxHeight)) {
|
||||
items(speeds, key = { it }) { speed ->
|
||||
TrackItem(
|
||||
title = formatSpeedLabel(speed),
|
||||
isSelected = playbackSpeed == speed,
|
||||
onClick = { onSpeedChange(speed) },
|
||||
subtitle = when {
|
||||
speed == 1.0f -> AppStrings.t(lang, "player.speed_standard")
|
||||
speed < 1.0f -> AppStrings.t(lang, "player.speed_slower")
|
||||
else -> AppStrings.t(lang, "player.speed_faster")
|
||||
},
|
||||
deviceType = deviceType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -288,31 +264,6 @@ private fun formatSpeedLabel(speed: Float): String {
|
|||
return "${text}x"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpeedChip(
|
||||
label: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(46.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(Color.White.copy(alpha = if (isSelected) 0.10f else 0.04f))
|
||||
.border(1.dp, Color.White.copy(alpha = if (isSelected) 0.4f else 0.08f), RoundedCornerShape(10.dp))
|
||||
.clickable { onClick() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
color = Color.White.copy(alpha = if (isSelected) 1f else 0.6f),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DelayAdjustmentItem(
|
||||
title: String,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import com.fluxa.app.common.AppStrings
|
|||
import com.fluxa.app.data.remote.Meta
|
||||
import com.fluxa.app.data.remote.Stream
|
||||
import com.fluxa.app.ui.catalog.DeviceType
|
||||
import com.fluxa.app.ui.catalog.FluxaDimensions
|
||||
import com.fluxa.app.ui.catalog.FluxaIcons
|
||||
import com.fluxa.app.ui.catalog.LocalDeviceType
|
||||
import com.fluxa.app.ui.catalog.streamRawBody
|
||||
import com.fluxa.app.ui.catalog.streamSourceHeader
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
|
|
@ -45,6 +45,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.zIndex
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun SourceSidebar(streams: List<Stream>, currentUrl: String, deviceType: DeviceType, lang: String = "en", onSelect: (String) -> Unit, onClose: (() -> Unit)? = null) {
|
||||
|
|
@ -81,9 +82,23 @@ fun PlayerSidebarShell(
|
|||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
var shown by remember { mutableStateOf(false) }
|
||||
var closing by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { shown = true }
|
||||
val panelAlpha by animateFloatAsState(if (shown) 1f else 0f, animationSpec = tween(FluxaDimensions.AnimDuration.scaleAlpha), label = "sidebarAlpha")
|
||||
val panelOffset by animateFloatAsState(if (shown) 0f else 44f, animationSpec = tween(FluxaDimensions.AnimDuration.contentExpand, easing = FastOutSlowInEasing), label = "sidebarOffset")
|
||||
val visible = shown && !closing
|
||||
val progress by animateFloatAsState(
|
||||
targetValue = if (visible) 1f else 0f,
|
||||
animationSpec = if (visible) tween(300, easing = FastOutSlowInEasing) else tween(200, easing = FastOutSlowInEasing),
|
||||
label = "sidebarProgress"
|
||||
)
|
||||
fun requestClose() {
|
||||
if (onClose != null && !closing) closing = true
|
||||
}
|
||||
LaunchedEffect(closing) {
|
||||
if (closing) {
|
||||
delay(210L)
|
||||
onClose?.invoke()
|
||||
}
|
||||
}
|
||||
val isMobile = deviceType == DeviceType.Mobile
|
||||
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxSize().zIndex(100f)) {
|
||||
|
|
@ -93,11 +108,11 @@ fun PlayerSidebarShell(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.48f))
|
||||
.background(Color.Black.copy(alpha = 0.48f * progress))
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
) { onClose?.invoke() }
|
||||
) { requestClose() }
|
||||
)
|
||||
|
||||
val panelShape = if (isSideSheet) {
|
||||
|
|
@ -129,10 +144,10 @@ fun PlayerSidebarShell(
|
|||
modifier = Modifier
|
||||
.align(if (isSideSheet) Alignment.CenterEnd else Alignment.BottomCenter)
|
||||
.then(panelSizeModifier)
|
||||
.animateContentSize(tween(220, easing = FastOutSlowInEasing))
|
||||
.graphicsLayer {
|
||||
alpha = panelAlpha
|
||||
translationY = if (!isSideSheet) panelOffset + dragOffsetPx else 0f
|
||||
translationX = if (isSideSheet) panelOffset else 0f
|
||||
translationY = if (!isSideSheet) (1f - progress) * size.height + dragOffsetPx else 0f
|
||||
translationX = if (isSideSheet) (1f - progress) * size.width else 0f
|
||||
}
|
||||
.background(Color(0xFF10141A), shape = panelShape)
|
||||
.border(BorderStroke(1.dp, Color.White.copy(alpha = 0.08f)), shape = panelShape)
|
||||
|
|
@ -153,9 +168,10 @@ fun PlayerSidebarShell(
|
|||
detectVerticalDragGestures(
|
||||
onDragEnd = {
|
||||
if (dragOffsetPx > dismissThresholdPx) {
|
||||
onClose?.invoke()
|
||||
requestClose()
|
||||
} else {
|
||||
dragOffsetPx = 0f
|
||||
}
|
||||
dragOffsetPx = 0f
|
||||
},
|
||||
onVerticalDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
|
|
@ -207,7 +223,7 @@ fun PlayerSidebarShell(
|
|||
.size(if (deviceType == DeviceType.TV) 38.dp else 34.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.08f))
|
||||
.clickable { onClose() },
|
||||
.clickable { requestClose() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(FluxaIcons.Close, null, tint = Color.White, modifier = Modifier.size(if (deviceType == DeviceType.TV) 20.dp else 16.dp))
|
||||
|
|
|
|||
Loading…
Reference in a new issue