package stories.presentation import android.net.Uri import androidx.annotation.OptIn import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close 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.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.PlayerView import coil.compose.AsyncImage import kotlinx.coroutines.delay import kotlinx.coroutines.launch @OptIn(UnstableApi::class) @Composable fun StoryViewerScreen( stories: List, // URL-адреса фото/видео onClose: () -> Unit ) { val context = LocalContext.current var currentIndex by remember { mutableIntStateOf(0) } val progress = remember { Animatable(0f) } val scope = rememberCoroutineScope() var isPaused by remember { mutableStateOf(false) } val currentUrl = stories[currentIndex] val isVideo = currentUrl.endsWith(".mp4") || currentUrl.contains("video") val exoPlayer = remember { ExoPlayer.Builder(context).build().apply { repeatMode = Player.REPEAT_MODE_OFF playWhenReady = true } } DisposableEffect(exoPlayer) { onDispose { exoPlayer.release() } } // Подготовка видео при смене индекса LaunchedEffect(currentIndex) { if (isVideo) { exoPlayer.setMediaItem(MediaItem.fromUri(Uri.parse(currentUrl))) exoPlayer.prepare() exoPlayer.play() } else { exoPlayer.stop() } } // Запуск таймера прогресса LaunchedEffect(currentIndex, isPaused) { if (!isPaused) { val duration = if (isVideo) { // Ждем пока загрузится длительность видео или используем 15сек по умолчанию delay(500) // Даем немного времени на загрузку метаданных if (exoPlayer.duration > 0) exoPlayer.duration else 15000 } else { 5000L // 5 секунд для фото } progress.animateTo( targetValue = 1f, animationSpec = tween( durationMillis = duration.toInt(), easing = LinearEasing ) ) // Когда прогресс дошел до конца — следующая история if (currentIndex < stories.size - 1) { currentIndex++ progress.snapTo(0f) } else { onClose() } } else { if (isVideo) exoPlayer.pause() else Unit } } // Возобновление видео при снятии паузы LaunchedEffect(isPaused) { if (!isPaused && isVideo) exoPlayer.play() } Box( modifier = Modifier .fillMaxSize() .background(Color.Black) .pointerInput(Unit) { detectTapGestures( onPress = { isPaused = true tryAwaitRelease() isPaused = false }, onTap = { offset -> val screenWidth = size.width if (offset.x < screenWidth / 3) { if (currentIndex > 0) { currentIndex-- scope.launch { progress.snapTo(0f) } } } else { if (currentIndex < stories.size - 1) { currentIndex++ scope.launch { progress.snapTo(0f) } } else { onClose() } } } ) } ) { if (isVideo) { AndroidView( factory = { PlayerView(context).apply { player = exoPlayer useController = false resizeMode = androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM } }, modifier = Modifier.fillMaxSize() ) } else { AsyncImage( model = currentUrl, contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) } // Верхние индикаторы Row( modifier = Modifier .fillMaxWidth() .padding(top = 16.dp, start = 8.dp, end = 8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { stories.forEachIndexed { index, _ -> val currentProgress = when { index < currentIndex -> 1f index == currentIndex -> progress.value else -> 0f } LinearProgressIndicator( progress = currentProgress, modifier = Modifier .weight(1f) .height(3.dp) .clip(RoundedCornerShape(2.dp)), color = Color.White, trackColor = Color.White.copy(alpha = 0.3f) ) } } IconButton( onClick = onClose, modifier = Modifier .align(Alignment.TopEnd) .padding(top = 24.dp, end = 16.dp) ) { Icon(Icons.Default.Close, contentDescription = "Close", tint = Color.White) } } }