feat: Refactor ProfileScreen to adopt Telegram-style components and improve UI consistency

This commit is contained in:
2026-01-21 16:13:24 +05:00
parent dcfbb020be
commit d443592435
4 changed files with 639 additions and 816 deletions

View File

@@ -4,6 +4,9 @@ import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@@ -46,23 +49,20 @@ fun BackupScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(backgroundColor) .background(backgroundColor)
.windowInsetsPadding(WindowInsets.statusBars)
) { ) {
// Top Bar // Telegram-style Header
Surface(
modifier = Modifier.fillMaxWidth(),
color = backgroundColor
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 8.dp), .padding(horizontal = 4.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back", contentDescription = "Back",
tint = if (isDarkTheme) Color.White else Color.Black tint = textColor
) )
} }
Text( Text(
@@ -73,7 +73,6 @@ fun BackupScreen(
modifier = Modifier.padding(start = 8.dp) modifier = Modifier.padding(start = 8.dp)
) )
} }
}
// Content // Content
Column( Column(
@@ -85,27 +84,26 @@ fun BackupScreen(
// Warning Card // Warning Card
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = (if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)).copy(alpha = 0.15f), color = surfaceColor,
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(10.dp)
) { ) {
Row( Column(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Icon( Icon(
imageVector = Icons.Filled.Warning, imageVector = Icons.Filled.Warning,
contentDescription = null, contentDescription = null,
tint = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444), tint = Color(0xFFFFAC00),
modifier = Modifier modifier = Modifier.size(48.dp)
.size(24.dp)
.padding(top = 2.dp)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text( Text(
text = "If you want to give your recovery phrase to someone, please stop! This may lead to the compromise of your conversations.", text = "If you want to give your recovery phrase to someone, please stop! This may lead to the compromise of your conversations.",
fontSize = 13.sp, fontSize = 14.sp,
color = textColor, color = secondaryTextColor,
lineHeight = 18.sp lineHeight = 20.sp,
modifier = Modifier.fillMaxWidth()
) )
} }
} }
@@ -113,19 +111,11 @@ fun BackupScreen(
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
if (seedPhrase == null) { if (seedPhrase == null) {
// Password Input // Password Input Section
Text(
text = "Confirm Password",
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
color = textColor,
modifier = Modifier.padding(bottom = 8.dp)
)
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = surfaceColor, color = surfaceColor,
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(10.dp)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -185,26 +175,18 @@ fun BackupScreen(
} }
Text( Text(
text = "To view your recovery phrase, enter the password you specified when creating your account.", text = "To view your recovery phrase, enter the password you specified when creating your account or restoring from a seed phrase.",
fontSize = 13.sp, fontSize = 14.sp,
color = secondaryTextColor, color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp), modifier = Modifier.padding(top = 8.dp),
lineHeight = 18.sp lineHeight = 20.sp
) )
} else { } else {
// Seed Phrase Display // Seed Phrase Display
Text(
text = "Your Recovery Phrase",
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
color = textColor,
modifier = Modifier.padding(bottom = 8.dp)
)
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = Color(0xFF2E7D32).copy(alpha = 0.1f), color = surfaceColor,
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(10.dp)
) { ) {
Column(modifier = Modifier.padding(20.dp)) { Column(modifier = Modifier.padding(20.dp)) {
val words = seedPhrase!!.split(" ") val words = seedPhrase!!.split(" ")
@@ -228,34 +210,14 @@ fun BackupScreen(
} }
Text( Text(
text = "Please don't share your seed phrase! The administration will never ask you for it. Write it down and store it in a safe place.", text = "Please don't share your seed phrase! The administration will never ask you for it.",
fontSize = 12.sp, fontSize = 14.sp,
color = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444), color = secondaryTextColor,
fontWeight = FontWeight.Medium, modifier = Modifier.padding(top = 8.dp),
modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp), lineHeight = 20.sp
lineHeight = 16.sp
) )
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
// TODO: Copy to clipboard
},
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF007AFF)
),
shape = RoundedCornerShape(12.dp)
) {
Text(
text = "Copy to Clipboard",
fontSize = 16.sp,
fontWeight = FontWeight.Medium
)
}
} }
} }
} }

View File

