Share video zoom geometry between Android viewers

This commit is contained in:
evgeny committed 2026-10-14 17:36:38 +02:00
1 parent 7ff95623c1
commit c39610f079
3 files changed
+91 -26

No files matched your search

@@ -36,7 +36,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -67,12 +67,6 @@ private tailrec fun Context.findActivity(): Activity? = when (this) {
else -> null
}
private fun clampOffset(scale: Float, o: Offset, vW: Float, vH: Float, sW: Float, sH: Float): Offset {
val mx = ((vW * scale - sW) / 2f).coerceAtLeast(0f)
val my = ((vH * scale - sH) / 2f).coerceAtLeast(0f)
return Offset(o.x.coerceIn(-mx, mx), o.y.coerceIn(-my, my))
}
@Composable
fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
val player = remember(filePath) { MediaPlayer() }
@@ -88,8 +82,7 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
var dragPosition by remember { mutableStateOf(0) }
var wasPlayingBeforeDrag by remember { mutableStateOf(false) }
var videoInfo by remember { mutableStateOf<VideoInfo?>(null) }
var zoomScale by remember(filePath) { mutableStateOf(1f) }
var zoomOffset by remember(filePath) { mutableStateOf(Offset.Zero) }
val zoom = remember(filePath) { VideoZoomState() }
var hideTick by remember { mutableStateOf(0) }
val context = LocalContext.current
@@ -290,8 +283,8 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
val videoH = with(density) { h.toPx() }
/* re-clamp zoom offset when screen size changes (e.g. flip 180) */
LaunchedEffect(screenW, screenH) {
zoomOffset = clampOffset(zoomScale, zoomOffset, videoW, videoH, screenW, screenH)
LaunchedEffect(screenW, screenH, videoW, videoH) {
zoom.constrain(Size(videoW, videoH), Size(screenW, screenH))
}
AndroidView(
@@ -317,10 +310,10 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
}
},
modifier = videoModifier.graphicsLayer {
scaleX = zoomScale
scaleY = zoomScale
translationX = zoomOffset.x
translationY = zoomOffset.y
scaleX = zoom.scale
scaleY = zoom.scale
translationX = zoom.offset.x
translationY = zoom.offset.y
}
)
@@ -344,18 +337,9 @@ fun VideoPlayerScreen(filePath: String, onClose: () -> Unit) {
}
}
.pointerInput(screenW, screenH, videoW, videoH) {
detectTransformGestures { centroid, pan, zoom, _ ->
detectTransformGestures { centroid, pan, factor, _ ->
onInteraction()
val old = zoomScale
val new = (zoomScale * zoom).coerceIn(1f, 4f)
val z = new / old
val rel = centroid - Offset(screenW / 2f, screenH / 2f)
val raw = Offset(
zoomOffset.x * z + rel.x * (1f - z) + pan.x,
zoomOffset.y * z + rel.y * (1f - z) + pan.y
)
zoomScale = new
zoomOffset = clampOffset(new, raw, videoW, videoH, screenW, screenH)
zoom.transform(centroid, pan, factor, Size(videoW, videoH), Size(screenW, screenH))
}
}
)
@@ -0,0 +1,30 @@
package com.utun.chat.ui.screens
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
/** Общая геометрия увеличения для файла и live: точка под пальцами сохраняется, края ограничивают сдвиг. */
internal class VideoZoomState {
var scale by mutableStateOf(1f)
private set
var offset by mutableStateOf(Offset.Zero)
private set
fun transform(centroid: Offset, pan: Offset, factor: Float, video: Size, viewport: Size) {
val next = (scale * factor).coerceIn(1f, 4f)
val ratio = next / scale
val relative = centroid - Offset(viewport.width / 2f, viewport.height / 2f)
offset = offset * ratio + relative * (1f - ratio) + pan
scale = next
constrain(video, viewport)
}
fun constrain(video: Size, viewport: Size) {
val x = ((video.width * scale - viewport.width) / 2f).coerceAtLeast(0f)
val y = ((video.height * scale - viewport.height) / 2f).coerceAtLeast(0f)
offset = Offset(offset.x.coerceIn(-x, x), offset.y.coerceIn(-y, y))
}
}