package chats.presentation.chat_detail import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState 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.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import chats.presentation.components.EmojiPicker import chats.presentation.components.MessageBubble import core.presentation.components.AppAvatar import core.utils.VoiceRecorder import core.utils.copyUriToFile import java.io.File import ru.knot.messager.R @OptIn(ExperimentalMaterial3Api::class) @Composable fun ChatDetailScreen( chatId: String, chatName: String, viewModel: ChatDetailViewModel, onBack: () -> Unit ) { val state by viewModel.state.collectAsState() val context = LocalContext.current val listState = rememberLazyListState() val voiceRecorder = remember { VoiceRecorder(context) } var textInput by remember { mutableStateOf("") } var isEmojiPickerVisible by remember { mutableStateOf(false) } var isRecording by remember { mutableStateOf(false) } // Пикер галереи val galleryLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent() ) { uri: Uri? -> uri?.let { val file = copyUriToFile(context, it) file?.let { viewModel.uploadMedia(it) } } } // Загрузка данных чата при входе LaunchedEffect(chatId) { viewModel.setChatId(chatId) } // Автопрокрутка к последнему сообщению LaunchedEffect(state.messages.size) { if (state.messages.isNotEmpty()) { listState.animateScrollToItem(state.messages.size - 1) } } Scaffold( topBar = { TopAppBar( title = { Row(verticalAlignment = Alignment.CenterVertically) { AppAvatar( url = state.chatAvatar, name = state.chatName ?: chatName, size = 36.dp, modifier = Modifier.padding(end = 8.dp) ) Column { Text(state.chatName ?: chatName, style = MaterialTheme.typography.titleMedium) if (state.isTyping) { Text( stringResource(R.string.typing), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary ) } } } }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back)) } }, actions = { if (state.canCall) { IconButton(onClick = { /* Вызов (WebRTC) */ }) { Icon(Icons.Default.Call, contentDescription = stringResource(R.string.call)) } IconButton(onClick = { /* Видеозвонок (WebRTC) */ }) { Icon(Icons.Default.VideoCall, contentDescription = stringResource(R.string.video_call)) } } } ) } ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { // Список сообщений Box( modifier = Modifier .weight(1f) .background(MaterialTheme.colorScheme.background) ) { LazyColumn( state = listState, modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(state.messages) { message -> MessageBubble( message = message, isCurrentUser = message.senderId == viewModel.getCurrentUserId(), senderAvatar = if (message.senderId == viewModel.getCurrentUserId()) null else message.senderAvatar, onReactionClick = { emoji -> viewModel.addReaction(message.id, emoji) } ) } } if (state.isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } // Панель ввода Column { Row( modifier = Modifier .fillMaxWidth() .padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = { isEmojiPickerVisible = !isEmojiPickerVisible }) { Icon( Icons.Default.EmojiEmotions, contentDescription = stringResource(R.string.emoji), tint = if (isEmojiPickerVisible) MaterialTheme.colorScheme.primary else Color.Gray ) } IconButton(onClick = { galleryLauncher.launch("*/*") }) { Icon(Icons.Default.AttachFile, contentDescription = stringResource(R.string.attach)) } TextField( value = textInput, onValueChange = { textInput = it }, modifier = Modifier.weight(1f), placeholder = { Text(stringResource(R.string.message_placeholder)) }, maxLines = 4, colors = TextFieldDefaults.colors( focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent ) ) if (textInput.isBlank()) { IconButton( onClick = { if (!isRecording) { val file = File(context.cacheDir, "voice_${System.currentTimeMillis()}.mp3") voiceRecorder.startRecording(file) isRecording = true } else { voiceRecorder.stopRecording() isRecording = false } } ) { Icon( if (isRecording) Icons.Default.Stop else Icons.Default.Mic, contentDescription = stringResource(R.string.voice_message), tint = if (isRecording) Color.Red else Color.Gray ) } } else { IconButton(onClick = { viewModel.sendMessage(textInput) textInput = "" }) { Icon(Icons.Default.Send, contentDescription = stringResource(R.string.send)) } } } if (isEmojiPickerVisible) { EmojiPicker(onEmojiSelected = { textInput += it }) } } } } }