From 5f348f329e467ec2881ac60798c7c3758fa3097c Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 12 Jan 2026 01:55:44 +0500 Subject: [PATCH] feat: Add dropdown menu for chat options and confirmation dialogs for delete and block actions --- .../messenger/ui/chats/ChatDetailScreen.kt | 205 ++++++++++++++++-- 1 file changed, 189 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index ec200ec..7ccfbf2 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -54,6 +54,10 @@ import com.rosetta.messenger.ui.components.AppleEmojiText import com.rosetta.messenger.ui.components.AppleEmojiTextField import com.rosetta.messenger.ui.components.VerifiedBadge import com.rosetta.messenger.ui.onboarding.PrimaryBlue +import android.view.inputmethod.InputMethodManager +import android.content.Context +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import java.text.SimpleDateFormat import java.util.* import kotlinx.coroutines.delay @@ -226,6 +230,11 @@ fun ChatDetailScreen( var showLogs by remember { mutableStateOf(false) } // val debugLogs by ProtocolManager.debugLogs.collectAsState() val debugLogs = remember { emptyList() } + + // Состояние выпадающего меню + var showMenu by remember { mutableStateOf(false) } + var showDeleteConfirm by remember { mutableStateOf(false) } + var showBlockConfirm by remember { mutableStateOf(false) } // Подключаем к ViewModel val messages by viewModel.messages.collectAsState() @@ -446,18 +455,82 @@ fun ChatDetailScreen( ) } - IconButton( - onClick = { - keyboardController?.hide() - focusManager.clearFocus() - /* TODO: More options */ + // Кнопка меню с выпадающим списком + Box { + IconButton( + onClick = { + keyboardController?.hide() + focusManager.clearFocus() + showMenu = true + } + ) { + Icon( + Icons.Default.MoreVert, + contentDescription = "More", + tint = textColor + ) + } + + // Выпадающее меню + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + modifier = Modifier + .background( + color = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, + shape = RoundedCornerShape(12.dp) + ) + ) { + // Delete Chat + DropdownMenuItem( + text = { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Default.Delete, + contentDescription = null, + tint = PrimaryBlue, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + "Delete Chat", + color = textColor, + fontSize = 16.sp + ) + } + }, + onClick = { + showMenu = false + showDeleteConfirm = true + } + ) + + // Block User (не показываем для Saved Messages) + if (!isSavedMessages) { + DropdownMenuItem( + text = { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Default.Block, + contentDescription = null, + tint = Color(0xFFFF3B30), + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + "Block User", + color = Color(0xFFFF3B30), + fontSize = 16.sp + ) + } + }, + onClick = { + showMenu = false + showBlockConfirm = true + } + ) } - ) { - Icon( - Icons.Default.MoreVert, - contentDescription = "More", - tint = textColor - ) + } } } // Нижняя линия для разделения @@ -725,6 +798,79 @@ fun ChatDetailScreen( confirmButton = { TextButton(onClick = { showLogs = false }) { Text("Close") } } ) } + + // Диалог подтверждения удаления чата + if (showDeleteConfirm) { + AlertDialog( + onDismissRequest = { showDeleteConfirm = false }, + containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, + title = { + Text( + "Delete Chat", + fontWeight = FontWeight.Bold, + color = textColor + ) + }, + text = { + Text( + "Are you sure you want to delete this chat? This action cannot be undone.", + color = secondaryTextColor + ) + }, + confirmButton = { + TextButton( + onClick = { + showDeleteConfirm = false + // TODO: Implement delete chat + onBack() + } + ) { + Text("Delete", color = Color(0xFFFF3B30)) + } + }, + dismissButton = { + TextButton(onClick = { showDeleteConfirm = false }) { + Text("Cancel", color = PrimaryBlue) + } + } + ) + } + + // Диалог подтверждения блокировки + if (showBlockConfirm) { + AlertDialog( + onDismissRequest = { showBlockConfirm = false }, + containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, + title = { + Text( + "Block ${user.title.ifEmpty { "User" }}", + fontWeight = FontWeight.Bold, + color = textColor + ) + }, + text = { + Text( + "Are you sure you want to block this user? They won't be able to send you messages.", + color = secondaryTextColor + ) + }, + confirmButton = { + TextButton( + onClick = { + showBlockConfirm = false + // TODO: Implement block user + } + ) { + Text("Block", color = Color(0xFFFF3B30)) + } + }, + dismissButton = { + TextButton(onClick = { showBlockConfirm = false }) { + Text("Cancel", color = PrimaryBlue) + } + } + ) + } } /** 🚀 Анимация появления сообщения Telegram-style */ @@ -900,26 +1046,53 @@ private fun MessageInputBar( placeholderColor: Color ) { var showEmojiPicker by remember { mutableStateOf(false) } + // Флаг для запуска закрытия клавиатуры перед открытием emoji picker + var pendingEmojiPicker by remember { mutableStateOf(false) } val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current val interactionSource = remember { MutableInteractionSource() } + val scope = rememberCoroutineScope() + + // Получаем context и view для гарантированного закрытия клавиатуры + val context = LocalContext.current + val view = LocalView.current // Состояние отправки val canSend = remember(value) { value.isNotBlank() } // Easing анимации val backEasing = CubicBezierEasing(0.34f, 1.56f, 0.64f, 1f) + + // Функция для гарантированного закрытия клавиатуры через InputMethodManager + fun hideKeyboardCompletely() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.windowToken, 0) + focusManager.clearFocus(force = true) + } + + // Эффект для закрытия клавиатуры и открытия emoji picker + LaunchedEffect(pendingEmojiPicker) { + if (pendingEmojiPicker) { + // Гарантированно закрываем клавиатуру через InputMethodManager + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.windowToken, 0) + focusManager.clearFocus(force = true) + // Ждём пока клавиатура закроется + delay(200) + // Теперь открываем emoji picker + showEmojiPicker = true + pendingEmojiPicker = false + } + } // Функция переключения emoji picker fun toggleEmojiPicker() { if (showEmojiPicker) { + // Закрываем emoji picker showEmojiPicker = false } else { - // Сначала скрываем клавиатуру, затем показываем emoji picker - focusManager.clearFocus(force = true) - keyboardController?.hide() - // Небольшая задержка чтобы клавиатура успела закрыться - showEmojiPicker = true + // Запускаем процесс: сначала закрыть клавиатуру, потом открыть picker + pendingEmojiPicker = true } }