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