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

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
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,11 @@ package core.presentation.components
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable 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.detectTransformGestures
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.* 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.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
@@ -43,6 +48,7 @@ fun AppMediaLightbox(
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(initialPage = initialIndex, pageCount = { mediaList.size }) val pagerState = rememberPagerState(initialPage = initialIndex, pageCount = { mediaList.size })
var canScroll by remember { mutableStateOf(true) }
Dialog( Dialog(
onDismissRequest = onClose, onDismissRequest = onClose,
@@ -60,7 +66,8 @@ fun AppMediaLightbox(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
pageSpacing = 16.dp, pageSpacing = 16.dp,
beyondBoundsPageCount = 1 beyondBoundsPageCount = 1,
userScrollEnabled = canScroll
) { page -> ) { page ->
val media = mediaList[page] val media = mediaList[page]
@@ -80,6 +87,60 @@ fun AppMediaLightbox(
var scale by remember { mutableFloatStateOf(1f) } var scale by remember { mutableFloatStateOf(1f) }
var offset by remember { mutableStateOf(androidx.compose.ui.geometry.Offset.Zero) } 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
}
Box(
modifier = Modifier
.fillMaxSize()
.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( AsyncImage(
model = media.url, model = media.url,
imageLoader = core.utils.CoilUtils.getGifImageLoader(LocalContext.current), imageLoader = core.utils.CoilUtils.getGifImageLoader(LocalContext.current),
@@ -94,7 +155,9 @@ fun AppMediaLightbox(
), ),
contentScale = ContentScale.Fit contentScale = ContentScale.Fit
) )
} else { }
}
else {
// Document / File placeholder in Lightbox // Document / File placeholder in Lightbox
Column( Column(
modifier = Modifier.fillMaxSize().padding(32.dp), modifier = Modifier.fillMaxSize().padding(32.dp),