feat: Implement modern popup menu with iOS/Telegram style; enhance user interaction with smooth animations and improved design
This commit is contained in:
@@ -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 стиль
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user