Зум и пролистывание

This commit is contained in:
Халимов Рустам
2026-04-15 02:12:43 +03:00
parent c6bebec599
commit f17edfc0da
8 changed files with 77 additions and 14 deletions

View File

@@ -3,7 +3,11 @@ package core.presentation.components
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.*
@@ -22,6 +26,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@@ -43,6 +48,7 @@ fun AppMediaLightbox(
val context = LocalContext.current
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(initialPage = initialIndex, pageCount = { mediaList.size })
var canScroll by remember { mutableStateOf(true) }
Dialog(
onDismissRequest = onClose,
@@ -60,7 +66,8 @@ fun AppMediaLightbox(
state = pagerState,
modifier = Modifier.fillMaxSize(),
pageSpacing = 16.dp,
beyondBoundsPageCount = 1
beyondBoundsPageCount = 1,
userScrollEnabled = canScroll
) { page ->
val media = mediaList[page]
@@ -79,22 +86,78 @@ fun AppMediaLightbox(
} else if (media.type.startsWith("image") || media.type.contains("gif")) {
var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(androidx.compose.ui.geometry.Offset.Zero) }
// Reset zoom when page changes
LaunchedEffect(pagerState.currentPage) {
scale = 1f
offset = androidx.compose.ui.geometry.Offset.Zero
canScroll = true
}
AsyncImage(
model = media.url,
imageLoader = core.utils.CoilUtils.getGifImageLoader(LocalContext.current),
contentDescription = null,
Box(
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offset.x,
translationY = offset.y
),
contentScale = ContentScale.Fit
)
} else {
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
if (scale > 1f) {
scale = 1f
offset = Offset.Zero
canScroll = true
} else {
scale = 3f
canScroll = false
}
}
)
}
.pointerInput(Unit) {
awaitEachGesture {
do {
val event = awaitPointerEvent()
val zoomActive = scale > 1f || event.changes.size > 1
if (zoomActive) {
// Если мы в режиме зума - поглощаем события
event.changes.forEach { it.consume() }
// Сами вычисляем трансформацию
val zoomChange = event.calculateZoom()
val panChange = event.calculatePan()
scale = (scale * zoomChange).coerceIn(1f, 5f)
canScroll = scale <= 1f
if (scale > 1f) {
offset += panChange
} else {
offset = Offset.Zero
}
}
// Если zoomActive == false, мы НЕ вызываем consume(),
// и событие уходит наверх в HorizontalPager
} while (event.changes.any { it.pressed })
}
},
contentAlignment = Alignment.Center
) {
AsyncImage(
model = media.url,
imageLoader = core.utils.CoilUtils.getGifImageLoader(LocalContext.current),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offset.x,
translationY = offset.y
),
contentScale = ContentScale.Fit
)
}
}
else {
// Document / File placeholder in Lightbox
Column(
modifier = Modifier.fillMaxSize().padding(32.dp),