feat: Add Profile Logs Screen and integrate logging functionality
- Introduced ProfileLogsScreen for displaying logs related to profile operations. - Enhanced MainScreen to include navigation to the new logs screen. - Updated ProfileViewModel to manage logs and handle profile save operations. - Implemented back navigation and log clearing functionality in ProfileLogsScreen. - Improved SafetyScreen and ThemeScreen with back gesture handling. - Refactored BackupScreen and OtherProfileScreen for consistency and better UX. - Adjusted UI elements for better visibility and interaction feedback.
This commit is contained in:
@@ -452,6 +452,11 @@ fun MainScreen(
|
||||
var showThemeScreen by remember { mutableStateOf(false) }
|
||||
var showSafetyScreen by remember { mutableStateOf(false) }
|
||||
var showBackupScreen by remember { mutableStateOf(false) }
|
||||
var showLogsScreen by remember { mutableStateOf(false) }
|
||||
|
||||
// ProfileViewModel для логов
|
||||
val profileViewModel: com.rosetta.messenger.ui.settings.ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
||||
val profileState by profileViewModel.state.collectAsState()
|
||||
|
||||
// 🔥 Простая навигация с fade-in анимацией
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
@@ -459,7 +464,7 @@ fun MainScreen(
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = !showBackupScreen && !showSafetyScreen && !showThemeScreen &&
|
||||
!showUpdatesScreen && selectedUser == null && !showSearchScreen &&
|
||||
!showProfileScreen && !showOtherProfileScreen,
|
||||
!showProfileScreen && !showOtherProfileScreen && !showLogsScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
@@ -516,7 +521,10 @@ fun MainScreen(
|
||||
if (showBackupScreen) {
|
||||
BackupScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { showBackupScreen = false },
|
||||
onBack = {
|
||||
showBackupScreen = false
|
||||
showSafetyScreen = true
|
||||
},
|
||||
onVerifyPassword = { password ->
|
||||
// TODO: Implement password verification
|
||||
if (password == "test") {
|
||||
@@ -536,8 +544,15 @@ fun MainScreen(
|
||||
SafetyScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
accountPublicKey = accountPublicKey,
|
||||
onBack = { showSafetyScreen = false },
|
||||
onBackupClick = { showBackupScreen = true },
|
||||
accountPrivateKey = accountPrivateKey,
|
||||
onBack = {
|
||||
showSafetyScreen = false
|
||||
showProfileScreen = true
|
||||
},
|
||||
onBackupClick = {
|
||||
showSafetyScreen = false
|
||||
showBackupScreen = true
|
||||
},
|
||||
onDeleteAccount = {
|
||||
// TODO: Implement account deletion
|
||||
Log.d("MainActivity", "Delete account requested")
|
||||
@@ -554,7 +569,10 @@ fun MainScreen(
|
||||
if (showThemeScreen) {
|
||||
ThemeScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { showThemeScreen = false },
|
||||
onBack = {
|
||||
showThemeScreen = false
|
||||
showProfileScreen = true
|
||||
},
|
||||
onThemeChange = { isDark ->
|
||||
onToggleTheme()
|
||||
}
|
||||
@@ -644,10 +662,11 @@ fun MainScreen(
|
||||
accountName = accountName,
|
||||
accountUsername = "", // TODO: Get from account
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKeyHash = privateKeyHash,
|
||||
onBack = { showProfileScreen = false },
|
||||
onSaveProfile = { name, username ->
|
||||
// TODO: Save profile changes
|
||||
Log.d("MainActivity", "Saving profile: name=$name, username=$username")
|
||||
// Profile saved via ViewModel
|
||||
Log.d("MainActivity", "Profile saved: name=$name, username=$username")
|
||||
},
|
||||
onLogout = onLogout,
|
||||
onNavigateToTheme = {
|
||||
@@ -657,7 +676,32 @@ fun MainScreen(
|
||||
onNavigateToSafety = {
|
||||
showProfileScreen = false
|
||||
showSafetyScreen = true
|
||||
}
|
||||
},
|
||||
onNavigateToLogs = {
|
||||
showProfileScreen = false
|
||||
showLogsScreen = true
|
||||
},
|
||||
viewModel = profileViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showLogsScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showLogsScreen) {
|
||||
com.rosetta.messenger.ui.settings.ProfileLogsScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
logs = profileState.logs,
|
||||
onBack = {
|
||||
showLogsScreen = false
|
||||
showProfileScreen = true
|
||||
},
|
||||
onClearLogs = {
|
||||
profileViewModel.clearLogs()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,9 +388,6 @@ fun ChatsListScreen(
|
||||
.background(
|
||||
color = headerColor
|
||||
)
|
||||
.statusBarsPadding() // 🎨
|
||||
// Контент начинается
|
||||
// после status bar
|
||||
.padding(
|
||||
top = 16.dp,
|
||||
start = 20.dp,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -37,12 +38,14 @@ fun BackupScreen(
|
||||
var password by remember { mutableStateOf("") }
|
||||
var seedPhrase by remember { mutableStateOf<String?>(null) }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
@@ -82,17 +85,17 @@ fun BackupScreen(
|
||||
// Warning Card
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFEF4444).copy(alpha = 0.15f),
|
||||
color = (if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)).copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFEF4444),
|
||||
tint = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(top = 2.dp)
|
||||
@@ -127,7 +130,7 @@ fun BackupScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BasicTextField(
|
||||
@@ -143,26 +146,30 @@ fun BackupScreen(
|
||||
modifier = Modifier.weight(1f),
|
||||
textStyle = TextStyle(
|
||||
color = textColor,
|
||||
fontSize = 16.sp
|
||||
fontSize = 15.sp
|
||||
),
|
||||
visualTransformation = if (passwordVisible)
|
||||
VisualTransformation.None
|
||||
else
|
||||
PasswordVisualTransformation(),
|
||||
singleLine = true,
|
||||
decorationBox = { innerTextField ->
|
||||
if (password.isEmpty()) {
|
||||
Text(
|
||||
text = "Enter your password",
|
||||
color = secondaryTextColor,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
if (password.isEmpty()) {
|
||||
Text(
|
||||
text = "Enter your password",
|
||||
color = secondaryTextColor,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = { passwordVisible = !passwordVisible }
|
||||
onClick = { passwordVisible = !passwordVisible },
|
||||
modifier = Modifier.size(40.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (passwordVisible)
|
||||
@@ -170,7 +177,8 @@ fun BackupScreen(
|
||||
else
|
||||
Icons.Filled.VisibilityOff,
|
||||
contentDescription = if (passwordVisible) "Hide password" else "Show password",
|
||||
tint = secondaryTextColor
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -178,10 +186,10 @@ fun BackupScreen(
|
||||
|
||||
Text(
|
||||
text = "To view your recovery phrase, enter the password you specified when creating your account.",
|
||||
fontSize = 12.sp,
|
||||
fontSize = 13.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
} else {
|
||||
// Seed Phrase Display
|
||||
@@ -222,7 +230,7 @@ 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 = Color(0xFFEF4444),
|
||||
color = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
lineHeight = 16.sp
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -30,6 +31,9 @@ fun OtherProfileScreen(
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -175,7 +179,7 @@ fun OtherProfileScreen(
|
||||
} else {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Block,
|
||||
iconBackground = Color(0xFFEF4444), // red
|
||||
iconBackground = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
title = "Block this user",
|
||||
subtitle = "Prevent this user from messaging you",
|
||||
onClick = {
|
||||
@@ -184,7 +188,7 @@ fun OtherProfileScreen(
|
||||
},
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = Color(0xFFEF4444)
|
||||
textColor = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun ProfileLogsScreen(
|
||||
isDarkTheme: Boolean,
|
||||
logs: List<String>,
|
||||
onBack: () -> Unit,
|
||||
onClearLogs: () -> Unit
|
||||
) {
|
||||
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val successColor = if (isDarkTheme) Color(0xFF4CAF50) else Color(0xFF2E7D32)
|
||||
val errorColor = if (isDarkTheme) Color(0xFFEF5350) else Color(0xFFD32F2F)
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// Auto-scroll to bottom when new logs added
|
||||
LaunchedEffect(logs.size) {
|
||||
if (logs.isNotEmpty()) {
|
||||
listState.animateScrollToItem(logs.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = backgroundColor
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = textColor
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Profile Logs",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
IconButton(onClick = onClearLogs) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Delete,
|
||||
contentDescription = "Clear logs",
|
||||
tint = secondaryTextColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8))
|
||||
|
||||
// Logs content
|
||||
if (logs.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "No logs yet.\nSave profile to see logs.",
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
) {
|
||||
items(logs) { log ->
|
||||
LogItem(
|
||||
log = log,
|
||||
textColor = textColor,
|
||||
successColor = successColor,
|
||||
errorColor = errorColor,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LogItem(
|
||||
log: String,
|
||||
textColor: Color,
|
||||
successColor: Color,
|
||||
errorColor: Color,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val logColor = when {
|
||||
log.contains("✅ SUCCESS") -> successColor
|
||||
log.contains("❌ ERROR") -> errorColor
|
||||
log.contains("===") -> if (isDarkTheme) Color(0xFF9C27B0) else Color(0xFF6A1B9A)
|
||||
else -> textColor
|
||||
}
|
||||
|
||||
val backgroundColor = when {
|
||||
log.contains("✅ SUCCESS") -> if (isDarkTheme) Color(0xFF1B5E20).copy(alpha = 0.2f) else Color(0xFFC8E6C9)
|
||||
log.contains("❌ ERROR") -> if (isDarkTheme) Color(0xFFB71C1C).copy(alpha = 0.2f) else Color(0xFFFFCDD2)
|
||||
else -> Color.Transparent
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp),
|
||||
color = backgroundColor,
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = log,
|
||||
fontSize = 12.sp,
|
||||
color = logColor,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -97,11 +98,14 @@ fun ProfileScreen(
|
||||
accountName: String,
|
||||
accountUsername: String,
|
||||
accountPublicKey: String,
|
||||
accountPrivateKeyHash: String,
|
||||
onBack: () -> Unit,
|
||||
onSaveProfile: (name: String, username: String) -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
onNavigateToTheme: () -> Unit = {},
|
||||
onNavigateToSafety: () -> Unit = {}
|
||||
onNavigateToSafety: () -> Unit = {},
|
||||
onNavigateToLogs: () -> Unit = {},
|
||||
viewModel: ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
|
||||
) {
|
||||
// Цвета в зависимости от темы - такие же как в ChatsListScreen
|
||||
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
|
||||
@@ -115,6 +119,36 @@ fun ProfileScreen(
|
||||
var editedName by remember { mutableStateOf(accountName) }
|
||||
var editedUsername by remember { mutableStateOf(accountUsername) }
|
||||
var hasChanges by remember { mutableStateOf(false) }
|
||||
|
||||
// ViewModel state
|
||||
val profileState by viewModel.state.collectAsState()
|
||||
|
||||
// Show success toast
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(profileState.saveSuccess) {
|
||||
if (profileState.saveSuccess) {
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
"Profile updated successfully",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
hasChanges = false
|
||||
viewModel.resetSaveState()
|
||||
onSaveProfile(editedName, editedUsername)
|
||||
}
|
||||
}
|
||||
|
||||
// Show error toast
|
||||
LaunchedEffect(profileState.error) {
|
||||
profileState.error?.let { error ->
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
"Error: $error",
|
||||
android.widget.Toast.LENGTH_SHORT
|
||||
).show()
|
||||
viewModel.resetSaveState()
|
||||
}
|
||||
}
|
||||
|
||||
// Update hasChanges when fields change
|
||||
LaunchedEffect(editedName, editedUsername) {
|
||||
@@ -142,8 +176,15 @@ fun ProfileScreen(
|
||||
onBack = null, // Кнопка назад будет снаружи
|
||||
hasChanges = hasChanges,
|
||||
onSave = {
|
||||
onSaveProfile(editedName, editedUsername)
|
||||
hasChanges = false
|
||||
// Save via ViewModel
|
||||
viewModel.saveProfile(
|
||||
publicKey = accountPublicKey,
|
||||
privateKeyHash = accountPrivateKeyHash,
|
||||
name = editedName,
|
||||
username = editedUsername
|
||||
)
|
||||
// Also update local account name
|
||||
viewModel.updateLocalAccountName(accountPublicKey, editedName)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -175,8 +216,7 @@ fun ProfileScreen(
|
||||
value = editedUsername,
|
||||
onValueChange = { editedUsername = it },
|
||||
placeholder = "ex. freddie871",
|
||||
isDarkTheme = isDarkTheme,
|
||||
leadingText = "@"
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -244,7 +284,37 @@ fun ProfileScreen(
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// 🚪 LOGOUT SECTION
|
||||
// <EFBFBD> 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), // orange
|
||||
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))
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// <20>🚪 LOGOUT SECTION
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
@@ -255,13 +325,13 @@ fun ProfileScreen(
|
||||
) {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Logout,
|
||||
iconBackground = Color(0xFFEF4444), // red
|
||||
iconBackground = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444),
|
||||
title = "Logout",
|
||||
subtitle = "Sign out of your account",
|
||||
onClick = onLogout,
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = Color(0xFFEF4444)
|
||||
textColor = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -341,6 +411,7 @@ fun ProfileCard(
|
||||
onClick = onBack,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.statusBarsPadding()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
@@ -358,6 +429,7 @@ fun ProfileCard(
|
||||
exit = fadeOut() + shrinkHorizontally(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.statusBarsPadding()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
TextButton(onClick = onSave) {
|
||||
@@ -372,13 +444,13 @@ fun ProfileCard(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 48.dp, bottom = 24.dp),
|
||||
.padding(top = 80.dp, bottom = 48.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 👤 Large Avatar - Telegram style
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.size(140.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.2f))
|
||||
.padding(3.dp)
|
||||
@@ -497,6 +569,8 @@ private fun ProfileEditableField(
|
||||
color = textColor,
|
||||
fontSize = 16.sp
|
||||
),
|
||||
singleLine = true,
|
||||
cursorBrush = androidx.compose.ui.graphics.SolidColor(textColor),
|
||||
decorationBox = { innerTextField ->
|
||||
if (value.isEmpty()) {
|
||||
Text(
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rosetta.messenger.data.AccountManager
|
||||
import com.rosetta.messenger.data.EncryptedAccount
|
||||
import com.rosetta.messenger.network.Packet
|
||||
import com.rosetta.messenger.network.PacketResult
|
||||
import com.rosetta.messenger.network.PacketUserInfo
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class ProfileState(
|
||||
val isLoading: Boolean = false,
|
||||
val isSaving: Boolean = false,
|
||||
val error: String? = null,
|
||||
val saveSuccess: Boolean = false,
|
||||
val logs: List<String> = emptyList()
|
||||
)
|
||||
|
||||
class ProfileViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val accountManager = AccountManager(application)
|
||||
|
||||
private val _state = MutableStateFlow(ProfileState())
|
||||
val state: StateFlow<ProfileState> = _state
|
||||
|
||||
private var resultCallback: ((Packet) -> Unit)? = null
|
||||
|
||||
init {
|
||||
// Register listener for PacketResult (0x02)
|
||||
resultCallback = { packet ->
|
||||
if (packet is PacketResult) {
|
||||
handlePacketResult(packet)
|
||||
}
|
||||
}
|
||||
ProtocolManager.waitPacket(0x02, resultCallback!!)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// Unregister listener
|
||||
resultCallback?.let { ProtocolManager.unwaitPacket(0x02, it) }
|
||||
}
|
||||
|
||||
private fun addLog(message: String) {
|
||||
val timestamp = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
|
||||
val logMessage = "[$timestamp] $message"
|
||||
_state.value = _state.value.copy(logs = _state.value.logs + logMessage)
|
||||
Log.d("ProfileViewModel", logMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Save profile (name and username) to server
|
||||
*/
|
||||
fun saveProfile(
|
||||
publicKey: String,
|
||||
privateKeyHash: String,
|
||||
name: String,
|
||||
username: String
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
addLog("=== Starting profile save ===")
|
||||
addLog("Public Key: ${publicKey.take(20)}...")
|
||||
addLog("Name: '$name'")
|
||||
addLog("Username: '$username'")
|
||||
|
||||
_state.value = _state.value.copy(isSaving = true, error = null, saveSuccess = false)
|
||||
|
||||
// Create and send PacketUserInfo
|
||||
val packet = PacketUserInfo()
|
||||
packet.publicKey = publicKey
|
||||
packet.title = name
|
||||
packet.username = username
|
||||
|
||||
addLog("Packet created: PacketUserInfo")
|
||||
addLog("Packet ID: 0x${packet.getPacketId().toString(16).uppercase()}")
|
||||
addLog("Sending packet to server...")
|
||||
|
||||
ProtocolManager.send(packet)
|
||||
addLog("Packet sent successfully")
|
||||
|
||||
// Wait for response (handled in handlePacketResult)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProfileViewModel", "Error saving profile", e)
|
||||
_state.value = _state.value.copy(
|
||||
isSaving = false,
|
||||
error = e.message ?: "Unknown error"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle PacketResult from server
|
||||
*/
|
||||
private fun handlePacketResult(packet: PacketResult) {
|
||||
viewModelScope.launch {
|
||||
addLog("Received PacketResult from server")
|
||||
addLog("Result code: ${packet.resultCode}")
|
||||
addLog("Result message: '${packet.message}'")
|
||||
|
||||
when (packet.resultCode) {
|
||||
0 -> { // SUCCESS
|
||||
addLog("✅ SUCCESS: Profile saved successfully")
|
||||
_state.value = _state.value.copy(
|
||||
isSaving = false,
|
||||
saveSuccess = true,
|
||||
error = null
|
||||
)
|
||||
addLog("State updated: saveSuccess=true")
|
||||
|
||||
// Update local account name if needed
|
||||
// (username is stored on server only)
|
||||
|
||||
}
|
||||
else -> { // ERROR
|
||||
addLog("❌ ERROR: Profile save failed")
|
||||
addLog("Error details: ${packet.message}")
|
||||
_state.value = _state.value.copy(
|
||||
isSaving = false,
|
||||
error = packet.message.ifEmpty { "Failed to save profile" }
|
||||
)
|
||||
addLog("State updated: error='${packet.message}'")
|
||||
}
|
||||
}
|
||||
addLog("=== Profile save completed ===")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update local account name
|
||||
*/
|
||||
fun updateLocalAccountName(publicKey: String, name: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val account = accountManager.getAccount(publicKey)
|
||||
if (account != null) {
|
||||
val updatedAccount = EncryptedAccount(
|
||||
publicKey = account.publicKey,
|
||||
encryptedPrivateKey = account.encryptedPrivateKey,
|
||||
encryptedSeedPhrase = account.encryptedSeedPhrase,
|
||||
name = name
|
||||
)
|
||||
accountManager.saveAccount(updatedAccount)
|
||||
Log.d("ProfileViewModel", "Local account name updated")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProfileViewModel", "Error updating local account name", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset save success state
|
||||
*/
|
||||
fun resetSaveState() {
|
||||
_state.value = _state.value.copy(saveSuccess = false, error = null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logs
|
||||
*/
|
||||
fun clearLogs() {
|
||||
_state.value = _state.value.copy(logs = emptyList())
|
||||
addLog("Logs cleared")
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,37 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.outlined.ContentCopy
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SafetyScreen(
|
||||
isDarkTheme: Boolean,
|
||||
accountPublicKey: String,
|
||||
accountPrivateKey: String = "",
|
||||
onBack: () -> Unit,
|
||||
onBackupClick: () -> Unit,
|
||||
onDeleteAccount: () -> Unit
|
||||
@@ -30,12 +40,30 @@ fun SafetyScreen(
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val redColor = if (isDarkTheme) Color(0xFFFF8787) else Color(0xFFEF4444)
|
||||
val greenColor = if (isDarkTheme) Color(0xFF3FC158) else Color(0xFF34C759)
|
||||
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// Copy states
|
||||
var copiedPublicKey by remember { mutableStateOf(false) }
|
||||
var copiedPrivateKey by remember { mutableStateOf(false) }
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
// Short display keys
|
||||
val shortPublicKey = if (accountPublicKey.length > 20)
|
||||
accountPublicKey.take(20) else accountPublicKey
|
||||
val shortPrivateKey = if (accountPrivateKey.length > 20)
|
||||
accountPrivateKey.take(20) else "B8zPXVMQrYkxaWE39v"
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
@@ -52,11 +80,11 @@ fun SafetyScreen(
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = if (isDarkTheme) Color.White else Color.Black
|
||||
tint = textColor
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Safety & Security",
|
||||
text = "Safety",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
@@ -72,47 +100,175 @@ fun SafetyScreen(
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Warning Card
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Public Key - clickable row with copy feedback
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFFF9800).copy(alpha = 0.15f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
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
|
||||
}
|
||||
},
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFFF9800),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(top = 2.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = "Never share your private key or seed phrase with anyone. Keep your backup secure and private.",
|
||||
fontSize = 13.sp,
|
||||
text = "Public Key",
|
||||
fontSize = 15.sp,
|
||||
color = textColor,
|
||||
lineHeight = 18.sp
|
||||
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))
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 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
|
||||
)
|
||||
|
||||
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(
|
||||
imageVector = Icons.Filled.ChevronRight,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Public Key Section
|
||||
Text(
|
||||
text = "Public Key",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
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
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Delete Account
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onDeleteAccount),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
@@ -123,106 +279,23 @@ fun SafetyScreen(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = accountPublicKey.take(20) + "..." + accountPublicKey.takeLast(20),
|
||||
fontSize = 12.sp,
|
||||
color = textColor,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
IconButton(
|
||||
onClick = { /* TODO: Copy to clipboard */ }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.ContentCopy,
|
||||
contentDescription = "Copy",
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "This is your public key. You can safely share this with others so they can message you.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Backup Section
|
||||
Text(
|
||||
text = "Backup & Recovery",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.ContentCopy,
|
||||
iconBackground = Color(0xFFFF9800),
|
||||
title = "Backup Seed Phrase",
|
||||
subtitle = "View and save your recovery phrase",
|
||||
onClick = onBackupClick,
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = Color(0xFFFF9800)
|
||||
text = "Delete Account",
|
||||
fontSize = 15.sp,
|
||||
color = redColor,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Please save your seed phrase, it is necessary for future access to your conversations. Do not share this seed phrase with anyone.",
|
||||
fontSize = 12.sp,
|
||||
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 = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp),
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Danger Zone
|
||||
Text(
|
||||
text = "Danger Zone",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color(0xFFEF4444),
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.ContentCopy,
|
||||
iconBackground = Color(0xFFEF4444),
|
||||
title = "Delete Account",
|
||||
subtitle = "Permanently delete your account",
|
||||
onClick = onDeleteAccount,
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = Color(0xFFEF4444)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "This action cannot be undone. It will result in the complete deletion of account data from your device and server.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
@@ -40,12 +41,14 @@ fun ThemeScreen(
|
||||
|
||||
// Theme mode: "light", "dark", "auto"
|
||||
var themeMode by remember { mutableStateOf(if (isDarkTheme) "dark" else "light") }
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -25,12 +26,14 @@ fun UpdatesScreen(
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
|
||||
// Handle back gesture
|
||||
BackHandler { onBack() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
|
||||
Reference in New Issue
Block a user