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 25f8bf7..6c06cf0 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 @@ -11,6 +11,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.only @@ -51,6 +52,8 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalDensity @@ -804,61 +807,21 @@ fun ChatDetailScreen( } } - // Кнопка меню с выпадающим списком - Box { - IconButton( - onClick = { - // 🔥 НЕ закрываем клавиатуру при открытии меню - showMenu = true - }, - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - ) { - Icon( - Icons.Default.MoreVert, - contentDescription = "More", - tint = headerIconColor, - modifier = Modifier.size(26.dp) - ) - } - - // 🔥 Современное выпадающее меню в стиле iOS/Telegram - ModernPopupMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false }, - isDarkTheme = isDarkTheme - ) { - // Block/Unblock User (не показываем для Saved Messages) - if (!isSavedMessages) { - ModernMenuItem( - icon = if (isBlocked) Icons.Default.CheckCircle else Icons.Default.Block, - text = if (isBlocked) "Unblock User" else "Block User", - onClick = { - showMenu = false - if (isBlocked) { - showUnblockConfirm = true - } else { - showBlockConfirm = true - } - }, - isDarkTheme = isDarkTheme, - tintColor = PrimaryBlue - ) - } - - // Delete Chat - деструктивное действие - ModernMenuItem( - icon = Icons.Default.Delete, - text = "Delete Chat", - onClick = { - showMenu = false - showDeleteConfirm = true - }, - isDarkTheme = isDarkTheme, - isDestructive = true - ) - } + // Кнопка меню - открывает bottom sheet + IconButton( + onClick = { + showMenu = true + }, + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + ) { + Icon( + Icons.Default.MoreVert, + contentDescription = "More", + tint = headerIconColor, + modifier = Modifier.size(26.dp) + ) } } } @@ -1435,7 +1398,68 @@ fun ChatDetailScreen( ) } - // 📨 Forward Chat Picker BottomSheet + // � Bottom Sheet меню (вместо popup menu) + if (showMenu) { + ModalBottomSheet( + onDismissRequest = { showMenu = false }, + containerColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White, + shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp), + dragHandle = { + Box( + modifier = Modifier + .padding(vertical = 12.dp) + .width(36.dp) + .height(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(if (isDarkTheme) Color.White.copy(alpha = 0.3f) else Color.Black.copy(alpha = 0.2f)) + ) + } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 32.dp) + ) { + // Block/Unblock User + if (!isSavedMessages) { + BottomSheetMenuItem( + icon = if (isBlocked) Icons.Default.CheckCircle else Icons.Default.Block, + text = if (isBlocked) "Unblock User" else "Block User", + onClick = { + showMenu = false + if (isBlocked) { + showUnblockConfirm = true + } else { + showBlockConfirm = true + } + }, + isDarkTheme = isDarkTheme, + tintColor = PrimaryBlue + ) + + Divider( + modifier = Modifier.padding(horizontal = 16.dp), + thickness = 0.5.dp, + color = if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f) + ) + } + + // Delete Chat + BottomSheetMenuItem( + icon = Icons.Default.Delete, + text = "Delete Chat", + onClick = { + showMenu = false + showDeleteConfirm = true + }, + isDarkTheme = isDarkTheme, + isDestructive = true + ) + } + } + } + + // �📨 Forward Chat Picker BottomSheet if (showForwardPicker) { ForwardChatPickerBottomSheet( dialogs = dialogsList, @@ -2628,8 +2652,9 @@ private fun SkeletonBubble( } /** - * 🔥 Современное выпадающее меню в стиле iOS/Telegram - * С blur эффектом, красивыми тенями и плавными анимациями + * 🔥 Современное выпадающее меню в стиле Telegram + * Использует Popup вместо Material DropdownMenu + * С красивыми анимациями как в оригинальном Telegram */ @Composable private fun ModernPopupMenu( @@ -2638,64 +2663,75 @@ private fun ModernPopupMenu( isDarkTheme: Boolean, content: @Composable ColumnScope.() -> Unit ) { - // Анимация появления - val transition = updateTransition(targetState = expanded, label = "menu") + if (!expanded) return - val scale by transition.animateFloat( - transitionSpec = { - if (targetState) { - spring(dampingRatio = 0.8f, stiffness = 400f) - } else { - tween(150, easing = FastOutSlowInEasing) - } - }, + // Анимация появления в стиле Telegram + val scale by animateFloatAsState( + targetValue = if (expanded) 1f else 0.3f, + animationSpec = spring(dampingRatio = 0.75f, stiffness = 350f), label = "scale" - ) { if (it) 1f else 0.92f } + ) - val alpha by transition.animateFloat( - transitionSpec = { - if (targetState) tween(200) else tween(100) - }, + val alpha by animateFloatAsState( + targetValue = if (expanded) 1f else 0f, + animationSpec = tween(150, easing = FastOutSlowInEasing), label = "alpha" - ) { if (it) 1f else 0f } + ) - // Цвета меню + // Цвета меню с акцентным оттенком val menuBackgroundColor = if (isDarkTheme) { - Color(0xFF2C2C2E) // iOS dark mode menu color + Color(0xFF212121) // Telegram dark menu } else { - Color(0xFFFFFFFF) + Color.White } - DropdownMenu( - expanded = expanded, + val accentBorderColor = PrimaryBlue.copy(alpha = 0.3f) // Тонкая акцентная обводка + + Popup( + alignment = Alignment.TopEnd, + offset = IntOffset(-16, 60), // Отступ от кнопки меню onDismissRequest = onDismissRequest, - modifier = Modifier - .graphicsLayer { - scaleX = scale - scaleY = scale - this.alpha = alpha - transformOrigin = TransformOrigin(0.85f, 0f) - } - .width(200.dp) - .shadow( - elevation = 16.dp, - shape = RoundedCornerShape(14.dp), - ambientColor = Color.Black.copy(alpha = 0.12f), - spotColor = Color.Black.copy(alpha = 0.08f) - ) - .clip(RoundedCornerShape(14.dp)) - .background(menuBackgroundColor) + properties = PopupProperties( + focusable = true, + dismissOnBackPress = true, + dismissOnClickOutside = true + ) ) { - content() + Column( + modifier = Modifier + .graphicsLayer { + scaleX = scale + scaleY = scale + this.alpha = alpha + transformOrigin = TransformOrigin(1f, 0f) // Анимация от правого верхнего угла + } + .width(220.dp) + .shadow( + elevation = 8.dp, + shape = RoundedCornerShape(12.dp), + spotColor = PrimaryBlue.copy(alpha = 0.2f), + ambientColor = PrimaryBlue.copy(alpha = 0.1f) + ) + .border( + width = 1.dp, + color = accentBorderColor, + shape = RoundedCornerShape(12.dp) + ) + .clip(RoundedCornerShape(12.dp)) + .background(menuBackgroundColor) + .padding(vertical = 8.dp) + ) { + content() + } } } /** - * 🔥 Современный элемент меню с иконкой - * Плавные hover эффекты и красивая типографика + * 🔥 Элемент меню для Bottom Sheet + * Красивый дизайн с большими отступами для удобного нажатия */ @Composable -private fun ModernMenuItem( +private fun BottomSheetMenuItem( icon: ImageVector, text: String, onClick: () -> Unit, @@ -2703,7 +2739,7 @@ private fun ModernMenuItem( tintColor: Color = if (isDarkTheme) Color.White else Color.Black, isDestructive: Boolean = false ) { - val actualTintColor = if (isDestructive) Color(0xFFFF3B30) else tintColor // iOS red + val actualTintColor = if (isDestructive) Color(0xFFFF3B30) else tintColor val textColor = if (isDestructive) { Color(0xFFFF3B30) } else if (isDarkTheme) { @@ -2712,32 +2748,40 @@ private fun ModernMenuItem( Color.Black } - // Hover/pressed состояние + // Ripple эффект при нажатии val interactionSource = remember { MutableInteractionSource() } + val isPressed = interactionSource.collectIsPressedAsState() + + val backgroundColor = if (isPressed.value) { + if (isDarkTheme) Color.White.copy(alpha = 0.08f) + else Color.Black.copy(alpha = 0.04f) + } else { + Color.Transparent + } Row( modifier = Modifier .fillMaxWidth() + .background(backgroundColor) .clickable( interactionSource = interactionSource, indication = null ) { onClick() } - .padding(horizontal = 16.dp, vertical = 12.dp), + .padding(horizontal = 24.dp, vertical = 20.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = icon, contentDescription = null, tint = actualTintColor, - modifier = Modifier.size(22.dp) + modifier = Modifier.size(28.dp) ) - Spacer(modifier = Modifier.width(12.dp)) + Spacer(modifier = Modifier.width(20.dp)) Text( text = text, color = textColor, - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - letterSpacing = (-0.2).sp // iOS стиль + fontSize = 18.sp, + fontWeight = FontWeight.Medium ) } }