@@ -238,156 +238,80 @@ fun ProfileScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
// ✏️ EDITABLE FIELDS // 📋 ACCOUNT SECTION - Telegram style
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
ProfileSectionTitle(title = "Profile Information", isDarkTheme = isDarkTheme) TelegramSectionTitle(title = "Account", isDarkTheme = isDarkTheme)
Surface( // Name field
modifier = Modifier TelegramTextField(
.fillMaxWidth()
.padding(horizontal = 16.dp),
color = surfaceColor,
shape = RoundedCornerShape(16.dp)
) {
Column {
ProfileEditableField(
label = "Your name",
value = editedName, value = editedName,
onValueChange = { editedName = it }, label = "Your name",
placeholder = "ex. Freddie Gibson",
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
showDivider = true isEditable = true,
onValueChange = { editedName = it },
showDivider = true,
placeholder = "Add your name"
) )
ProfileEditableField(
label = "Username", // Username field
TelegramTextField(
value = editedUsername, value = editedUsername,
onValueChange = { editedUsername = it }, label = "Username",
placeholder = "ex. freddie871", isDarkTheme = isDarkTheme,
isDarkTheme = isDarkTheme isEditable = true,
onValueChange = {
editedUsername = it
},
showDivider = true,
placeholder = "Add username"
) )
}
}
Spacer(modifier = Modifier.height(8.dp)) // Public Key field
TelegramCopyField(
// Public Key Copy Field value = accountPublicKey.take(16) + "..." + accountPublicKey.takeLast(6),
ProfileCopyField( fullValue = accountPublicKey,
label = "Public Key", label = "Public Key",
value = accountPublicKey,
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
Text(
text = "This is your public key. If you haven't set a @username yet, you can ask a friend to message you using your public key.",
fontSize = 12.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
lineHeight = 16.sp
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
// 🔧 SETTINGS SECTION // ⚙️ SETTINGS SECTION - Telegram style
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
ProfileSectionTitle(title = "Settings", isDarkTheme = isDarkTheme) TelegramSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
Surface( TelegramSettingsItem(
modifier = Modifier icon = Icons.Outlined.Palette,
.fillMaxWidth()
.padding(horizontal = 16.dp),
color = surfaceColor,
shape = RoundedCornerShape(16.dp)
) {
Column {
ProfileNavigationItem(
icon = Icons.Outlined.Brush,
iconBackground = Color(0xFF6366F1),
title = "Theme", title = "Theme",
subtitle = "Customize appearance",
onClick = onNavigateToTheme, onClick = onNavigateToTheme,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
showDivider = true showDivider = true
) )
ProfileNavigationItem(
icon = Icons.Outlined.AdminPanelSettings, TelegramSettingsItem(
iconBackground = Color(0xFF9333EA), icon = Icons.Outlined.Lock,
title = "Safety", title = "Safety",
subtitle = "Backup and security settings",
onClick = onNavigateToSafety, onClick = onNavigateToSafety,
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme,
) showDivider = true
}
}
Text(
text = "You can learn more about your safety on the safety page, please make sure you are viewing the screen alone before proceeding to the safety page.",
fontSize = 12.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
lineHeight = 16.sp
) )
Spacer(modifier = Modifier.height(24.dp)) TelegramSettingsItem(
icon = Icons.Outlined.Terminal,
// ═════════════════════════════════════════════════════════════
// 🐛 DEBUG / LOGS SECTION
// ═════════════════════════════════════════════════════════════
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
color = surfaceColor,
shape = RoundedCornerShape(16.dp)
) {
ProfileNavigationItem(
icon = Icons.Outlined.BugReport,
iconBackground = Color(0xFFFB8C00),
title = "View Logs", title = "View Logs",
subtitle = "Debug profile save operations",
onClick = onNavigateToLogs, onClick = onNavigateToLogs,
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
}
Text(
text = "View detailed logs of profile save operations for debugging.",
fontSize = 12.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
lineHeight = 16.sp
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
// 🚪 LOGOUT SECTION // 🚪 LOGOUT - Telegram style (red text)
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
Surface( TelegramLogoutItem(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
color = surfaceColor,
shape = RoundedCornerShape(16.dp)
) {
ProfileNavigationItem(
icon = Icons.Outlined.Logout,
iconBackground = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
title = "Logout",
subtitle = "Sign out of your account",
onClick = onLogout, onClick = onLogout,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme
hideChevron = true,
textColor = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)
)
}
Text(
text = "Logging out of your account. After logging out, you will be redirected to the password entry page.",
fontSize = 12.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
lineHeight = 16.sp
) )
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
@@ -717,60 +641,43 @@ fun ProfileCard(
} }
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
// 📦 HELPER COMPONENTS // <EFBFBD> TELEGRAM-STYLE COMPONENTS
// ═════════════════════════════════════════════════════════════ // ═════════════════════════════════════════════════════════════
@Composable @Composable
private fun ProfileSectionTitle(title: String, isDarkTheme: Boolean) { private fun TelegramSectionTitle(title: String, isDarkTheme: Boolean) {
val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93) val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
Text( Text(
text = title.uppercase(), text = title,
fontSize = 13.sp, fontSize = 15.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.Medium,
color = textColor, color = textColor,
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp) modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
) )
} }
@Composable @Composable
private fun ProfileEditableField( private fun TelegramTextField(
label: String,
value: String, value: String,
onValueChange: (String) -> Unit, label: String,
placeholder: String,
isDarkTheme: Boolean, isDarkTheme: Boolean,
isEditable: Boolean = false,
onValueChange: ((String) -> Unit)? = null,
showDivider: Boolean = false, showDivider: Boolean = false,
leadingText: String? = null placeholder: String = ""
) { ) {
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8) val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0)
Column { Column {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
) { ) {
Text( if (isEditable && onValueChange != null) {
text = label,
fontSize = 13.sp,
color = secondaryTextColor,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(4.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
if (leadingText != null) {
Text(
text = leadingText,
fontSize = 16.sp,
color = textColor
)
Spacer(modifier = Modifier.width(2.dp))
}
BasicTextField( BasicTextField(
value = value, value = value,
onValueChange = onValueChange, onValueChange = onValueChange,
@@ -782,17 +689,31 @@ private fun ProfileEditableField(
singleLine = true, singleLine = true,
cursorBrush = androidx.compose.ui.graphics.SolidColor(textColor), cursorBrush = androidx.compose.ui.graphics.SolidColor(textColor),
decorationBox = { innerTextField -> decorationBox = { innerTextField ->
if (value.isEmpty()) { if (value.isEmpty() && placeholder.isNotEmpty()) {
Text( Text(
text = placeholder, text = placeholder,
color = secondaryTextColor.copy(alpha = 0.5f), fontSize = 16.sp,
fontSize = 16.sp color = secondaryTextColor.copy(alpha = 0.5f)
) )
} }
innerTextField() innerTextField()
} }
) )
} else {
Text(
text = value.ifBlank { placeholder },
fontSize = 16.sp,
color = if (value.isBlank()) secondaryTextColor.copy(alpha = 0.5f) else textColor
)
} }
Spacer(modifier = Modifier.height(4.dp))
Text(
text = label,
fontSize = 13.sp,
color = secondaryTextColor
)
} }
if (showDivider) { if (showDivider) {
@@ -806,26 +727,24 @@ private fun ProfileEditableField(
} }
@Composable @Composable
private fun ProfileCopyField( private fun TelegramCopyField(
label: String,
value: String, value: String,
fullValue: String,
label: String,
isDarkTheme: Boolean isDarkTheme: Boolean
) { ) {
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var showCopied by remember { mutableStateOf(false) } var showCopied by remember { mutableStateOf(false) }
Surface( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp)
.clickable { .clickable {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(label, value) val clip = ClipData.newPlainText(label, fullValue)
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
scope.launch { scope.launch {
@@ -833,45 +752,123 @@ private fun ProfileCopyField(
delay(1500) delay(1500)
showCopied = false showCopied = false
} }
}, }
color = surfaceColor, .padding(horizontal = 16.dp, vertical = 12.dp)
shape = RoundedCornerShape(16.dp)
) { ) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
fontSize = 14.sp,
color = secondaryTextColor,
fontWeight = FontWeight.Medium
)
AnimatedContent( AnimatedContent(
targetState = showCopied, targetState = showCopied,
label = "copy_animation" label = "copy_animation"
) { copied -> ) { copied ->
if (copied) {
Text( Text(
text = "Copied!", text = if (copied) "Copied!" else value,
fontSize = 14.sp, fontSize = 16.sp,
color = Color(0xFF22C55E),
fontWeight = FontWeight.Medium
)
} else {
Text(
text = value.take(16) + "...",
fontSize = 14.sp,
color = textColor color = textColor
) )
} }
Spacer(modifier = Modifier.height(4.dp))
Text(
text = label,
fontSize = 13.sp,
color = secondaryTextColor
)
}
}
@Composable
private fun TelegramSettingsItem(
icon: ImageVector,
title: String,
onClick: () -> Unit,
isDarkTheme: Boolean,
showDivider: Boolean = false
) {
val textColor = if (isDarkTheme) Color.White else Color.Black
val iconColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0)
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = title,
fontSize = 16.sp,
color = textColor,
modifier = Modifier.weight(1f)
)
}
if (showDivider) {
Divider(
color = dividerColor,
thickness = 0.5.dp,
modifier = Modifier.padding(start = 60.dp)
)
} }
} }
} }
@Composable
private fun TelegramLogoutItem(
onClick: () -> Unit,
isDarkTheme: Boolean
) {
val redColor = if (isDarkTheme) Color(0xFFFF5555) else Color(0xFFFF3B30)
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Outlined.Logout,
contentDescription = null,
tint = redColor,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = "Log Out",
fontSize = 16.sp,
color = redColor
)
}
}
// ═════════════════════════════════════════════════════════════
// 📦 LEGACY COMPONENTS (kept for compatibility)
// ═════════════════════════════════════════════════════════════
@Composable
private fun ProfileSectionTitle(title: String, isDarkTheme: Boolean) {
val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
Text(
text = title.uppercase(),
fontSize = 13.sp,
fontWeight = FontWeight.SemiBold,
color = textColor,
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
)
} }
@Composable @Composable
@@ -899,11 +896,10 @@ fun ProfileNavigationItem(
.padding(horizontal = 16.dp, vertical = 12.dp), .padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// Colored Icon Background
Box( Box(
modifier = Modifier modifier = Modifier
.size(36.dp) .size(36.dp)
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(8.dp))
.background(iconBackground), .background(iconBackground),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@@ -917,7 +913,6 @@ fun ProfileNavigationItem(
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
// Title and Subtitle
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = title, text = title,
@@ -933,7 +928,6 @@ fun ProfileNavigationItem(
) )
} }
// Arrow
if (!hideChevron) { if (!hideChevron) {
Icon( Icon(
imageVector = Icons.Default.ChevronRight, imageVector = Icons.Default.ChevronRight,

View File

@@ -73,6 +73,7 @@ fun SafetyScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
.padding(horizontal = 4.dp, vertical = 8.dp), .padding(horizontal = 4.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -98,15 +99,20 @@ fun SafetyScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(16.dp)
) { ) {
Spacer(modifier = Modifier.height(8.dp))
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// Public Key - clickable row with copy feedback // Keys Section - Telegram style
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
Surface( TelegramSectionHeader("Keys", secondaryTextColor)
modifier = Modifier
.fillMaxWidth() TelegramCopyRow(
.clickable { label = "Public Key",
value = shortPublicKey,
fullValue = accountPublicKey,
isCopied = copiedPublicKey,
onCopy = {
clipboardManager.setText(AnnotatedString(accountPublicKey)) clipboardManager.setText(AnnotatedString(accountPublicKey))
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Public Key", accountPublicKey) val clip = ClipData.newPlainText("Public Key", accountPublicKey)
@@ -117,55 +123,18 @@ fun SafetyScreen(
copiedPublicKey = false copiedPublicKey = false
} }
}, },
color = surfaceColor, textColor = textColor,
shape = RoundedCornerShape(12.dp) secondaryTextColor = secondaryTextColor,
) { greenColor = greenColor,
Row( showDivider = true
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Public Key",
fontSize = 15.sp,
color = textColor,
fontWeight = FontWeight.Normal
)
if (copiedPublicKey) {
Text(
text = "copied",
fontSize = 15.sp,
color = greenColor
)
} else {
Text(
text = shortPublicKey,
fontSize = 15.sp,
color = secondaryTextColor
)
}
}
}
Text(
text = "This is your public key. If you haven't set a @username yet, you can ask a friend to message you using your public key.",
fontSize = 13.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
lineHeight = 18.sp
) )
Spacer(modifier = Modifier.height(16.dp)) TelegramCopyRow(
label = "Private Key",
// ═══════════════════════════════════════════════════════════════ value = shortPrivateKey,
// Private Key - clickable row with copy feedback fullValue = accountPrivateKey,
// ═══════════════════════════════════════════════════════════════ isCopied = copiedPrivateKey,
Surface( onCopy = {
modifier = Modifier
.fillMaxWidth()
.clickable {
if (accountPrivateKey.isNotEmpty()) { if (accountPrivateKey.isNotEmpty()) {
clipboardManager.setText(AnnotatedString(accountPrivateKey)) clipboardManager.setText(AnnotatedString(accountPrivateKey))
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
@@ -178,70 +147,122 @@ fun SafetyScreen(
} }
} }
}, },
color = surfaceColor, textColor = textColor,
shape = RoundedCornerShape(12.dp) secondaryTextColor = secondaryTextColor,
greenColor = greenColor,
showDivider = false
)
TelegramInfoText(
text = "Your private key is encrypted. Never share it with anyone.",
secondaryTextColor = secondaryTextColor
)
Spacer(modifier = Modifier.height(24.dp))
// ═══════════════════════════════════════════════════════════════
// Actions Section - Telegram style
// ═══════════════════════════════════════════════════════════════
TelegramActionRow(
label = "Backup",
onClick = onBackupClick,
textColor = textColor,
secondaryTextColor = secondaryTextColor,
showDivider = true
)
TelegramActionRow(
label = "Delete Account",
onClick = onDeleteAccount,
textColor = redColor,
secondaryTextColor = secondaryTextColor,
showDivider = false
)
TelegramInfoText(
text = "Deleting your account will permanently remove all data from this device and the server.",
secondaryTextColor = secondaryTextColor
)
Spacer(modifier = Modifier.height(32.dp))
}
}
}
@Composable
private fun TelegramSectionHeader(title: String, color: Color) {
Text(
text = title,
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = color,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
)
}
@Composable
private fun TelegramCopyRow(
label: String,
value: String,
fullValue: String,
isCopied: Boolean,
onCopy: () -> Unit,
textColor: Color,
secondaryTextColor: Color,
greenColor: Color,
showDivider: Boolean
) { ) {
Column {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp), .clickable(onClick = onCopy)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text( Text(
text = "Private key", text = label,
fontSize = 15.sp, fontSize = 16.sp,
color = textColor
)
Text(
text = if (isCopied) "Copied" else value,
fontSize = 16.sp,
color = if (isCopied) greenColor else secondaryTextColor
)
}
if (showDivider) {
Divider(
color = if (textColor == Color.White) Color(0xFF3A3A3A) else Color(0xFFE0E0E0),
thickness = 0.5.dp,
modifier = Modifier.padding(start = 16.dp)
)
}
}
}
@Composable
private fun TelegramActionRow(
label: String,
onClick: () -> Unit,
textColor: Color,
secondaryTextColor: Color,
showDivider: Boolean
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
fontSize = 16.sp,
color = textColor, color = textColor,
fontWeight = FontWeight.Normal modifier = Modifier.weight(1f)
)
if (copiedPrivateKey) {
Text(
text = "copied",
fontSize = 15.sp,
color = greenColor
)
} else {
Text(
text = shortPrivateKey,
fontSize = 15.sp,
color = secondaryTextColor
)
}
}
}
Text(
text = "This is your private key. For security reasons, we provide it only in an encrypted form so you can simply admire it. If anyone asks you for this key, please do not share it.",
fontSize = 13.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
lineHeight = 18.sp
)
Spacer(modifier = Modifier.height(16.dp))
// ═══════════════════════════════════════════════════════════════
// Backup - with chevron like desktop
// ═══════════════════════════════════════════════════════════════
Surface(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onBackupClick),
color = surfaceColor,
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Backup",
fontSize = 15.sp,
color = redColor,
fontWeight = FontWeight.Normal
) )
Icon( Icon(
imageVector = Icons.Filled.ChevronRight, imageVector = Icons.Filled.ChevronRight,
@@ -250,52 +271,23 @@ fun SafetyScreen(
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
) )
} }
if (showDivider) {
Divider(
color = if (textColor == Color.White) Color(0xFF3A3A3A) else Color(0xFFE0E0E0),
thickness = 0.5.dp,
modifier = Modifier.padding(start = 16.dp)
)
}
}
} }
@Composable
private fun TelegramInfoText(text: String, secondaryTextColor: Color) {
Text( Text(
text = "Please save your seed phrase, it is necessary for future access to your conversations. Do not share this seed phrase with anyone, otherwise, the person you share it with will gain access to your conversations.", text = text,
fontSize = 13.sp, fontSize = 13.sp,
color = secondaryTextColor, color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp), modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
lineHeight = 18.sp lineHeight = 18.sp
) )
Spacer(modifier = Modifier.height(16.dp))
// ═══════════════════════════════════════════════════════════════
// Delete Account
// ═══════════════════════════════════════════════════════════════
Surface(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onDeleteAccount),
color = surfaceColor,
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Delete Account",
fontSize = 15.sp,
color = redColor,
fontWeight = FontWeight.Normal
)
}
}
Text(
text = "This action cannot be undone, it will result in the complete deletion of account data from your device. Please note, this will also delete your data on the server, such as your avatar, encrypted messages, and username.",
fontSize = 13.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
lineHeight = 18.sp
)
Spacer(modifier = Modifier.height(32.dp))
}
}
} }

