From da065ef7f7ee8dbe2ae40581a5253a870da2dcae Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Fri, 16 Jan 2026 23:13:10 +0500 Subject: [PATCH] feat: Implement modern popup menu with iOS/Telegram style; enhance user interaction with smooth animations and improved design --- .../messenger/ui/chats/ChatDetailScreen.kt | 248 +++++++++++------- 1 file changed, 146 insertions(+), 102 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 86003a6..25f8bf7 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 @@ -43,6 +43,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path @@ -822,113 +823,41 @@ fun ChatDetailScreen( ) } - // Выпадающее меню - чистый дизайн без артефактов - MaterialTheme( - colorScheme = MaterialTheme.colorScheme.copy( - surface = inputBackgroundColor - ) + // 🔥 Современное выпадающее меню в стиле iOS/Telegram + ModernPopupMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + isDarkTheme = isDarkTheme ) { - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false }, - modifier = Modifier - .width(220.dp) - .background( - color = inputBackgroundColor, - shape = RoundedCornerShape(16.dp) - ) - ) { - // Delete Chat - красный - DropdownMenuItem( - text = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(vertical = 4.dp) - ) { - Icon( - Icons.Default.Delete, - contentDescription = null, - tint = Color(0xFFE53935), - modifier = Modifier.size(22.dp) - ) - Spacer(modifier = Modifier.width(14.dp)) - Text( - "Delete Chat", - color = Color(0xFFE53935), - fontSize = 16.sp, - fontWeight = FontWeight.Medium - ) - } - }, + // 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 - showDeleteConfirm = true + if (isBlocked) { + showUnblockConfirm = true + } else { + showBlockConfirm = true + } }, - modifier = Modifier.padding(horizontal = 8.dp) - .background(inputBackgroundColor), - colors = MenuDefaults.itemColors( - textColor = Color(0xFFE53935) - ) + isDarkTheme = isDarkTheme, + tintColor = PrimaryBlue + ) + } + + // Delete Chat - деструктивное действие + ModernMenuItem( + icon = Icons.Default.Delete, + text = "Delete Chat", + onClick = { + showMenu = false + showDeleteConfirm = true + }, + isDarkTheme = isDarkTheme, + isDestructive = true ) - - // Разделитель - if (!isSavedMessages) { - 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) - ) - } - - // Block/Unblock User - синий (не показываем для Saved Messages) - if (!isSavedMessages) { - DropdownMenuItem( - text = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(vertical = 4.dp) - ) { - Icon( - if (isBlocked) Icons.Default.Check else Icons.Default.Block, - contentDescription = null, - tint = PrimaryBlue, - modifier = Modifier.size(22.dp) - ) - Spacer(modifier = Modifier.width(14.dp)) - Text( - if (isBlocked) "Unblock User" else "Block User", - color = PrimaryBlue, - fontSize = 16.sp, - fontWeight = FontWeight.Medium - ) - } - }, - onClick = { - showMenu = false - if (isBlocked) { - showUnblockConfirm = true - } else { - showBlockConfirm = true - } - }, - modifier = Modifier.padding(horizontal = 8.dp) - .background(inputBackgroundColor), - colors = MenuDefaults.itemColors( - textColor = 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) - ) - } - - } } } } @@ -2697,3 +2626,118 @@ private fun SkeletonBubble( ) } } + +/** + * 🔥 Современное выпадающее меню в стиле iOS/Telegram + * С blur эффектом, красивыми тенями и плавными анимациями + */ +@Composable +private fun ModernPopupMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + isDarkTheme: Boolean, + content: @Composable ColumnScope.() -> Unit +) { + // Анимация появления + val transition = updateTransition(targetState = expanded, label = "menu") + + val scale by transition.animateFloat( + transitionSpec = { + if (targetState) { + spring(dampingRatio = 0.8f, stiffness = 400f) + } else { + tween(150, easing = FastOutSlowInEasing) + } + }, + label = "scale" + ) { if (it) 1f else 0.92f } + + val alpha by transition.animateFloat( + transitionSpec = { + if (targetState) tween(200) else tween(100) + }, + label = "alpha" + ) { if (it) 1f else 0f } + + // Цвета меню + val menuBackgroundColor = if (isDarkTheme) { + Color(0xFF2C2C2E) // iOS dark mode menu color + } else { + Color(0xFFFFFFFF) + } + + DropdownMenu( + expanded = expanded, + 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) + ) { + content() + } +} + +/** + * 🔥 Современный элемент меню с иконкой + * Плавные hover эффекты и красивая типографика + */ +@Composable +private fun ModernMenuItem( + icon: ImageVector, + text: String, + onClick: () -> Unit, + isDarkTheme: Boolean, + tintColor: Color = if (isDarkTheme) Color.White else Color.Black, + isDestructive: Boolean = false +) { + val actualTintColor = if (isDestructive) Color(0xFFFF3B30) else tintColor // iOS red + val textColor = if (isDestructive) { + Color(0xFFFF3B30) + } else if (isDarkTheme) { + Color.White + } else { + Color.Black + } + + // Hover/pressed состояние + val interactionSource = remember { MutableInteractionSource() } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable( + interactionSource = interactionSource, + indication = null + ) { onClick() } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = actualTintColor, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = text, + color = textColor, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + letterSpacing = (-0.2).sp // iOS стиль + ) + } +}