fix(media): stable hover controls, fix text cutoff
- Single hover zone on entire video — no more flickering from competing zones. Play button always visible when paused, bottom controls appear on hover (works in fullscreen too) - Media height capped to 50% of window (min 200dp) so post text is never pushed off-screen by large media Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+15
-51
@@ -81,35 +81,25 @@ fun VideoControls(
|
|||||||
onViewModeChange: ((ViewMode) -> Unit)? = null,
|
onViewModeChange: ((ViewMode) -> Unit)? = null,
|
||||||
trailingControls: @Composable (() -> Unit)? = null,
|
trailingControls: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
var hoveringCenter by remember { mutableStateOf(false) }
|
var hovering by remember { mutableStateOf(false) }
|
||||||
var hoveringBottom by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clickable { onPlayPause() },
|
.clickable { onPlayPause() }
|
||||||
) {
|
.pointerInput(Unit) {
|
||||||
// Center hover zone — top 70% of the video
|
awaitPointerEventScope {
|
||||||
Box(
|
while (true) {
|
||||||
modifier =
|
val event = awaitPointerEvent()
|
||||||
Modifier
|
when (event.type) {
|
||||||
.fillMaxWidth()
|
PointerEventType.Enter -> hovering = true
|
||||||
.fillMaxSize(0.7f)
|
PointerEventType.Exit -> hovering = false
|
||||||
.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
|
// Center play/buffering indicator
|
||||||
if (isBuffering) {
|
if (isBuffering) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
@@ -130,40 +120,14 @@ fun VideoControls(
|
|||||||
modifier = Modifier.size(48.dp),
|
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 — show on hover over bottom area
|
// Bottom controls — show on hover
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = hoveringBottom || !isPlaying,
|
visible = hovering,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
modifier =
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
awaitPointerEventScope {
|
|
||||||
while (true) {
|
|
||||||
val event = awaitPointerEvent()
|
|
||||||
when (event.type) {
|
|
||||||
PointerEventType.Enter -> hoveringBottom = true
|
|
||||||
PointerEventType.Exit -> hoveringBottom = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
|
|||||||
+7
-3
@@ -129,10 +129,14 @@ fun NoteCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap media height so each media item fits in the visible window
|
// Cap media height to half the window so text is never pushed off-screen
|
||||||
// Subtract ~200dp for card chrome (header, text preview, actions, padding)
|
|
||||||
val windowState = LocalWindowState.current
|
val windowState = LocalWindowState.current
|
||||||
val maxMediaHeight = if (windowState != null) (windowState.size.height - 200.dp) else 400.dp
|
val maxMediaHeight =
|
||||||
|
if (windowState != null) {
|
||||||
|
(windowState.size.height * 0.5f).coerceAtLeast(200.dp)
|
||||||
|
} else {
|
||||||
|
400.dp
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
|||||||
Reference in New Issue
Block a user