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.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.InsertDriveFile import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color 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 import coil.compose.AsyncImage import chats.domain.model.Media import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import android.content.Intent import android.net.Uri import kotlinx.coroutines.launch @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable fun AppMediaLightbox( mediaList: List, initialIndex: Int = 0, onClose: () -> Unit ) { val context = LocalContext.current val scope = rememberCoroutineScope() val pagerState = rememberPagerState(initialPage = initialIndex, pageCount = { mediaList.size }) var canScroll by remember { mutableStateOf(true) } Dialog( onDismissRequest = onClose, properties = DialogProperties( usePlatformDefaultWidth = false, decorFitsSystemWindows = false ) ) { Box( modifier = Modifier .fillMaxSize() .background(Color.Black) ) { HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize(), pageSpacing = 16.dp, beyondBoundsPageCount = 1, userScrollEnabled = canScroll ) { page -> val media = mediaList[page] Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { if (media.type.startsWith("video")) { val isCurrentPage = pagerState.currentPage == page AppVideoPlayer( url = media.url, modifier = Modifier.fillMaxSize(), useController = true, autoPlay = isCurrentPage // Только если страница активна ) } 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 } 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( 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), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = Icons.Default.InsertDriveFile, contentDescription = null, tint = Color.White, modifier = Modifier.size(100.dp) ) Spacer(modifier = Modifier.height(24.dp)) Text( text = media.filename ?: "File", color = Color.White, style = MaterialTheme.typography.headlineSmall, textAlign = androidx.compose.ui.text.style.TextAlign.Center ) Spacer(modifier = Modifier.height(32.dp)) Button( onClick = { try { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.url)) context.startActivity(intent) } catch (e: Exception) { // Handle error (no app to open) } }, colors = ButtonDefaults.buttonColors(containerColor = Color.White, contentColor = Color.Black) ) { Text("Открыть в приложении") } } } } } // Top Bar Row( modifier = Modifier .fillMaxWidth() .statusBarsPadding() .padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { IconButton( onClick = onClose, modifier = Modifier .clip(CircleShape) .background(Color.Black.copy(alpha = 0.5f)) ) { Icon(Icons.Default.Close, contentDescription = null, tint = Color.White) } if (mediaList.isNotEmpty()) { Text( text = "${pagerState.currentPage + 1} / ${mediaList.size}", color = Color.White, style = MaterialTheme.typography.titleMedium ) } IconButton( onClick = { val currentMedia = mediaList[pagerState.currentPage] scope.launch { core.utils.DownloadUtils.downloadMedia(context, currentMedia.url, currentMedia.filename) } }, modifier = Modifier .clip(CircleShape) .background(Color.Black.copy(alpha = 0.5f)) ) { Icon(Icons.Default.Download, contentDescription = null, tint = Color.White) } } } } }