refactor: rename player gesture handlers for clarity

- Rename `PlayerGesturesLayer` parameters to be more descriptive of the gesture type (e.g., `onDoubleTapLeft`, `onDoubleTapCenter`, `onDoubleTapRight`).
- Update the `PlayerScreen` to use the new, more specific gesture handler names.
This commit is contained in:
2026-01-27 18:18:09 +01:00
parent 8cd97fc928
commit 3d7b6bcf13
3 changed files with 143 additions and 1 deletions

View File

@@ -13,7 +13,10 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -23,7 +26,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.AspectRatioFrameLayout
@@ -35,6 +40,8 @@ import hu.bbara.purefin.player.ui.components.PlayerQueuePanel
import hu.bbara.purefin.player.ui.components.PlayerSettingsSheet import hu.bbara.purefin.player.ui.components.PlayerSettingsSheet
import hu.bbara.purefin.player.ui.components.PlayerSideSliders import hu.bbara.purefin.player.ui.components.PlayerSideSliders
import hu.bbara.purefin.player.viewmodel.PlayerViewModel import hu.bbara.purefin.player.viewmodel.PlayerViewModel
import kotlinx.coroutines.delay
import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@@ -57,6 +64,15 @@ fun PlayerScreen(
var brightnessOverlayVisible by remember { mutableStateOf(false) } var brightnessOverlayVisible by remember { mutableStateOf(false) }
var volumeOverlayVisible by remember { mutableStateOf(false) } var volumeOverlayVisible by remember { mutableStateOf(false) }
var showQueuePanel 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) { LaunchedEffect(uiState.isPlaying) {
if (uiState.isPlaying) { if (uiState.isPlaying) {
@@ -107,9 +123,27 @@ fun PlayerScreen(
(volume * maxVolume).roundToInt(), (volume * maxVolume).roundToInt(),
0 0
) )
},
setFeedBackPreview = {
showFeedbackPreview = it
},
onHorizontalDragPreview = {
horizontalSeekFeedback = it
},
onHorizontalDrag = {
viewModel.seekBy(it)
horizontalSeekFeedback = it
} }
) )
horizontalSeekFeedback?.let { delta ->
SeekAmountIndicator(
deltaMs = delta,
modifier = Modifier
.align(Alignment.Center)
)
}
AnimatedVisibility( AnimatedVisibility(
visible = volumeOverlayVisible || brightnessOverlayVisible, visible = volumeOverlayVisible || brightnessOverlayVisible,
enter = fadeIn(), enter = fadeIn(),
@@ -197,6 +231,36 @@ fun PlayerScreen(
} }
} }
@Composable
private fun SeekAmountIndicator(deltaMs: Long, modifier: Modifier = Modifier) {
val scheme = MaterialTheme.colorScheme
val prefix = if (deltaMs >= 0) "+" else "-"
val formatted = formatSeekDelta(abs(deltaMs))
Box(
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(scheme.surface.copy(alpha = 0.9f))
.padding(horizontal = 20.dp, vertical = 12.dp)
) {
Text(
text = "$prefix$formatted",
color = scheme.onSurface,
style = MaterialTheme.typography.titleMedium
)
}
}
private fun formatSeekDelta(deltaMs: Long): String {
val totalSeconds = (deltaMs / 1000).toInt()
val seconds = totalSeconds % 60
val minutes = totalSeconds / 60
return if (minutes > 0) {
"%d:%02d".format(minutes, seconds)
} else {
"%02d s".format(seconds)
}
}
private fun readCurrentBrightness(activity: Activity?): Float { private fun readCurrentBrightness(activity: Activity?): Float {
val current = activity?.window?.attributes?.screenBrightness val current = activity?.window?.attributes?.screenBrightness
return if (current != null && current >= 0) current else 0.5f return if (current != null && current >= 0) current else 0.5f

View File

@@ -0,0 +1,21 @@
package hu.bbara.purefin.player.ui.components
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.pow
internal object HorizontalSeekGestureHelper {
val START_THRESHOLD = 24.dp
private const val COEFFICIENT = 1.3f
const val EXPONENT = 1.8f
private const val MAX_DELTA_MS = 300_000L
fun deltaMs(rawDelta: Float): Long {
val magnitude = abs(rawDelta)
if (magnitude == 0f) return 0L
val magnitudePow = magnitude.toDouble().pow(EXPONENT.toDouble()).toFloat()
val scaled = COEFFICIENT * magnitudePow
val signed = if (rawDelta > 0f) scaled else -scaled
return signed.toLong().coerceIn(-MAX_DELTA_MS, MAX_DELTA_MS)
}
}

View File

@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
@Composable @Composable
fun PlayerGesturesLayer( fun PlayerGesturesLayer(
@@ -17,8 +18,14 @@ fun PlayerGesturesLayer(
onDoubleTapRight: () -> Unit, onDoubleTapRight: () -> Unit,
onDoubleTapLeft: () -> Unit, onDoubleTapLeft: () -> Unit,
onVerticalDragLeft: (delta: Float) -> Unit, onVerticalDragLeft: (delta: Float) -> Unit,
onVerticalDragRight: (delta: Float) -> Unit 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() }
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@@ -50,5 +57,55 @@ fun PlayerGesturesLayer(
} }
} }
} }
.pointerInput(Unit) {
var accumulatedHorizontalDrag = 0f
var isHorizontalDragActive = false
var lastPreviewDelta: Long? = null
detectHorizontalDragGestures(
onDragStart = {
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()
val deltaMs = HorizontalSeekGestureHelper.deltaMs(accumulatedHorizontalDrag)
if (deltaMs != 0L && deltaMs != lastPreviewDelta) {
lastPreviewDelta = deltaMs
onHorizontalDragPreview(deltaMs)
}
}
},
onDragEnd = {
if (isHorizontalDragActive) {
val deltaMs = HorizontalSeekGestureHelper.deltaMs(accumulatedHorizontalDrag)
if (deltaMs != 0L) {
onHorizontalDrag(deltaMs)
onHorizontalDragPreview(deltaMs)
}
}
accumulatedHorizontalDrag = 0f
isHorizontalDragActive = false
lastPreviewDelta = null
setFeedBackPreview(false)
onHorizontalDragPreview(null)
},
onDragCancel = {
accumulatedHorizontalDrag = 0f
isHorizontalDragActive = false
lastPreviewDelta = null
setFeedBackPreview(false)
onHorizontalDragPreview(null)
}
)
}
) )
} }