feat: Refactor ProfileScreen to adopt Telegram-style components and improve UI consistency
This commit is contained in:
@@ -4,6 +4,9 @@ import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
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.text.BasicTextField
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -46,33 +49,29 @@ fun BackupScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.windowInsetsPadding(WindowInsets.statusBars)
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = backgroundColor
|
||||
// Telegram-style Header
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 4.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = if (isDarkTheme) Color.White else Color.Black
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Backup",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = textColor
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Backup",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Content
|
||||
@@ -85,27 +84,26 @@ fun BackupScreen(
|
||||
// Warning Card
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = (if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)).copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
) {
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null,
|
||||
tint = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(top = 2.dp)
|
||||
tint = Color(0xFFFFAC00),
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
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,
|
||||
color = textColor,
|
||||
lineHeight = 18.sp
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor,
|
||||
lineHeight = 20.sp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -113,19 +111,11 @@ fun BackupScreen(
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
if (seedPhrase == null) {
|
||||
// Password Input
|
||||
Text(
|
||||
text = "Confirm Password",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
// Password Input Section
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -185,26 +175,18 @@ fun BackupScreen(
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "To view your recovery phrase, enter the password you specified when creating your account.",
|
||||
fontSize = 13.sp,
|
||||
text = "To view your recovery phrase, enter the password you specified when creating your account or restoring from a seed phrase.",
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
lineHeight = 18.sp
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
lineHeight = 20.sp
|
||||
)
|
||||
} else {
|
||||
// Seed Phrase Display
|
||||
Text(
|
||||
text = "Your Recovery Phrase",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFF2E7D32).copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(20.dp)) {
|
||||
val words = seedPhrase!!.split(" ")
|
||||
@@ -228,34 +210,14 @@ fun BackupScreen(
|
||||
}
|
||||
|
||||
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.",
|
||||
fontSize = 12.sp,
|
||||
color = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
lineHeight = 16.sp
|
||||
text = "Please don't share your seed phrase! The administration will never ask you for it.",
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
lineHeight = 20.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,156 +238,80 @@ fun ProfileScreen(
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// ✏️ EDITABLE FIELDS
|
||||
// 📋 ACCOUNT SECTION - Telegram style
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileSectionTitle(title = "Profile Information", isDarkTheme = isDarkTheme)
|
||||
TelegramSectionTitle(title = "Account", isDarkTheme = isDarkTheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileEditableField(
|
||||
label = "Your name",
|
||||
value = editedName,
|
||||
onValueChange = { editedName = it },
|
||||
placeholder = "ex. Freddie Gibson",
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileEditableField(
|
||||
label = "Username",
|
||||
value = editedUsername,
|
||||
onValueChange = { editedUsername = it },
|
||||
placeholder = "ex. freddie871",
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
// Name field
|
||||
TelegramTextField(
|
||||
value = editedName,
|
||||
label = "Your name",
|
||||
isDarkTheme = isDarkTheme,
|
||||
isEditable = true,
|
||||
onValueChange = { editedName = it },
|
||||
showDivider = true,
|
||||
placeholder = "Add your name"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// Username field
|
||||
TelegramTextField(
|
||||
value = editedUsername,
|
||||
label = "Username",
|
||||
isDarkTheme = isDarkTheme,
|
||||
isEditable = true,
|
||||
onValueChange = {
|
||||
editedUsername = it
|
||||
},
|
||||
showDivider = true,
|
||||
placeholder = "Add username"
|
||||
)
|
||||
|
||||
// Public Key Copy Field
|
||||
ProfileCopyField(
|
||||
// Public Key field
|
||||
TelegramCopyField(
|
||||
value = accountPublicKey.take(16) + "..." + accountPublicKey.takeLast(6),
|
||||
fullValue = accountPublicKey,
|
||||
label = "Public Key",
|
||||
value = accountPublicKey,
|
||||
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))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// ⚙️ SETTINGS SECTION - Telegram style
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
TelegramSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
|
||||
|
||||
TelegramSettingsItem(
|
||||
icon = Icons.Outlined.Palette,
|
||||
title = "Theme",
|
||||
onClick = onNavigateToTheme,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
|
||||
TelegramSettingsItem(
|
||||
icon = Icons.Outlined.Lock,
|
||||
title = "Safety",
|
||||
onClick = onNavigateToSafety,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
|
||||
TelegramSettingsItem(
|
||||
icon = Icons.Outlined.Terminal,
|
||||
title = "View Logs",
|
||||
onClick = onNavigateToLogs,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🔧 SETTINGS SECTION
|
||||
// 🚪 LOGOUT - Telegram style (red text)
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
ProfileSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Brush,
|
||||
iconBackground = Color(0xFF6366F1),
|
||||
title = "Theme",
|
||||
subtitle = "Customize appearance",
|
||||
onClick = onNavigateToTheme,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.AdminPanelSettings,
|
||||
iconBackground = Color(0xFF9333EA),
|
||||
title = "Safety",
|
||||
subtitle = "Backup and security settings",
|
||||
onClick = onNavigateToSafety,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🐛 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",
|
||||
subtitle = "Debug profile save operations",
|
||||
onClick = onNavigateToLogs,
|
||||
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))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🚪 LOGOUT SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
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,
|
||||
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
|
||||
TelegramLogoutItem(
|
||||
onClick = onLogout,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
@@ -717,60 +641,43 @@ fun ProfileCard(
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 📦 HELPER COMPONENTS
|
||||
// <EFBFBD> TELEGRAM-STYLE COMPONENTS
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
|
||||
@Composable
|
||||
private fun ProfileSectionTitle(title: String, isDarkTheme: Boolean) {
|
||||
private fun TelegramSectionTitle(title: String, isDarkTheme: Boolean) {
|
||||
val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
|
||||
|
||||
Text(
|
||||
text = title.uppercase(),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
text = title,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileEditableField(
|
||||
label: String,
|
||||
private fun TelegramTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
placeholder: String,
|
||||
label: String,
|
||||
isDarkTheme: Boolean,
|
||||
isEditable: Boolean = false,
|
||||
onValueChange: ((String) -> Unit)? = null,
|
||||
showDivider: Boolean = false,
|
||||
leadingText: String? = null
|
||||
placeholder: String = ""
|
||||
) {
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
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(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
) {
|
||||
Text(
|
||||
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))
|
||||
}
|
||||
if (isEditable && onValueChange != null) {
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
@@ -782,17 +689,31 @@ private fun ProfileEditableField(
|
||||
singleLine = true,
|
||||
cursorBrush = androidx.compose.ui.graphics.SolidColor(textColor),
|
||||
decorationBox = { innerTextField ->
|
||||
if (value.isEmpty()) {
|
||||
if (value.isEmpty() && placeholder.isNotEmpty()) {
|
||||
Text(
|
||||
text = placeholder,
|
||||
color = secondaryTextColor.copy(alpha = 0.5f),
|
||||
fontSize = 16.sp
|
||||
fontSize = 16.sp,
|
||||
color = secondaryTextColor.copy(alpha = 0.5f)
|
||||
)
|
||||
}
|
||||
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) {
|
||||
@@ -806,26 +727,24 @@ private fun ProfileEditableField(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileCopyField(
|
||||
label: String,
|
||||
private fun TelegramCopyField(
|
||||
value: String,
|
||||
fullValue: String,
|
||||
label: String,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var showCopied by remember { mutableStateOf(false) }
|
||||
|
||||
Surface(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText(label, value)
|
||||
val clip = ClipData.newPlainText(label, fullValue)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
|
||||
scope.launch {
|
||||
@@ -833,47 +752,125 @@ private fun ProfileCopyField(
|
||||
delay(1500)
|
||||
showCopied = false
|
||||
}
|
||||
},
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = showCopied,
|
||||
label = "copy_animation"
|
||||
) { copied ->
|
||||
Text(
|
||||
text = if (copied) "Copied!" else value,
|
||||
fontSize = 16.sp,
|
||||
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()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor,
|
||||
fontWeight = FontWeight.Medium
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = iconColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = showCopied,
|
||||
label = "copy_animation"
|
||||
) { copied ->
|
||||
if (copied) {
|
||||
Text(
|
||||
text = "Copied!",
|
||||
fontSize = 14.sp,
|
||||
color = Color(0xFF22C55E),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = value.take(16) + "...",
|
||||
fontSize = 14.sp,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
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
|
||||
fun ProfileNavigationItem(
|
||||
icon: ImageVector,
|
||||
@@ -899,11 +896,10 @@ fun ProfileNavigationItem(
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Colored Icon Background
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(iconBackground),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -917,7 +913,6 @@ fun ProfileNavigationItem(
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
// Title and Subtitle
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
@@ -933,7 +928,6 @@ fun ProfileNavigationItem(
|
||||
)
|
||||
}
|
||||
|
||||
// Arrow
|
||||
if (!hideChevron) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
|
||||
@@ -73,6 +73,7 @@ fun SafetyScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@@ -98,204 +99,195 @@ fun SafetyScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Public Key - clickable row with copy feedback
|
||||
// Keys Section - Telegram style
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
clipboardManager.setText(AnnotatedString(accountPublicKey))
|
||||
TelegramSectionHeader("Keys", secondaryTextColor)
|
||||
|
||||
TelegramCopyRow(
|
||||
label = "Public Key",
|
||||
value = shortPublicKey,
|
||||
fullValue = accountPublicKey,
|
||||
isCopied = copiedPublicKey,
|
||||
onCopy = {
|
||||
clipboardManager.setText(AnnotatedString(accountPublicKey))
|
||||
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Public Key", accountPublicKey)
|
||||
cm.setPrimaryClip(clip)
|
||||
copiedPublicKey = true
|
||||
scope.launch {
|
||||
delay(2000)
|
||||
copiedPublicKey = false
|
||||
}
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
greenColor = greenColor,
|
||||
showDivider = true
|
||||
)
|
||||
|
||||
TelegramCopyRow(
|
||||
label = "Private Key",
|
||||
value = shortPrivateKey,
|
||||
fullValue = accountPrivateKey,
|
||||
isCopied = copiedPrivateKey,
|
||||
onCopy = {
|
||||
if (accountPrivateKey.isNotEmpty()) {
|
||||
clipboardManager.setText(AnnotatedString(accountPrivateKey))
|
||||
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Public Key", accountPublicKey)
|
||||
val clip = ClipData.newPlainText("Private Key", accountPrivateKey)
|
||||
cm.setPrimaryClip(clip)
|
||||
copiedPublicKey = true
|
||||
copiedPrivateKey = true
|
||||
scope.launch {
|
||||
delay(2000)
|
||||
copiedPublicKey = false
|
||||
copiedPrivateKey = false
|
||||
}
|
||||
},
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
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
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
greenColor = greenColor,
|
||||
showDivider = false
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Private Key - clickable row with copy feedback
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (accountPrivateKey.isNotEmpty()) {
|
||||
clipboardManager.setText(AnnotatedString(accountPrivateKey))
|
||||
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Private Key", accountPrivateKey)
|
||||
cm.setPrimaryClip(clip)
|
||||
copiedPrivateKey = true
|
||||
scope.launch {
|
||||
delay(2000)
|
||||
copiedPrivateKey = false
|
||||
}
|
||||
}
|
||||
},
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "Private key",
|
||||
fontSize = 15.sp,
|
||||
color = textColor,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
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
|
||||
TelegramInfoText(
|
||||
text = "Your private key is encrypted. Never share it with anyone.",
|
||||
secondaryTextColor = secondaryTextColor
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Backup - with chevron like desktop
|
||||
// Actions Section - Telegram style
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
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(
|
||||
imageVector = Icons.Filled.ChevronRight,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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.",
|
||||
fontSize = 13.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
lineHeight = 18.sp
|
||||
TelegramActionRow(
|
||||
label = "Backup",
|
||||
onClick = onBackupClick,
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
TelegramActionRow(
|
||||
label = "Delete Account",
|
||||
onClick = onDeleteAccount,
|
||||
textColor = redColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = false
|
||||
)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 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
|
||||
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(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onCopy)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
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,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ChevronRight,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor,
|
||||
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,
|
||||
fontSize = 13.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
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.Smartphone
|
||||
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.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.rosetta.messenger.ui.components.AppleEmojiText
|
||||
|
||||
@Composable
|
||||
fun ThemeScreen(
|
||||
@@ -58,6 +60,7 @@ fun ThemeScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
|
||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@@ -83,293 +86,90 @@ fun ThemeScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.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))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 🔘 THEME MODE SELECTOR
|
||||
// CHAT PREVIEW - Message bubbles like in real chat
|
||||
// ═══════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ThemeModeOption(
|
||||
icon = Icons.Filled.LightMode,
|
||||
title = "Light",
|
||||
subtitle = "Always use light theme",
|
||||
isSelected = themeMode == "light",
|
||||
onClick = {
|
||||
themeMode = "light"
|
||||
onThemeChange(false)
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = true,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
ThemeModeOption(
|
||||
icon = Icons.Filled.DarkMode,
|
||||
title = "Dark",
|
||||
subtitle = "Always use dark theme",
|
||||
isSelected = themeMode == "dark",
|
||||
onClick = {
|
||||
themeMode = "dark"
|
||||
onThemeChange(true)
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = true,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
ThemeModeOption(
|
||||
icon = Icons.Filled.Smartphone,
|
||||
title = "System",
|
||||
subtitle = "Match system settings",
|
||||
isSelected = themeMode == "auto",
|
||||
onClick = {
|
||||
themeMode = "auto"
|
||||
// For now, we'll just keep the current theme
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = false,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "If you choose the automatic mode, the theme will change depending on your system settings. We recommend using automatic mode.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
ChatPreview(isDarkTheme = isDarkTheme)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 💬 MESSAGE PREVIEW
|
||||
// MODE SELECTOR - Telegram style
|
||||
// ═══════════════════════════════════════════════════════
|
||||
Text(
|
||||
text = "Preview",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 12.dp, start = 4.dp)
|
||||
TelegramSectionHeader("Appearance", secondaryTextColor)
|
||||
|
||||
Column {
|
||||
TelegramThemeOption(
|
||||
icon = Icons.Filled.LightMode,
|
||||
title = "Light",
|
||||
isSelected = themeMode == "light",
|
||||
onClick = {
|
||||
themeMode = "light"
|
||||
onThemeChange(false)
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = true,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
TelegramThemeOption(
|
||||
icon = Icons.Filled.DarkMode,
|
||||
title = "Dark",
|
||||
isSelected = themeMode == "dark",
|
||||
onClick = {
|
||||
themeMode = "dark"
|
||||
onThemeChange(true)
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = true,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
TelegramThemeOption(
|
||||
icon = Icons.Filled.Smartphone,
|
||||
title = "System",
|
||||
isSelected = themeMode == "auto",
|
||||
onClick = {
|
||||
themeMode = "auto"
|
||||
// For now, we'll just keep the current theme
|
||||
},
|
||||
textColor = textColor,
|
||||
secondaryTextColor = secondaryTextColor,
|
||||
showDivider = false,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
|
||||
TelegramInfoText(
|
||||
text = "System mode automatically switches between light and dark themes based on your device settings.",
|
||||
secondaryTextColor = secondaryTextColor
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThemePreviewCard(
|
||||
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 = label,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
color = if (isSelected) Color(0xFF007AFF) else Color(0xFF8E8E93)
|
||||
)
|
||||
}
|
||||
}
|
||||
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 ThemeModeOption(
|
||||
private fun TelegramThemeOption(
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
textColor: Color,
|
||||
@@ -377,116 +177,191 @@ private fun ThemeModeOption(
|
||||
showDivider: Boolean,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
||||
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
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(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 16.sp,
|
||||
color = textColor,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
// Radio button
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
tint = Color(0xFF007AFF),
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = textColor
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
|
||||
// Radio button
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.border(
|
||||
width = 2.dp,
|
||||
color = if (isSelected) Color(0xFF007AFF) else secondaryTextColor.copy(alpha = 0.5f),
|
||||
shape = CircleShape
|
||||
)
|
||||
.padding(4.dp)
|
||||
.background(
|
||||
color = if (isSelected) Color(0xFF007AFF) else Color.Transparent,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (showDivider) {
|
||||
Divider(
|
||||
color = dividerColor,
|
||||
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
|
||||
private fun MessageBubble(
|
||||
text: String,
|
||||
isFromMe: Boolean,
|
||||
isDarkTheme: Boolean
|
||||
time: String,
|
||||
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(
|
||||
color = bubbleColor,
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 16.dp,
|
||||
topEnd = 16.dp,
|
||||
bottomStart = if (isFromMe) 16.dp else 4.dp,
|
||||
bottomEnd = if (isFromMe) 4.dp else 16.dp
|
||||
)
|
||||
bottomStart = if (isMe) 16.dp else 4.dp,
|
||||
bottomEnd = if (isMe) 4.dp else 16.dp
|
||||
),
|
||||
shadowElevation = 1.dp
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = textColorMsg,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.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 = time,
|
||||
fontSize = 11.sp,
|
||||
color = timeColor.copy(alpha = 0.7f)
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user