package stories.presentation import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* 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.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.compose.ui.viewinterop.AndroidView import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.PlayerView import coil.compose.AsyncImage import core.presentation.theme.OnSurfaceVariant import core.presentation.theme.PrimaryIndigo import core.presentation.theme.SurfaceContainer import ru.knot.messager.R @OptIn(ExperimentalMaterial3Api::class) @Composable fun CreateStoryScreen( onClose: () -> Unit, onCreated: () -> Unit, viewModel: CreateStoryViewModel = hiltViewModel() ) { val context = LocalContext.current val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent() ) { uri: Uri? -> uri?.let { val isVideo = context.contentResolver.getType(it)?.startsWith("video/") == true viewModel.onMediaSelected(it, isVideo) } } LaunchedEffect(viewModel.uploadSuccess) { viewModel.uploadSuccess.collect { success -> if (success) { onCreated() onClose() } } } Box( modifier = Modifier .fillMaxSize() .background(Color.Black) ) { Column(modifier = Modifier.fillMaxSize()) { // Header Header( onClose = onClose, isUploading = viewModel.isUploading, onPublish = { viewModel.publishStory(context) } ) Row(modifier = Modifier.weight(1f)) { // Main Area Box( modifier = Modifier .weight(1f) .padding(16.dp) .clip(RoundedCornerShape(32.dp)) .background(viewModel.bgColor) .border(8.dp, Color(0xFF161616), RoundedCornerShape(32.dp)), contentAlignment = Alignment.Center ) { if (viewModel.selectedMediaUri == null) { EmptyMediaState(onSelect = { launcher.launch("*/*") }) } else { MediaPreview( uri = viewModel.selectedMediaUri, isVideo = viewModel.isVideo, onRemove = { viewModel.removeMedia() } ) } } // Sidebar Tools Sidebar( currentTool = viewModel.currentTool, onToolSelect = { viewModel.setTool(it) }, onAddText = { viewModel.addText("New Text") }, onColorChange = { viewModel.updateBgColor(it) }, bgColor = viewModel.bgColor ) } } } } @Composable fun Header( onClose: () -> Unit, isUploading: Boolean, onPublish: () -> Unit ) { Row( modifier = Modifier .fillMaxWidth() .height(80.dp) .background(Color(0xFF0A0A0A)) .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Row(verticalAlignment = Alignment.CenterVertically) { IconButton(onClick = onClose) { Icon(Icons.Default.Close, contentDescription = stringResource(R.string.back), tint = Color.Gray) } Spacer(modifier = Modifier.width(8.dp)) Text( stringResource(R.string.story_editor), style = MaterialTheme.typography.labelMedium.copy( fontWeight = FontWeight.Black, letterSpacing = 2.sp, color = Color.Gray ) ) } Button( onClick = onPublish, enabled = !isUploading, colors = ButtonDefaults.buttonColors(containerColor = PrimaryIndigo), shape = RoundedCornerShape(12.dp) ) { if (isUploading) { CircularProgressIndicator( modifier = Modifier.size(20.dp), color = Color.White, strokeWidth = 2.dp ) } else { Text( stringResource(R.string.publish), style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Black) ) } } } } @Composable fun EmptyMediaState(onSelect: () -> Unit) { Column( modifier = Modifier.clickable { onSelect() }, horizontalAlignment = Alignment.CenterHorizontally ) { Box( modifier = Modifier .size(80.dp) .border(2.dp, Color(0xFF333333), CircleShape), contentAlignment = Alignment.Center ) { Icon(Icons.Default.CameraAlt, contentDescription = null, tint = Color(0xFF333333), modifier = Modifier.size(32.dp)) } Spacer(modifier = Modifier.height(16.dp)) Text( stringResource(R.string.start_creation), style = MaterialTheme.typography.labelSmall.copy( fontWeight = FontWeight.Black, letterSpacing = 4.sp, color = Color(0xFF333333) ) ) } } @Composable fun MediaPreview( uri: Uri?, isVideo: Boolean, onRemove: () -> Unit ) { val context = LocalContext.current val exoPlayer = remember { ExoPlayer.Builder(context).build().apply { repeatMode = Player.REPEAT_MODE_ALL playWhenReady = true } } LaunchedEffect(uri) { if (isVideo && uri != null) { exoPlayer.setMediaItem(MediaItem.fromUri(uri)) exoPlayer.prepare() } } DisposableEffect(Unit) { onDispose { exoPlayer.release() } } Box(modifier = Modifier.fillMaxSize()) { 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 = uri, contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) } IconButton( onClick = onRemove, modifier = Modifier .align(Alignment.TopEnd) .padding(16.dp) .background(Color.Black.copy(0.6f), RoundedCornerShape(12.dp)) ) { Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.remove), tint = Color.White) } } } @Composable fun Sidebar( currentTool: StoryTool, onToolSelect: (StoryTool) -> Unit, onAddText: () -> Unit, onColorChange: (Color) -> Unit, bgColor: Color ) { Column( modifier = Modifier .width(100.dp) .fillMaxHeight() .background(Color(0xFF0A0A0A)) .padding(vertical = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { ToolButton(Icons.Default.TextFields, stringResource(R.string.text_tool), currentTool is StoryTool.Text) { onAddText() } ToolButton(Icons.Default.ContentCut, stringResource(R.string.crop_tool), currentTool is StoryTool.Crop) { onToolSelect(StoryTool.Crop) } ToolButton(Icons.Default.SentimentSatisfied, stringResource(R.string.stickers_tool), currentTool is StoryTool.Stickers) { onToolSelect(StoryTool.Stickers) } ToolButton(Icons.Default.Brush, stringResource(R.string.brush_tool), currentTool is StoryTool.Brush) { onToolSelect(StoryTool.Brush) } ToolButton(Icons.Default.Tune, stringResource(R.string.filters_tool), currentTool is StoryTool.Filters) { onToolSelect(StoryTool.Filters) } Spacer(modifier = Modifier.weight(1f)) Box( modifier = Modifier .size(40.dp) .clip(CircleShape) .background(bgColor) .border(2.dp, Color.White.copy(0.1f), CircleShape) .clickable { onColorChange(Color((0..0xFFFFFF).random() or 0xFF000000.toInt())) } ) } } @Composable fun ToolButton( icon: ImageVector, label: String, isSelected: Boolean, onClick: () -> Unit ) { Column( modifier = Modifier .fillMaxWidth() .clickable { onClick() } .padding(vertical = 8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( icon, contentDescription = label, tint = if (isSelected) PrimaryIndigo else OnSurfaceVariant, modifier = Modifier.size(24.dp) ) Text( label, style = MaterialTheme.typography.labelSmall.copy( fontSize = 8.sp, fontWeight = FontWeight.Black, color = if (isSelected) Color.White else OnSurfaceVariant ) ) } }