diff --git a/client-mobile/.gradle/8.5/executionHistory/executionHistory.bin b/client-mobile/.gradle/8.5/executionHistory/executionHistory.bin index df01331..28ed572 100644 Binary files a/client-mobile/.gradle/8.5/executionHistory/executionHistory.bin and b/client-mobile/.gradle/8.5/executionHistory/executionHistory.bin differ diff --git a/client-mobile/.gradle/8.5/executionHistory/executionHistory.lock b/client-mobile/.gradle/8.5/executionHistory/executionHistory.lock index b21e214..bfde4f5 100644 Binary files a/client-mobile/.gradle/8.5/executionHistory/executionHistory.lock and b/client-mobile/.gradle/8.5/executionHistory/executionHistory.lock differ diff --git a/client-mobile/.gradle/8.5/fileHashes/fileHashes.bin b/client-mobile/.gradle/8.5/fileHashes/fileHashes.bin index dd110f5..20fbae6 100644 Binary files a/client-mobile/.gradle/8.5/fileHashes/fileHashes.bin and b/client-mobile/.gradle/8.5/fileHashes/fileHashes.bin differ diff --git a/client-mobile/.gradle/8.5/fileHashes/fileHashes.lock b/client-mobile/.gradle/8.5/fileHashes/fileHashes.lock index 25094ab..eeb7a89 100644 Binary files a/client-mobile/.gradle/8.5/fileHashes/fileHashes.lock and b/client-mobile/.gradle/8.5/fileHashes/fileHashes.lock differ diff --git a/client-mobile/.gradle/8.5/fileHashes/resourceHashesCache.bin b/client-mobile/.gradle/8.5/fileHashes/resourceHashesCache.bin index 6eefcba..0f6e47a 100644 Binary files a/client-mobile/.gradle/8.5/fileHashes/resourceHashesCache.bin and b/client-mobile/.gradle/8.5/fileHashes/resourceHashesCache.bin differ diff --git a/client-mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/client-mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock index fd2b6a0..52e9f62 100644 Binary files a/client-mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/client-mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/client-mobile/.gradle/buildOutputCleanup/outputFiles.bin b/client-mobile/.gradle/buildOutputCleanup/outputFiles.bin index b63a124..4fec251 100644 Binary files a/client-mobile/.gradle/buildOutputCleanup/outputFiles.bin and b/client-mobile/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/client-mobile/chats/presentation/chat_detail/ChatDetailScreen.kt b/client-mobile/chats/presentation/chat_detail/ChatDetailScreen.kt index f0924c9..afe0a0b 100644 --- a/client-mobile/chats/presentation/chat_detail/ChatDetailScreen.kt +++ b/client-mobile/chats/presentation/chat_detail/ChatDetailScreen.kt @@ -71,7 +71,6 @@ fun ChatDetailScreen( val listState = rememberLazyListState() val scope = rememberCoroutineScope() val voiceRecorder = remember { VoiceRecorder(context) } - var textInput by remember { mutableStateOf("") } var isEmojiPickerVisible by remember { mutableStateOf(false) } var isRecording by remember { mutableStateOf(false) } var recordingOffset by remember { mutableFloatStateOf(0f) } @@ -601,8 +600,8 @@ fun ChatDetailScreen( } BasicTextField( - value = textInput, - onValueChange = { textInput = it }, + value = state.inputText, + onValueChange = { viewModel.onInputTextChanged(it) }, modifier = Modifier .weight(1f) .onFocusChanged { @@ -611,23 +610,14 @@ fun ChatDetailScreen( } }, textStyle = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface), + cursorBrush = androidx.compose.ui.graphics.SolidColor(MaterialTheme.colorScheme.primary), keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Send, + imeAction = ImeAction.Default, capitalization = KeyboardCapitalization.Sentences ), - keyboardActions = KeyboardActions( - onSend = { - if (textInput.isNotBlank() || state.pendingAttachments.isNotEmpty()) { - viewModel.sendMessage(textInput, onFail = { failedText -> - textInput = failedText - }) - textInput = "" - isEmojiPickerVisible = false - } - } - ), + maxLines = 6, decorationBox = { innerTextField -> - if (textInput.isEmpty()) { + if (state.inputText.isEmpty()) { Text( stringResource(R.string.message_placeholder), style = MaterialTheme.typography.bodyLarge, @@ -643,15 +633,12 @@ fun ChatDetailScreen( Spacer(modifier = Modifier.width(8.dp)) // 3. Кнопка действия (в синем квадрате) - val showMic = textInput.isEmpty() && state.pendingAttachments.isEmpty() + val showMic = state.inputText.isEmpty() && state.pendingAttachments.isEmpty() Surface( onClick = { if (!showMic) { - viewModel.sendMessage(textInput, onFail = { failedText -> - textInput = failedText - }) - textInput = "" + viewModel.sendMessage() isEmojiPickerVisible = false } }, @@ -747,7 +734,7 @@ fun ChatDetailScreen( gifCategories = state.gifCategories, isGifsLoading = state.isGifsLoading, error = state.error, - onEmojiSelected = { textInput += it }, + onEmojiSelected = { viewModel.onInputTextChanged(state.inputText + it) }, onGifSelected = { url -> viewModel.sendGif(url) isEmojiPickerVisible = false diff --git a/client-mobile/chats/presentation/chat_detail/ChatDetailViewModel.kt b/client-mobile/chats/presentation/chat_detail/ChatDetailViewModel.kt index d94ea52..25b050c 100644 --- a/client-mobile/chats/presentation/chat_detail/ChatDetailViewModel.kt +++ b/client-mobile/chats/presentation/chat_detail/ChatDetailViewModel.kt @@ -40,7 +40,8 @@ data class ChatDetailState( val initialScrollIndex: Int? = null, val pendingAttachments: List = emptyList(), val isUploading: Boolean = false, - val isCompressionEnabled: Boolean = true + val isCompressionEnabled: Boolean = true, + val inputText: String = "" ) @HiltViewModel @@ -277,11 +278,19 @@ class ChatDetailViewModel @Inject constructor( } } - fun sendMessage(text: String, onFail: (String) -> Unit = {}) { + fun onInputTextChanged(text: String) { + _state.update { it.copy(inputText = text) } + } + + fun sendMessage(onFail: (String) -> Unit = {}) { val chatId = currentChatId ?: return + val text = _state.value.inputText val pending = _state.value.pendingAttachments if (text.isBlank() && pending.isEmpty()) return + // Clear input immediately to avoid double clicks and ensure UI experience + _state.update { it.copy(inputText = "") } + val tempId = "temp_${System.currentTimeMillis()}" val userId = getCurrentUserId()