refactor: improve player gesture feedback with timed visibility components

- Replace `LaunchedEffect` and manual state handling for showing/hiding player overlays (brightness, volume, seek feedback) with new reusable timed visibility composables.
- Introduce `ValueChangeTimedVisibility` to automatically show brightness and volume sliders for a short duration when their values change.
- Implement `EmptyValueTimedVisibility` to display the horizontal seek amount indicator and hide it after a delay.
- Remove redundant state variables (`brightnessOverlayVisible`, `volumeOverlayVisible`, `showFeedbackPreview`) and related logic from `PlayerScreen`.
- Clean up `PlayerGesturesLayer` by removing the `setFeedBackPreview` callback, simplifying its responsibility.
- Remove the auto-hide `LaunchedEffect` from `PlayerSideSliders` as this is now handled externally.
This commit is contained in:
2026-01-27 19:16:23 +01:00
parent b97867d9f1
commit d24eff19cc
3 changed files with 28 additions and 47 deletions

View File

@@ -33,6 +33,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import hu.bbara.purefin.common.ui.components.EmptyValueTimedVisibility
import hu.bbara.purefin.common.ui.components.ValueChangeTimedVisibility
import hu.bbara.purefin.player.ui.components.PlayerControlsOverlay
import hu.bbara.purefin.player.ui.components.PlayerGesturesLayer
import hu.bbara.purefin.player.ui.components.PlayerLoadingErrorEndCard
@@ -40,7 +42,6 @@ import hu.bbara.purefin.player.ui.components.PlayerQueuePanel
import hu.bbara.purefin.player.ui.components.PlayerSettingsSheet
import hu.bbara.purefin.player.ui.components.PlayerSideSliders
import hu.bbara.purefin.player.viewmodel.PlayerViewModel
import kotlinx.coroutines.delay
import kotlin.math.abs
import kotlin.math.roundToInt
@@ -61,18 +62,8 @@ fun PlayerScreen(
var volume by remember { mutableStateOf(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / maxVolume.toFloat()) }
var brightness by remember { mutableStateOf(readCurrentBrightness(activity)) }
var showSettings by remember { mutableStateOf(false) }
var brightnessOverlayVisible by remember { mutableStateOf(false) }
var volumeOverlayVisible by remember { mutableStateOf(false) }
var showQueuePanel by remember { mutableStateOf(false) }
var horizontalSeekFeedback by remember { mutableStateOf<Long?>(null) }
var showFeedbackPreview by remember { mutableStateOf(false) }
LaunchedEffect(showFeedbackPreview) {
if (!showFeedbackPreview) {
delay(1000)
horizontalSeekFeedback = null
}
}
LaunchedEffect(uiState.isPlaying) {
if (uiState.isPlaying) {
@@ -111,22 +102,17 @@ fun PlayerScreen(
onVerticalDragLeft = { delta ->
val diff = (-delta / 800f)
brightness = (brightness + diff).coerceIn(0f, 1f)
brightnessOverlayVisible = true
applyBrightness(activity, brightness)
},
onVerticalDragRight = { delta ->
val diff = (-delta / 800f)
volume = (volume + diff).coerceIn(0f, 1f)
volumeOverlayVisible = true
audioManager.setStreamVolume(
AudioManager.STREAM_MUSIC,
(volume * maxVolume).roundToInt(),
0
)
},
setFeedBackPreview = {
showFeedbackPreview = it
},
onHorizontalDragPreview = {
horizontalSeekFeedback = it
},
@@ -136,30 +122,40 @@ fun PlayerScreen(
}
)
horizontalSeekFeedback?.let { delta ->
EmptyValueTimedVisibility(
value = horizontalSeekFeedback,
hideAfterMillis = 1_000
) {
SeekAmountIndicator(
deltaMs = delta,
deltaMs = it,
modifier = Modifier
.align(Alignment.Center)
)
}
AnimatedVisibility(
visible = volumeOverlayVisible || brightnessOverlayVisible,
enter = fadeIn(),
exit = fadeOut()
) {
ValueChangeTimedVisibility(
value = brightness,
hideAfterMillis = 800
) { currentBrightness ->
PlayerSideSliders(
modifier = Modifier
.fillMaxSize(),
brightness = brightness,
modifier = Modifier.fillMaxSize(),
brightness = currentBrightness,
volume = volume,
showBrightness = brightnessOverlayVisible,
showVolume = volumeOverlayVisible,
onHide = {
brightnessOverlayVisible = false
volumeOverlayVisible = false
showBrightness = true,
showVolume = false,
)
}
ValueChangeTimedVisibility(
value = volume,
hideAfterMillis = 800
) { currentVolume ->
PlayerSideSliders(
modifier = Modifier.fillMaxSize(),
brightness = brightness,
volume = currentVolume,
showBrightness = false,
showVolume = true,
)
}

View File

@@ -21,7 +21,6 @@ fun PlayerGesturesLayer(
onVerticalDragRight: (delta: Float) -> Unit,
onHorizontalDragPreview: (deltaMs: Long?) -> Unit = {},
onHorizontalDrag: (deltaMs: Long) -> Unit,
setFeedBackPreview: (show: Boolean) -> Unit
) {
val density = LocalDensity.current
val horizontalThresholdPx = with(density) { HorizontalSeekGestureHelper.START_THRESHOLD.toPx() }
@@ -66,14 +65,12 @@ fun PlayerGesturesLayer(
accumulatedHorizontalDrag = 0f
isHorizontalDragActive = false
lastPreviewDelta = null
setFeedBackPreview(false)
onHorizontalDragPreview(null)
},
onHorizontalDrag = { change, dragAmount ->
accumulatedHorizontalDrag += dragAmount
if (!isHorizontalDragActive && kotlin.math.abs(accumulatedHorizontalDrag) >= horizontalThresholdPx) {
isHorizontalDragActive = true
setFeedBackPreview(true)
}
if (isHorizontalDragActive) {
change.consume()
@@ -95,14 +92,12 @@ fun PlayerGesturesLayer(
accumulatedHorizontalDrag = 0f
isHorizontalDragActive = false
lastPreviewDelta = null
setFeedBackPreview(false)
onHorizontalDragPreview(null)
},
onDragCancel = {
accumulatedHorizontalDrag = 0f
isHorizontalDragActive = false
lastPreviewDelta = null
setFeedBackPreview(false)
onHorizontalDragPreview(null)
}
)

View File

@@ -15,12 +15,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@Composable
fun PlayerSideSliders(
@@ -29,17 +27,9 @@ fun PlayerSideSliders(
volume: Float,
showBrightness: Boolean,
showVolume: Boolean,
onHide: () -> Unit
) {
val scheme = MaterialTheme.colorScheme
LaunchedEffect(showBrightness, showVolume) {
if (showBrightness || showVolume) {
delay(800)
onHide()
}
}
Box(modifier = modifier.fillMaxWidth()) {
if (showBrightness) {
SideOverlay(