feat: Update menu icon color for improved visibility in ChatsListScreen

This commit is contained in:
k1ngsterr1
2026-01-17 21:03:19 +05:00
parent 569aa34432
commit c9136ed499
10 changed files with 939 additions and 879 deletions

View File

@@ -105,7 +105,7 @@ fun SetPasswordScreen(
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back",
tint = textColor
tint = textColor.copy(alpha = 0.6f)
)
}
Spacer(modifier = Modifier.weight(1f))

View File

@@ -285,7 +285,7 @@ fun UnlockScreen(
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
tint = secondaryTextColor,
tint = secondaryTextColor.copy(alpha = 0.6f),
modifier = Modifier
.size(24.dp)
.graphicsLayer {
@@ -332,7 +332,7 @@ fun UnlockScreen(
Icon(
Icons.Default.Search,
contentDescription = null,
tint = secondaryTextColor
tint = secondaryTextColor.copy(alpha = 0.6f)
)
},
colors = OutlinedTextFieldDefaults.colors(

View File

@@ -80,7 +80,7 @@ fun WelcomeScreen(
Icon(
Icons.Default.ArrowBack,
contentDescription = "Back",
tint = textColor
tint = textColor.copy(alpha = 0.6f)
)
}
}

View File

@@ -802,7 +802,7 @@ fun ChatDetailScreen(
Icon(
Icons.Default.Call,
contentDescription = "Call",
tint = headerIconColor
tint = headerIconColor.copy(alpha = 0.6f)
)
}
}
@@ -826,7 +826,7 @@ fun ChatDetailScreen(
Icon(
Icons.Default.MoreVert,
contentDescription = "More",
tint = headerIconColor,
tint = headerIconColor.copy(alpha = 0.6f),
modifier = Modifier.size(26.dp)
)
}
@@ -2480,7 +2480,7 @@ private fun MessageInputBar(
Icon(
Icons.Default.AttachFile,
contentDescription = "Attach",
tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93),
tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f) else Color(0xFF8E8E93).copy(alpha = 0.6f),
modifier = Modifier.size(24.dp)
)
}
@@ -2534,7 +2534,7 @@ private fun MessageInputBar(
if (showEmojiPicker) Icons.Default.Keyboard
else Icons.Default.SentimentSatisfiedAlt,
contentDescription = "Emoji",
tint = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93),
tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f) else Color(0xFF8E8E93).copy(alpha = 0.6f),
modifier = Modifier.size(24.dp)
)
}

View File