View File

@@ -15,6 +15,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.DoneAll
import androidx.compose.material.icons.filled.LightMode import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.Smartphone import androidx.compose.material.icons.filled.Smartphone
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -27,6 +28,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.rosetta.messenger.ui.components.AppleEmojiText
@Composable @Composable
fun ThemeScreen( fun ThemeScreen(
@@ -58,6 +60,7 @@ fun ThemeScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
.padding(horizontal = 4.dp, vertical = 8.dp), .padding(horizontal = 4.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -83,70 +86,25 @@ fun ThemeScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(16.dp)
) { ) {
// ═══════════════════════════════════════════════════════ Spacer(modifier = Modifier.height(8.dp))
// 🎨 THEME PREVIEW CARDS - Like Desktop Version
// ═══════════════════════════════════════════════════════
Surface(
modifier = Modifier.fillMaxWidth(),
color = surfaceColor,
shape = RoundedCornerShape(16.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Theme",
fontSize = 14.sp,
color = secondaryTextColor,
modifier = Modifier.padding(bottom = 12.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
// Light Theme Preview
ThemePreviewCard(
isSelected = themeMode == "light",
isDark = false,
label = "Light",
onClick = {
themeMode = "light"
onThemeChange(false)
}
)
Spacer(modifier = Modifier.width(12.dp))
// Dark Theme Preview
ThemePreviewCard(
isSelected = themeMode == "dark",
isDark = true,
label = "Dark",
onClick = {
themeMode = "dark"
onThemeChange(true)
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// ═══════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════
// 🔘 THEME MODE SELECTOR // CHAT PREVIEW - Message bubbles like in real chat
// ═══════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════
Surface( ChatPreview(isDarkTheme = isDarkTheme)
modifier = Modifier.fillMaxWidth(),
color = surfaceColor, Spacer(modifier = Modifier.height(24.dp))
shape = RoundedCornerShape(16.dp)
) { // ═══════════════════════════════════════════════════════
// MODE SELECTOR - Telegram style
// ═══════════════════════════════════════════════════════
TelegramSectionHeader("Appearance", secondaryTextColor)
Column { Column {
ThemeModeOption( TelegramThemeOption(
icon = Icons.Filled.LightMode, icon = Icons.Filled.LightMode,
title = "Light", title = "Light",
subtitle = "Always use light theme",
isSelected = themeMode == "light", isSelected = themeMode == "light",
onClick = { onClick = {
themeMode = "light" themeMode = "light"
@@ -158,10 +116,9 @@ fun ThemeScreen(
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
ThemeModeOption( TelegramThemeOption(
icon = Icons.Filled.DarkMode, icon = Icons.Filled.DarkMode,
title = "Dark", title = "Dark",
subtitle = "Always use dark theme",
isSelected = themeMode == "dark", isSelected = themeMode == "dark",
onClick = { onClick = {
themeMode = "dark" themeMode = "dark"
@@ -173,10 +130,9 @@ fun ThemeScreen(
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
ThemeModeOption( TelegramThemeOption(
icon = Icons.Filled.Smartphone, icon = Icons.Filled.Smartphone,
title = "System", title = "System",
subtitle = "Match system settings",
isSelected = themeMode == "auto", isSelected = themeMode == "auto",
onClick = { onClick = {
themeMode = "auto" themeMode = "auto"
@@ -188,188 +144,32 @@ fun ThemeScreen(
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
} }
}
Text( TelegramInfoText(
text = "If you choose the automatic mode, the theme will change depending on your system settings. We recommend using automatic mode.", text = "System mode automatically switches between light and dark themes based on your device settings.",
fontSize = 12.sp, secondaryTextColor = secondaryTextColor
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp),
lineHeight = 16.sp
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(32.dp))
// ═══════════════════════════════════════════════════════
// 💬 MESSAGE PREVIEW
// ═══════════════════════════════════════════════════════
Text(
text = "Preview",
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = textColor,
modifier = Modifier.padding(bottom = 12.dp, start = 4.dp)
)
Surface(
modifier = Modifier
.fillMaxWidth()
.height(220.dp),
color = surfaceColor,
shape = RoundedCornerShape(16.dp)
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
// Received message
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) {
MessageBubble(
text = "Hey! How are you? 👋",
isFromMe = false,
isDarkTheme = isDarkTheme
)
}
Spacer(modifier = Modifier.height(8.dp))
// Sent message
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
MessageBubble(
text = "I'm great, thanks! 🎉",
isFromMe = true,
isDarkTheme = isDarkTheme
)
}
Spacer(modifier = Modifier.height(8.dp))
// Another received message
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) {
MessageBubble(
text = "Love the new theme! 🌙",
isFromMe = false,
isDarkTheme = isDarkTheme
)
}
}
}
}
} }
} }
} }
@Composable @Composable
private fun ThemePreviewCard( private fun TelegramSectionHeader(title: String, color: Color) {
isSelected: Boolean,
isDark: Boolean,
label: String,
onClick: () -> Unit
) {
val cardBg = if (isDark) Color(0xFF1C1C1E) else Color(0xFFF5F5F5)
val messageBg = if (isDark) Color(0xFF3A3A3C) else Color(0xFFE5E5EA)
val myMessageBg = Color(0xFF007AFF)
val borderColor = if (isSelected) Color(0xFF007AFF) else Color.Transparent
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.clickable(onClick = onClick)
) {
// Preview Card
Box(
modifier = Modifier
.size(width = 130.dp, height = 90.dp)
.clip(RoundedCornerShape(12.dp))
.border(
width = if (isSelected) 2.dp else 1.dp,
color = if (isSelected) borderColor else Color(0xFF3A3A3C).copy(alpha = 0.3f),
shape = RoundedCornerShape(12.dp)
)
.background(cardBg)
.padding(8.dp)
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly
) {
// Fake message bubbles
Box(
modifier = Modifier
.fillMaxWidth(0.7f)
.height(14.dp)
.clip(RoundedCornerShape(7.dp))
.background(messageBg)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Box(
modifier = Modifier
.fillMaxWidth(0.6f)
.height(14.dp)
.clip(RoundedCornerShape(7.dp))
.background(myMessageBg)
)
}
Box(
modifier = Modifier
.fillMaxWidth(0.5f)
.height(14.dp)
.clip(RoundedCornerShape(7.dp))
.background(messageBg)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
// Label with checkmark
Row(
verticalAlignment = Alignment.CenterVertically
) {
if (isSelected) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
tint = Color(0xFF007AFF),
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(4.dp))
}
Text( Text(
text = label, text = title,
fontSize = 13.sp, fontSize = 15.sp,
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, fontWeight = FontWeight.Medium,
color = if (isSelected) Color(0xFF007AFF) else Color(0xFF8E8E93) color = color,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
) )
} }
}
}
@Composable @Composable
private fun ThemeModeOption( private fun TelegramThemeOption(
icon: ImageVector, icon: ImageVector,
title: String, title: String,
subtitle: String,
isSelected: Boolean, isSelected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
textColor: Color, textColor: Color,
@@ -377,116 +177,191 @@ private fun ThemeModeOption(
showDivider: Boolean, showDivider: Boolean,
isDarkTheme: Boolean isDarkTheme: Boolean
) { ) {
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8) val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0)
Column( Column {
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp), .clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) {
// Icon with background
Box(
modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(8.dp))
.background(
when (title) {
"Light" -> Color(0xFFFFA500)
"Dark" -> Color(0xFF6366F1)
else -> Color(0xFF8E8E93)
}
),
contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
tint = Color.White, tint = secondaryTextColor,
modifier = Modifier.size(20.dp) modifier = Modifier.size(24.dp)
) )
}
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = title, text = title,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Medium, color = textColor,
color = textColor modifier = Modifier.weight(1f)
) )
Text(
text = subtitle,
fontSize = 12.sp,
color = secondaryTextColor
)
}
// Radio button // Radio button
Box( if (isSelected) {
modifier = Modifier Icon(
.size(24.dp) imageVector = Icons.Filled.Check,
.border( contentDescription = null,
width = 2.dp, tint = Color(0xFF007AFF),
color = if (isSelected) Color(0xFF007AFF) else secondaryTextColor.copy(alpha = 0.5f), modifier = Modifier.size(20.dp)
shape = CircleShape
)
.padding(4.dp)
.background(
color = if (isSelected) Color(0xFF007AFF) else Color.Transparent,
shape = CircleShape
)
) )
} }
}
if (showDivider) { if (showDivider) {
Divider( Divider(
color = dividerColor, color = dividerColor,
thickness = 0.5.dp, thickness = 0.5.dp,
modifier = Modifier.padding(start = 64.dp) modifier = Modifier.padding(start = 56.dp)
) )
} }
} }
} }
@Composable
private fun TelegramInfoText(text: String, secondaryTextColor: Color) {
Text(
text = text,
fontSize = 13.sp,
color = secondaryTextColor,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
lineHeight = 18.sp
)
}
// ═══════════════════════════════════════════════════════════════════
// 💬 CHAT PREVIEW - Real message bubbles preview
// ═══════════════════════════════════════════════════════════════════
@Composable
private fun ChatPreview(isDarkTheme: Boolean) {
val chatBgColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF0F0F0)
// Message colors matching real ChatDetailScreen
val myBubbleColor = Color(0xFF248AE6) // PrimaryBlue - same for both themes
val otherBubbleColor = if (isDarkTheme) Color(0xFF212121) else Color(0xFFF5F5F5)
val myTextColor = Color.White // White text on blue bubble
val otherTextColor = if (isDarkTheme) Color.White else Color.Black
val timeColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.height(230.dp),
color = chatBgColor,
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Incoming message
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) {
MessageBubble(
text = "Hey! How's it going? 👋",
time = "10:42",
isMe = false,
bubbleColor = otherBubbleColor,
textColor = otherTextColor,
timeColor = timeColor
)
}
// Outgoing message
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
MessageBubble(
text = "Hey! All good, just checking out this new theme 😊",
time = "10:43",
isMe = true,
bubbleColor = myBubbleColor,
textColor = myTextColor,
timeColor = timeColor
)
}
// Incoming message
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start
) {
MessageBubble(
text = "Nice! Looks great! 🔥",
time = "10:44",
isMe = false,
bubbleColor = otherBubbleColor,
textColor = otherTextColor,
timeColor = timeColor
)
}
}
}
}
@Composable @Composable
private fun MessageBubble( private fun MessageBubble(
text: String, text: String,
isFromMe: Boolean, time: String,
isDarkTheme: Boolean isMe: Boolean,
bubbleColor: Color,
textColor: Color,
timeColor: Color
) { ) {
val bubbleColor = if (isFromMe) {
Color(0xFF007AFF)
} else {
if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE5E5EA)
}
val textColorMsg = if (isFromMe) {
Color.White
} else {
if (isDarkTheme) Color.White else Color.Black
}
Surface( Surface(
color = bubbleColor, color = bubbleColor,
shape = RoundedCornerShape( shape = RoundedCornerShape(
topStart = 16.dp, topStart = 16.dp,
topEnd = 16.dp, topEnd = 16.dp,
bottomStart = if (isFromMe) 16.dp else 4.dp, bottomStart = if (isMe) 16.dp else 4.dp,
bottomEnd = if (isFromMe) 4.dp else 16.dp bottomEnd = if (isMe) 4.dp else 16.dp
),
shadowElevation = 1.dp
) {
Column(
modifier = Modifier
.widthIn(max = 260.dp)
.padding(horizontal = 12.dp, vertical = 8.dp)
) {
AppleEmojiText(
text = text,
fontSize = 15.sp,
color = textColor
) )
Row(
modifier = Modifier.align(Alignment.End),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(3.dp)
) { ) {
Text( Text(
text = text, text = time,
color = textColorMsg, fontSize = 11.sp,
fontSize = 14.sp, color = timeColor.copy(alpha = 0.7f)
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp) )
if (isMe) {
// Read checkmarks (DoneAll icon like in real chat)
Icon(
imageVector = Icons.Default.DoneAll,
contentDescription = null,
tint = Color(0xFF4FC3F7), // Blue checkmarks for read messages
modifier = Modifier.size(14.dp)
) )
} }
} }
}
}
}