From 248f38cc4713cff2cd854236e71798a08fe1bf4d Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Tue, 17 Mar 2026 16:32:08 +0200 Subject: [PATCH] =?UTF-8?q?fix(media):=20zone-based=20video=20controls=20?= =?UTF-8?q?=E2=80=94=20play=20always=20visible,=20controls=20on=20hover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Center play button always visible when paused - Pause button appears on center hover when playing - Bottom controls bar only appears when hovering the bottom area - Controls also show when paused for discoverability Co-Authored-By: Claude Opus 4.6 (1M context) --- .../desktop/ui/media/VideoControls.kt | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/media/VideoControls.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/media/VideoControls.kt index f6fd3db4b..f592f8e3f 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/media/VideoControls.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/media/VideoControls.kt @@ -51,7 +51,6 @@ import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -62,7 +61,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp -import kotlinx.coroutines.delay @Composable fun VideoControls( @@ -83,50 +81,44 @@ fun VideoControls( onViewModeChange: ((ViewMode) -> Unit)? = null, trailingControls: @Composable (() -> Unit)? = null, ) { - var hovering by remember { mutableStateOf(false) } - var showControls by remember { mutableStateOf(false) } - - // Show controls on hover, auto-hide 2s after mouse leaves - LaunchedEffect(hovering) { - if (hovering) { - showControls = true - } else { - delay(2000) - showControls = false - } - } + var hoveringCenter by remember { mutableStateOf(false) } + var hoveringBottom by remember { mutableStateOf(false) } Box( modifier = modifier .fillMaxSize() - .clickable { onPlayPause() } - .pointerInput(Unit) { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - when (event.type) { - PointerEventType.Enter -> { - hovering = true - showControls = true - } - - PointerEventType.Exit -> { - hovering = false + .clickable { onPlayPause() }, + ) { + // Center hover zone — top 70% of the video + Box( + modifier = + Modifier + .fillMaxWidth() + .fillMaxSize(0.7f) + .align(Alignment.TopCenter) + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + when (event.type) { + PointerEventType.Enter -> hoveringCenter = true + PointerEventType.Exit -> hoveringCenter = false } } } - } - }, - ) { - // Center play/buffering indicator — only on hover + }, + ) + + // Center play/buffering indicator if (isBuffering) { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center).size(48.dp), color = Color.White, strokeWidth = 3.dp, ) - } else if (!isPlaying && showControls) { + } else if (!isPlaying) { + // Always show play button when paused IconButton( onClick = onPlayPause, modifier = Modifier.align(Alignment.Center).size(64.dp), @@ -138,14 +130,40 @@ fun VideoControls( modifier = Modifier.size(48.dp), ) } + } else if (hoveringCenter) { + // Show pause button on center hover when playing + IconButton( + onClick = onPlayPause, + modifier = Modifier.align(Alignment.Center).size(64.dp), + ) { + Icon( + Icons.Default.Pause, + contentDescription = "Pause", + tint = Color.White, + modifier = Modifier.size(48.dp), + ) + } } - // Bottom controls — two rows: seek slider on top, buttons below + // Bottom controls — show on hover over bottom area AnimatedVisibility( - visible = showControls, + visible = hoveringBottom || !isPlaying, enter = fadeIn(), exit = fadeOut(), - modifier = Modifier.align(Alignment.BottomCenter), + modifier = + Modifier + .align(Alignment.BottomCenter) + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + when (event.type) { + PointerEventType.Enter -> hoveringBottom = true + PointerEventType.Exit -> hoveringBottom = false + } + } + } + }, ) { Column( modifier =