@@ -1,8 +1,10 @@
package com.rosetta.messenger.ui.chats
import android.content.Context
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -19,26 +21,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalContext
import android.content.Context
import com.airbnb.lottie.compose.*
import com.rosetta.messenger.R
import com.rosetta.messenger.data.RecentSearchesManager
import com.rosetta.messenger.database.RosettaDatabase
import com.rosetta.messenger.network.ProtocolManager
import com.rosetta.messenger.network.ProtocolState
import com.rosetta.messenger.ui.components.AppleEmojiText
@@ -255,33 +250,33 @@ fun ChatsListScreen(
)
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 500.dp)
) {
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 500.dp)) {
// Status indicator
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp)
modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp)
) {
Box(
modifier = Modifier
.size(12.dp)
modifier =
Modifier.size(12.dp)
.clip(CircleShape)
.background(
when (protocolState) {
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50)
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107)
ProtocolState.DISCONNECTED -> Color(0xFFF44336)
ProtocolState.AUTHENTICATED ->
Color(0xFF4CAF50)
ProtocolState.CONNECTING,
ProtocolState.CONNECTED,
ProtocolState.HANDSHAKING ->
Color(0xFFFFC107)
ProtocolState.DISCONNECTED ->
Color(0xFFF44336)
}
)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = when (protocolState) {
text =
when (protocolState) {
ProtocolState.DISCONNECTED -> "Disconnected"
ProtocolState.CONNECTING -> "Connecting..."
ProtocolState.CONNECTED -> "Connected"
@@ -301,9 +296,7 @@ fun ChatsListScreen(
// Logs header with copy button
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
@@ -316,30 +309,38 @@ fun ChatsListScreen(
TextButton(
onClick = {
val logsText = debugLogs.joinToString("\n")
clipboardManager.setText(androidx.compose.ui.text.AnnotatedString(logsText))
clipboardManager.setText(
androidx.compose.ui.text.AnnotatedString(logsText)
)
android.widget.Toast.makeText(
context,
"Logs copied to clipboard!",
android.widget.Toast.LENGTH_SHORT
).show()
)
.show()
},
enabled = debugLogs.isNotEmpty()
) {
Text(
"Copy All",
fontSize = 12.sp,
color = if (debugLogs.isNotEmpty()) PrimaryBlue else Color.Gray
color =
if (debugLogs.isNotEmpty()) PrimaryBlue
else Color.Gray
)
}
}
// Logs content
Box(
modifier = Modifier
.fillMaxWidth()
modifier =
Modifier.fillMaxWidth()
.weight(1f, fill = false)
.clip(RoundedCornerShape(8.dp))
.background(if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF5F5F5))
.background(
if (isDarkTheme) Color(0xFF1A1A1A)
else Color(0xFFF5F5F5)
)
.padding(8.dp)
) {
if (debugLogs.isEmpty()) {
@@ -351,9 +352,8 @@ fun ChatsListScreen(
)
} else {
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(scrollState)
modifier =
Modifier.fillMaxWidth().verticalScroll(scrollState)
) {
debugLogs.forEach { log ->
Text(
@@ -361,8 +361,8 @@ fun ChatsListScreen(
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = textColor,
modifier = Modifier
.fillMaxWidth()
modifier =
Modifier.fillMaxWidth()
.padding(vertical = 2.dp)
)
}
@@ -376,18 +376,20 @@ fun ChatsListScreen(
ProtocolManager.enableUILogs(!debugLogs.isNotEmpty())
android.widget.Toast.makeText(
context,
if (debugLogs.isEmpty()) "Logs enabled" else "Logs disabled",
if (debugLogs.isEmpty()) "Logs enabled"
else "Logs disabled",
android.widget.Toast.LENGTH_SHORT
).show()
)
.show()
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
) {
Text(
if (debugLogs.isEmpty()) "⚠️ Enable Logs" else "Disable Logs",
fontSize = 12.sp,
color = if (debugLogs.isEmpty()) Color(0xFFFFC107) else Color.Gray
color =
if (debugLogs.isEmpty()) Color(0xFFFFC107)
else Color.Gray
)
}
}
@@ -395,12 +397,8 @@ fun ChatsListScreen(
confirmButton = {
Button(
onClick = { showStatusDialog = false },
colors = ButtonDefaults.buttonColors(
containerColor = PrimaryBlue
)
) {
Text("Close", color = Color.White)
}
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)
) { Text("Close", color = Color.White) }
},
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
)
@@ -413,28 +411,31 @@ fun ChatsListScreen(
drawerContent = {
ModalDrawerSheet(
drawerContainerColor = Color.Transparent,
windowInsets = WindowInsets(0), // 🎨 Убираем системные отступы - drawer идет до верха
windowInsets =
WindowInsets(
0
), // 🎨 Убираем системные отступы - drawer идет до верха
modifier = Modifier.width(300.dp)
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(drawerBackgroundColor)
modifier = Modifier.fillMaxSize().background(drawerBackgroundColor)
) {
// ═══════════════════════════════════════════════════════════
// 🎨 DRAWER HEADER - Avatar and status
// ═══════════════════════════════════════════════════════════
val headerColor = if (isDarkTheme) {
val headerColor =
if (isDarkTheme) {
Color(0xFF2C5282)
} else {
Color(0xFF4A90D9)
}
Box(
modifier = Modifier
.fillMaxWidth()
modifier =
Modifier.fillMaxWidth()
.background(color = headerColor)
.statusBarsPadding() // 🎨 Контент начинается после status bar
.statusBarsPadding() // 🎨 Контент начинается
// после status bar
.padding(
top = 16.dp,
start = 20.dp,
@@ -446,13 +447,17 @@ fun ChatsListScreen(
// Avatar with border
val avatarColors = getAvatarColor(accountPublicKey, isDarkTheme)
Box(
modifier = Modifier
.size(72.dp)
modifier =
Modifier.size(72.dp)
.clip(CircleShape)
.background(Color.White.copy(alpha = 0.2f))
.background(
Color.White.copy(alpha = 0.2f)
)
.padding(3.dp)
.clip(CircleShape)
.background(avatarColors.backgroundColor),
.background(
avatarColors.backgroundColor
),
contentAlignment = Alignment.Center
) {
Text(
@@ -488,12 +493,30 @@ fun ChatsListScreen(
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White,
fontStyle = if (isCopied) androidx.compose.ui.text.font.FontStyle.Italic else androidx.compose.ui.text.font.FontStyle.Normal,
modifier = Modifier.clickable {
fontStyle =
if (isCopied)
androidx.compose.ui.text.font
.FontStyle.Italic
else
androidx.compose.ui.text.font
.FontStyle.Normal,
modifier =
Modifier.clickable {
if (!showCopiedToast) {
// Копируем публичный ключ
val clipboard = context.getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("Public Key", accountPublicKey)
val clipboard =
context.getSystemService(
android.content
.Context
.CLIPBOARD_SERVICE
) as
android.content.ClipboardManager
val clip =
android.content.ClipData
.newPlainText(
"Public Key",
accountPublicKey
)
clipboard.setPrimaryClip(clip)
showCopiedToast = true
}
@@ -514,22 +537,29 @@ fun ChatsListScreen(
// Connection status indicator
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { showStatusDialog = true }
modifier =
Modifier.clickable { showStatusDialog = true }
) {
val statusColor = when (protocolState) {
val statusColor =
when (protocolState) {
ProtocolState.AUTHENTICATED -> Color(0xFF4ADE80)
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFBBF24)
ProtocolState.CONNECTING,
ProtocolState.CONNECTED,
ProtocolState.HANDSHAKING -> Color(0xFFFBBF24)
else -> Color(0xFFF87171)
}
val statusText = when (protocolState) {
val statusText =
when (protocolState) {
ProtocolState.AUTHENTICATED -> "Online"
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> "Connecting..."
ProtocolState.CONNECTING,
ProtocolState.CONNECTED,
ProtocolState.HANDSHAKING -> "Connecting..."
else -> "Offline"
}
Box(
modifier = Modifier
.size(8.dp)
modifier =
Modifier.size(8.dp)
.clip(CircleShape)
.background(statusColor)
)
@@ -547,13 +577,13 @@ fun ChatsListScreen(
// 📱 MENU ITEMS
// ═══════════════════════════════════════════════════════════
Column(
modifier = Modifier
.fillMaxWidth()
modifier =
Modifier.fillMaxWidth()
.weight(1f)
.verticalScroll(rememberScrollState())
.padding(vertical = 8.dp)
) {
val menuIconColor = if (isDarkTheme) Color(0xFFB0B0B0) else Color(0xFF5C5C5C)
val menuIconColor = textColor.copy(alpha = 0.6f)
// 👤 Profile Section
DrawerMenuItemEnhanced(
@@ -633,7 +663,9 @@ fun ChatsListScreen(
// 🌓 Theme Toggle
DrawerMenuItemEnhanced(
icon = if (isDarkTheme) Icons.Outlined.LightMode else Icons.Outlined.DarkMode,
icon =
if (isDarkTheme) Icons.Outlined.LightMode
else Icons.Outlined.DarkMode,
text = if (isDarkTheme) "Light Mode" else "Dark Mode",
iconColor = menuIconColor,
textColor = textColor,
@@ -656,11 +688,11 @@ fun ChatsListScreen(
// ═══════════════════════════════════════════════════════════
// 🚪 FOOTER - Logout & Version
// ═══════════════════════════════════════════════════════════
Column(
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.fillMaxWidth()) {
Divider(
color = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8),
color =
if (isDarkTheme) Color(0xFF2A2A2A)
else Color(0xFFE8E8E8),
thickness = 0.5.dp
)
@@ -681,15 +713,20 @@ fun ChatsListScreen(
// Version info
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 12.dp),
modifier =
Modifier.fillMaxWidth()
.padding(
horizontal = 20.dp,
vertical = 12.dp
),
contentAlignment = Alignment.CenterStart
) {
Text(
text = "Rosetta v1.0.0",
fontSize = 12.sp,
color = if (isDarkTheme) Color(0xFF666666) else Color(0xFF999999)
color =
if (isDarkTheme) Color(0xFF666666)
else Color(0xFF999999)
)
}
@@ -703,19 +740,27 @@ fun ChatsListScreen(
topBar = {
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(300)) + expandVertically(
animationSpec = tween(300, easing = FastOutSlowInEasing)
),
exit = fadeOut(tween(200)) + shrinkVertically(
animationSpec = tween(200)
enter =
fadeIn(tween(300)) +
expandVertically(
animationSpec =
tween(
300,
easing = FastOutSlowInEasing
)
),
exit =
fadeOut(tween(200)) +
shrinkVertically(animationSpec = tween(200))
) {
key(isDarkTheme, showRequestsScreen) {
TopAppBar(
navigationIcon = {
if (showRequestsScreen) {
// Back button for Requests
IconButton(onClick = { showRequestsScreen = false }) {
IconButton(
onClick = { showRequestsScreen = false }
) {
Icon(
Icons.Default.ArrowBack,
contentDescription = "Back",
@@ -732,7 +777,7 @@ fun ChatsListScreen(
Icon(
Icons.Default.Menu,
contentDescription = "Menu",
tint = textColor
tint = textColor.copy(alpha = 0.6f)
)
}
}
@@ -749,7 +794,8 @@ fun ChatsListScreen(
} else {
// Rosetta title with status
Row(
verticalAlignment = Alignment.CenterVertically
verticalAlignment =
Alignment.CenterVertically
) {
Text(
"Rosetta",
@@ -759,17 +805,37 @@ fun ChatsListScreen(
)
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.size(10.dp)
modifier =
Modifier.size(10.dp)
.clip(CircleShape)
.background(
when (protocolState) {
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50)
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107)
ProtocolState.DISCONNECTED -> Color(0xFFF44336)
when (protocolState
) {
ProtocolState
.AUTHENTICATED ->
Color(
0xFF4CAF50
)
ProtocolState
.CONNECTING,
ProtocolState
.CONNECTED,
ProtocolState
.HANDSHAKING ->
Color(
0xFFFFC107
)
ProtocolState
.DISCONNECTED ->
Color(
0xFFF44336
)
}
)
.clickable { showStatusDialog = true }
.clickable {
showStatusDialog =
true
}
)
}
}
@@ -779,22 +845,36 @@ fun ChatsListScreen(
if (!showRequestsScreen) {
IconButton(
onClick = {
if (protocolState == ProtocolState.AUTHENTICATED) {
if (protocolState ==
ProtocolState
.AUTHENTICATED
) {
onSearchClick()
}
},
enabled = protocolState == ProtocolState.AUTHENTICATED
enabled =
protocolState ==
ProtocolState.AUTHENTICATED
) {
Icon(
Icons.Default.Search,
contentDescription = "Search",
tint = if (protocolState == ProtocolState.AUTHENTICATED)
textColor else textColor.copy(alpha = 0.5f)
tint =
if (protocolState ==
ProtocolState
.AUTHENTICATED
)
textColor.copy(alpha = 0.6f)
else
textColor.copy(
alpha = 0.5f
)
)
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = backgroundColor,
scrolledContainerColor = backgroundColor,
navigationIconContentColor = textColor,
@@ -829,7 +909,8 @@ fun ChatsListScreen(
// Main content
Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
// <20> Используем комбинированное состояние для атомарного обновления
// Это предотвращает "дергание" UI когда dialogs и requests обновляются независимо
// Это предотвращает "дергание" UI когда dialogs и requests обновляются
// независимо
val chatsState by chatsViewModel.chatsState.collectAsState()
val requests = chatsState.requests
val requestsCount = chatsState.requestsCount
@@ -841,21 +922,31 @@ fun ChatsListScreen(
if (targetState) {
// Переход на Requests: slide in from right + fade
(slideInHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
animationSpec =
tween(300, easing = FastOutSlowInEasing),
initialOffsetX = { it }
) + fadeIn(tween(300))) togetherWith
(slideOutHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
animationSpec =
tween(
300,
easing = FastOutSlowInEasing
),
targetOffsetX = { -it / 3 }
) + fadeOut(tween(200)))
} else {
// Возврат из Requests: slide out to right
(slideInHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
animationSpec =
tween(300, easing = FastOutSlowInEasing),
initialOffsetX = { -it / 3 }
) + fadeIn(tween(300))) togetherWith
(slideOutHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
animationSpec =
tween(
300,
easing = FastOutSlowInEasing
),
targetOffsetX = { it }
) + fadeOut(tween(200)))
}
@@ -882,7 +973,8 @@ fun ChatsListScreen(
)
} else {
// Show dialogs list
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
val dividerColor =
if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
// 🔥 Берем dialogs из chatsState для консистентности
val currentDialogs = chatsState.dialogs
@@ -895,10 +987,7 @@ fun ChatsListScreen(
isDarkTheme = isDarkTheme,
onClick = { showRequestsScreen = true }
)
Divider(
color = dividerColor,
thickness = 0.5.dp
)
Divider(color = dividerColor, thickness = 0.5.dp)
}
}
@@ -918,18 +1007,15 @@ fun ChatsListScreen(
isBlocked = isBlocked,
isSavedMessages = isSavedMessages,
onClick = {
val user = chatsViewModel.dialogToSearchUser(dialog)
val user =
chatsViewModel.dialogToSearchUser(
dialog
)
onUserSelect(user)
},
onDelete = {
dialogToDelete = dialog
},
onBlock = {
dialogToBlock = dialog
},
onUnblock = {
dialogToUnblock = dialog
}
onDelete = { dialogToDelete = dialog },
onBlock = { dialogToBlock = dialog },
onUnblock = { dialogToUnblock = dialog }
)
// 🔥 СЕПАРАТОР - линия разделения между диалогами
@@ -957,11 +1043,7 @@ fun ChatsListScreen(
onDismissRequest = { dialogToDelete = null },
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
title = {
Text(
"Delete Chat",
fontWeight = FontWeight.Bold,
color = textColor
)
Text("Delete Chat", fontWeight = FontWeight.Bold, color = textColor)
},
text = {
Text(
@@ -974,13 +1056,9 @@ fun ChatsListScreen(
onClick = {
val opponentKey = dialog.opponentKey
dialogToDelete = null
scope.launch {
chatsViewModel.deleteDialog(opponentKey)
}
}
) {
Text("Delete", color = Color(0xFFFF3B30))
scope.launch { chatsViewModel.deleteDialog(opponentKey) }
}
) { Text("Delete", color = Color(0xFFFF3B30)) }
},
dismissButton = {
TextButton(onClick = { dialogToDelete = null }) {
@@ -1018,9 +1096,7 @@ fun ChatsListScreen(
blocklistUpdateTrigger++
}
}
) {
Text("Block", color = Color(0xFFFF3B30))
}
) { Text("Block", color = Color(0xFFFF3B30)) }
},
dismissButton = {
TextButton(onClick = { dialogToBlock = null }) {
@@ -1058,9 +1134,7 @@ fun ChatsListScreen(
blocklistUpdateTrigger++
}
}
) {
Text("Unblock", color = PrimaryBlue)
}
) { Text("Unblock", color = PrimaryBlue) }
},
dismissButton = {
TextButton(onClick = { dialogToUnblock = null }) {
@@ -1223,7 +1297,7 @@ fun ChatItem(chat: Chat, isDarkTheme: Boolean, onClick: () -> Unit) {
Icon(
Icons.Default.PushPin,
contentDescription = "Pinned",
tint = secondaryTextColor,
tint = secondaryTextColor.copy(alpha = 0.6f),
modifier = Modifier.size(16.dp).padding(end = 4.dp)
)
}
@@ -1324,7 +1398,7 @@ fun DrawerMenuItem(
Icon(
imageVector = icon,
contentDescription = null,
tint = textColor,
tint = textColor.copy(alpha = 0.6f),
modifier = Modifier.size(24.dp)
)
@@ -1335,8 +1409,8 @@ fun DrawerMenuItem(
}
/**
* 🔥 Swipeable wrapper для DialogItem с кнопками Block и Delete
* Свайп влево показывает действия (как в React Native версии)
* 🔥 Swipeable wrapper для DialogItem с кнопками Block и Delete Свайп влево показывает действия
* (как в React Native версии)
*/
@Composable
fun SwipeableDialogItem(
@@ -1360,32 +1434,26 @@ fun SwipeableDialogItem(
val itemHeight = 80.dp
// Анимация возврата
val animatedOffsetX by animateFloatAsState(
val animatedOffsetX by
animateFloatAsState(
targetValue = offsetX,
animationSpec = spring(dampingRatio = 0.7f, stiffness = 400f),
label = "swipeOffset"
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(itemHeight)
.clipToBounds()
) {
Box(modifier = Modifier.fillMaxWidth().height(itemHeight).clipToBounds()) {
// 1. КНОПКИ - позиционированы справа, всегда видны при свайпе
Row(
modifier = Modifier
.align(Alignment.CenterEnd)
.height(itemHeight)
.width(swipeWidthDp)
) {
Row(modifier = Modifier.align(Alignment.CenterEnd).height(itemHeight).width(swipeWidthDp)) {
// Кнопка Block/Unblock (только если не Saved Messages)
if (!isSavedMessages) {
Box(
modifier = Modifier
.width(80.dp)
modifier =
Modifier.width(80.dp)
.fillMaxHeight()
.background(if (isBlocked) Color(0xFF4CAF50) else Color(0xFFFF6B6B))
.background(
if (isBlocked) Color(0xFF4CAF50)
else Color(0xFFFF6B6B)
)
.clickable {
if (isBlocked) onUnblock() else onBlock()
offsetX = 0f
@@ -1397,7 +1465,9 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = if (isBlocked) Icons.Default.LockOpen else Icons.Default.Block,
imageVector =
if (isBlocked) Icons.Default.LockOpen
else Icons.Default.Block,
contentDescription = if (isBlocked) "Unblock" else "Block",
tint = Color.White,
modifier = Modifier.size(22.dp)
@@ -1415,8 +1485,8 @@ fun SwipeableDialogItem(
// Кнопка Delete
Box(
modifier = Modifier
.width(80.dp)
modifier =
Modifier.width(80.dp)
.fillMaxHeight()
.background(PrimaryBlue)
.clickable {
@@ -1449,8 +1519,8 @@ fun SwipeableDialogItem(
// 2. КОНТЕНТ - поверх кнопок, сдвигается при свайпе
Column(
modifier = Modifier
.fillMaxSize()
modifier =
Modifier.fillMaxSize()
.offset { IntOffset(animatedOffsetX.toInt(), 0) }
.background(backgroundColor)
.pointerInput(Unit) {
@@ -1463,9 +1533,7 @@ fun SwipeableDialogItem(
offsetX = 0f
}
},
onDragCancel = {
offsetX = 0f
},
onDragCancel = { offsetX = 0f },
onHorizontalDrag = { _, dragAmount ->
// Только свайп влево (отрицательное значение)
val newOffset = offsetX + dragAmount
@@ -1494,16 +1562,27 @@ fun SwipeableDialogItem(
/** Элемент диалога из базы данных - ОПТИМИЗИРОВАННЫЙ (без сепаратора для SwipeableDialogItem) */
@Composable
fun DialogItemContent(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boolean = false, onClick: () -> Unit) {
fun DialogItemContent(
dialog: DialogUiModel,
isDarkTheme: Boolean,
isTyping: Boolean = false,
onClick: () -> Unit
) {
// 🔥 ОПТИМИЗАЦИЯ: Кешируем цвета и строки
val textColor = remember(isDarkTheme) { if (isDarkTheme) Color.White else Color.Black }
val secondaryTextColor = remember(isDarkTheme) { if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) }
val secondaryTextColor =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) }
val avatarColors = remember(dialog.opponentKey, isDarkTheme) { getAvatarColor(dialog.opponentKey, isDarkTheme) }
val displayName = remember(dialog.opponentTitle, dialog.opponentKey) {
val avatarColors =
remember(dialog.opponentKey, isDarkTheme) {
getAvatarColor(dialog.opponentKey, isDarkTheme)
}
val displayName =
remember(dialog.opponentTitle, dialog.opponentKey) {
dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
}
val initials = remember(dialog.opponentTitle, dialog.opponentKey) {
val initials =
remember(dialog.opponentTitle, dialog.opponentKey) {
if (dialog.opponentTitle.isNotEmpty()) {
dialog.opponentTitle
.split(" ")
@@ -1549,8 +1628,7 @@ fun DialogItemContent(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boo
.offset(x = (-2).dp, y = (-2).dp)
.clip(CircleShape)
.background(
if (isDarkTheme) Color(0xFF1C1C1E)
else Color.White
if (isDarkTheme) Color(0xFF1C1C1E) else Color.White
)
.padding(3.dp)
.clip(CircleShape)
@@ -1601,8 +1679,12 @@ fun DialogItemContent(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boo
AppleEmojiText(
text = dialog.lastMessage.ifEmpty { "No messages" },
fontSize = 14.sp,
color = if (dialog.unreadCount > 0) textColor.copy(alpha = 0.85f) else secondaryTextColor,
fontWeight = if (dialog.unreadCount > 0) FontWeight.Medium else FontWeight.Normal,
color =
if (dialog.unreadCount > 0) textColor.copy(alpha = 0.85f)
else secondaryTextColor,
fontWeight =
if (dialog.unreadCount > 0) FontWeight.Medium
else FontWeight.Normal,
maxLines = 1,
overflow = android.text.TextUtils.TruncateAt.END,
modifier = Modifier.weight(1f)
@@ -1612,7 +1694,8 @@ fun DialogItemContent(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boo
// Unread badge
if (dialog.unreadCount > 0) {
Spacer(modifier = Modifier.width(8.dp))
val unreadText = when {
val unreadText =
when {
dialog.unreadCount > 999 -> "999+"
dialog.unreadCount > 99 -> "99+"
else -> dialog.unreadCount.toString()
@@ -1641,8 +1724,7 @@ fun DialogItemContent(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boo
}
/**
* 🔥 Компактный индикатор typing для списка чатов
* Голубой текст "typing" с анимированными точками
* 🔥 Компактный индикатор typing для списка чатов Голубой текст "typing" с анимированными точками
*/
@Composable
fun TypingIndicatorSmall() {
@@ -1653,20 +1735,18 @@ fun TypingIndicatorSmall() {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(1.dp)
) {
Text(
text = "typing",
fontSize = 14.sp,
color = typingColor,
fontWeight = FontWeight.Medium
)
Text(text = "typing", fontSize = 14.sp, color = typingColor, fontWeight = FontWeight.Medium)
// 3 анимированные точки
repeat(3) { index ->
val offsetY by infiniteTransition.animateFloat(
val offsetY by
infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = -3f,
animationSpec = infiniteRepeatable(
animation = tween(
animationSpec =
infiniteRepeatable(
animation =
tween(
durationMillis = 500,
delayMillis = index * 120,
easing = FastOutSlowInEasing
@@ -1687,21 +1767,15 @@ fun TypingIndicatorSmall() {
}
}
/**
* 📬 Секция Requests - кнопка для перехода к списку запросов
*/
/** 📬 Секция Requests - кнопка для перехода к списку запросов */
@Composable
fun RequestsSection(
count: Int,
isDarkTheme: Boolean,
onClick: () -> Unit
) {
fun RequestsSection(count: Int, isDarkTheme: Boolean, onClick: () -> Unit) {
val textColor = if (isDarkTheme) Color(0xFF4DABF7) else Color(0xFF228BE6)
val arrowColor = if (isDarkTheme) Color(0xFFC9C9C9) else Color(0xFF228BE6)
Row(
modifier = Modifier
.fillMaxWidth()
modifier =
Modifier.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.SpaceBetween,
@@ -1717,15 +1791,13 @@ fun RequestsSection(
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = "Open requests",
tint = arrowColor,
tint = arrowColor.copy(alpha = 0.6f),
modifier = Modifier.size(24.dp)
)
}
}
/**
* 📬 Экран со списком Requests (без хедера - хедер в основном TopAppBar)
*/
/** 📬 Экран со списком Requests (без хедера - хедер в основном TopAppBar) */
@Composable
fun RequestsScreen(
requests: List<DialogUiModel>,
@@ -1736,17 +1808,11 @@ fun RequestsScreen(
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
Column(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
) {
Column(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
if (requests.isEmpty()) {
// Empty state
Box(
modifier = Modifier
.fillMaxSize()
.padding(32.dp),
modifier = Modifier.fillMaxSize().padding(32.dp),
contentAlignment = Alignment.Center
) {
Text(
@@ -1758,9 +1824,7 @@ fun RequestsScreen(
}
} else {
// Requests list
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(requests, key = { it.opponentKey }) { request ->
DialogItemContent(
dialog = request,
@@ -1780,9 +1844,7 @@ fun RequestsScreen(
}
}
/**
* 🎨 Enhanced Drawer Menu Item - красивый пункт меню с hover эффектом
*/
/** 🎨 Enhanced Drawer Menu Item - красивый пункт меню с hover эффектом */
@Composable
fun DrawerMenuItemEnhanced(
icon: androidx.compose.ui.graphics.vector.ImageVector,
@@ -1793,8 +1855,8 @@ fun DrawerMenuItemEnhanced(
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
modifier =
Modifier.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 20.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
@@ -1818,8 +1880,8 @@ fun DrawerMenuItemEnhanced(
badge?.let {
Box(
modifier = Modifier
.background(
modifier =
Modifier.background(
color = Color(0xFF4A90D9),
shape = RoundedCornerShape(10.dp)
)
@@ -1836,9 +1898,7 @@ fun DrawerMenuItemEnhanced(
}
}
/**
* 📏 Drawer Divider - разделитель между секциями
*/
/** 📏 Drawer Divider - разделитель между секциями */
@Composable
fun DrawerDivider(isDarkTheme: Boolean) {
Spacer(modifier = Modifier.height(8.dp))

View File

@@ -136,7 +136,7 @@ fun ForwardChatPickerBottomSheet(
Icon(
Icons.Default.Close,
contentDescription = "Close",
tint = secondaryTextColor
tint = secondaryTextColor.copy(alpha = 0.6f)
)
}
}

View File

@@ -126,7 +126,7 @@ fun SearchScreen(
Icon(
Icons.Default.ArrowBack,
contentDescription = "Back",
tint = textColor
tint = textColor.copy(alpha = 0.6f)
)
}
@@ -189,7 +189,7 @@ fun SearchScreen(
Icon(
Icons.Default.Clear,
contentDescription = "Clear",
tint = secondaryTextColor
tint = secondaryTextColor.copy(alpha = 0.6f)
)
}
}
@@ -381,7 +381,7 @@ private fun RecentUserItem(
Icon(
Icons.Default.Close,
contentDescription = "Remove",
tint = secondaryTextColor,
tint = secondaryTextColor.copy(alpha = 0.6f),
modifier = Modifier.size(20.dp)
)
}