feat: Enhance user profile and settings screens
- Updated ChatDetailScreen to pass user data on profile click. - Added BackupScreen for seed phrase recovery with password verification. - Introduced OtherProfileScreen for viewing and managing other users' profiles. - Created SafetyScreen for account security options including backup and deletion. - Developed ThemeScreen for theme customization with light, dark, and system modes. - Implemented UpdatesScreen to display app version and check for updates. - Removed unused navigation item for updates from ProfileScreen.
This commit is contained in:
@@ -39,7 +39,12 @@ import com.rosetta.messenger.ui.chats.ChatsListScreen
|
||||
import com.rosetta.messenger.ui.chats.SearchScreen
|
||||
import com.rosetta.messenger.ui.components.OptimizedEmojiCache
|
||||
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
|
||||
import com.rosetta.messenger.ui.settings.BackupScreen
|
||||
import com.rosetta.messenger.ui.settings.OtherProfileScreen
|
||||
import com.rosetta.messenger.ui.settings.ProfileScreen
|
||||
import com.rosetta.messenger.ui.settings.SafetyScreen
|
||||
import com.rosetta.messenger.ui.settings.ThemeScreen
|
||||
import com.rosetta.messenger.ui.settings.UpdatesScreen
|
||||
import com.rosetta.messenger.ui.splash.SplashScreen
|
||||
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -439,79 +444,152 @@ fun MainScreen(
|
||||
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
|
||||
var showSearchScreen by remember { mutableStateOf(false) }
|
||||
var showProfileScreen by remember { mutableStateOf(false) }
|
||||
var showOtherProfileScreen by remember { mutableStateOf(false) }
|
||||
var selectedOtherUser by remember { mutableStateOf<SearchUser?>(null) }
|
||||
|
||||
// 🔥 TELEGRAM-STYLE анимация - чистый slide БЕЗ прозрачности
|
||||
AnimatedContent(
|
||||
targetState = Pair(Pair(selectedUser, showSearchScreen), showProfileScreen),
|
||||
transitionSpec = {
|
||||
val isEnteringChat = targetState.first.first != null && initialState.first.first == null
|
||||
val isExitingChat = targetState.first.first == null && initialState.first.first != null
|
||||
val isEnteringSearch = targetState.first.second && !initialState.first.second
|
||||
val isExitingSearch = !targetState.first.second && initialState.first.second
|
||||
val isEnteringProfile = targetState.second && !initialState.second
|
||||
val isExitingProfile = !targetState.second && initialState.second
|
||||
// Дополнительные экраны настроек
|
||||
var showUpdatesScreen by remember { mutableStateOf(false) }
|
||||
var showThemeScreen by remember { mutableStateOf(false) }
|
||||
var showSafetyScreen by remember { mutableStateOf(false) }
|
||||
var showBackupScreen by remember { mutableStateOf(false) }
|
||||
|
||||
when {
|
||||
// 🚀 Вход в чат - плавный fade
|
||||
isEnteringChat -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
// 🔥 Простая навигация с fade-in анимацией
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
// Base layer - chats list
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = !showBackupScreen && !showSafetyScreen && !showThemeScreen &&
|
||||
!showUpdatesScreen && selectedUser == null && !showSearchScreen &&
|
||||
!showProfileScreen && !showOtherProfileScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
ChatsListScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
accountName = accountName,
|
||||
accountPhone = accountPhone,
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKey = accountPrivateKey,
|
||||
privateKeyHash = privateKeyHash,
|
||||
onToggleTheme = onToggleTheme,
|
||||
onProfileClick = {
|
||||
showProfileScreen = true
|
||||
},
|
||||
onNewGroupClick = {
|
||||
// TODO: Navigate to new group
|
||||
},
|
||||
onCallsClick = {
|
||||
// TODO: Navigate to calls
|
||||
},
|
||||
onSavedMessagesClick = {
|
||||
// Открываем чат с самим собой (Saved Messages)
|
||||
selectedUser =
|
||||
SearchUser(
|
||||
title = "Saved Messages",
|
||||
username = "",
|
||||
publicKey = accountPublicKey,
|
||||
verified = 0,
|
||||
online = 1
|
||||
)
|
||||
},
|
||||
onSettingsClick = { showProfileScreen = true },
|
||||
onInviteFriendsClick = {
|
||||
// TODO: Share invite link
|
||||
},
|
||||
onSearchClick = { showSearchScreen = true },
|
||||
onNewChat = {
|
||||
// TODO: Show new chat screen
|
||||
},
|
||||
onUserSelect = { selectedChatUser -> selectedUser = selectedChatUser },
|
||||
onLogout = onLogout
|
||||
)
|
||||
}
|
||||
|
||||
// Other screens with fade animation
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showBackupScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showBackupScreen) {
|
||||
BackupScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { showBackupScreen = false },
|
||||
onVerifyPassword = { password ->
|
||||
// TODO: Implement password verification
|
||||
if (password == "test") {
|
||||
"word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12"
|
||||
} else null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 🔙 Выход из чата - плавный fade
|
||||
isExitingChat -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showSafetyScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showSafetyScreen) {
|
||||
SafetyScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
accountPublicKey = accountPublicKey,
|
||||
onBack = { showSafetyScreen = false },
|
||||
onBackupClick = { showBackupScreen = true },
|
||||
onDeleteAccount = {
|
||||
// TODO: Implement account deletion
|
||||
Log.d("MainActivity", "Delete account requested")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 Вход в Search - плавный fade
|
||||
isEnteringSearch -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showThemeScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showThemeScreen) {
|
||||
ThemeScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { showThemeScreen = false },
|
||||
onThemeChange = { isDark ->
|
||||
onToggleTheme()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 🔙 Выход из Search - плавный fade
|
||||
isEnteringSearch -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showUpdatesScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showUpdatesScreen) {
|
||||
UpdatesScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { showUpdatesScreen = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 🔙 Выход из Search - плавный fade
|
||||
isExitingSearch -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// 👤 Вход в Profile - плавный fade
|
||||
isEnteringProfile -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// 🔙 Выход из Profile - плавный fade
|
||||
isExitingProfile -> {
|
||||
fadeIn(animationSpec = tween(200)) togetherWith
|
||||
fadeOut(animationSpec = tween(150))
|
||||
}
|
||||
|
||||
// Default - мгновенный переход
|
||||
else -> {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
}
|
||||
}
|
||||
},
|
||||
label = "screenNavigation"
|
||||
) { (chatAndSearchState, isProfileOpen) ->
|
||||
val (currentUser, isSearchOpen) = chatAndSearchState
|
||||
when {
|
||||
currentUser != null -> {
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = selectedUser != null,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (selectedUser != null) {
|
||||
// Экран чата
|
||||
ChatDetailScreen(
|
||||
user = currentUser,
|
||||
user = selectedUser!!,
|
||||
currentUserPublicKey = accountPublicKey,
|
||||
currentUserPrivateKey = accountPrivateKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { selectedUser = null },
|
||||
onUserProfileClick = { user ->
|
||||
// Открываем профиль другого пользователя
|
||||
selectedOtherUser = user
|
||||
showOtherProfileScreen = true
|
||||
},
|
||||
onNavigateToChat = { publicKey ->
|
||||
// 📨 Forward: переход в выбранный чат
|
||||
// Нужно получить SearchUser из публичного ключа
|
||||
@@ -528,7 +606,14 @@ fun MainScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
isSearchOpen -> {
|
||||
}
|
||||
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showSearchScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showSearchScreen) {
|
||||
// Экран поиска
|
||||
SearchScreen(
|
||||
privateKeyHash = privateKeyHash,
|
||||
@@ -542,7 +627,14 @@ fun MainScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
isProfileOpen -> {
|
||||
}
|
||||
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showProfileScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showProfileScreen) {
|
||||
// Экран профиля
|
||||
ProfileScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
@@ -554,67 +646,30 @@ fun MainScreen(
|
||||
// TODO: Save profile changes
|
||||
Log.d("MainActivity", "Saving profile: name=$name, username=$username")
|
||||
},
|
||||
onLogout = {
|
||||
// TODO: Implement logout
|
||||
Log.d("MainActivity", "Logout requested")
|
||||
},
|
||||
onLogout = onLogout,
|
||||
onNavigateToTheme = {
|
||||
// Toggle theme for now
|
||||
onToggleTheme()
|
||||
showThemeScreen = true
|
||||
},
|
||||
onNavigateToSafety = {
|
||||
// TODO: Navigate to safety screen
|
||||
Log.d("MainActivity", "Navigate to safety")
|
||||
},
|
||||
onNavigateToUpdates = {
|
||||
// TODO: Navigate to updates screen
|
||||
Log.d("MainActivity", "Navigate to updates")
|
||||
showSafetyScreen = true
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Список чатов
|
||||
ChatsListScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
accountName = accountName,
|
||||
accountPhone = accountPhone,
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKey = accountPrivateKey,
|
||||
privateKeyHash = privateKeyHash,
|
||||
onToggleTheme = onToggleTheme,
|
||||
onProfileClick = {
|
||||
showProfileScreen = true
|
||||
},
|
||||
onNewGroupClick = {
|
||||
// TODO: Navigate to new group
|
||||
},
|
||||
onContactsClick = {
|
||||
// TODO: Navigate to contacts
|
||||
},
|
||||
onCallsClick = {
|
||||
// TODO: Navigate to calls
|
||||
},
|
||||
onSavedMessagesClick = {
|
||||
// Открываем чат с самим собой (Saved Messages)
|
||||
selectedUser =
|
||||
SearchUser(
|
||||
title = "Saved Messages",
|
||||
username = "",
|
||||
publicKey = accountPublicKey,
|
||||
verified = 0,
|
||||
online = 1
|
||||
)
|
||||
},
|
||||
onSettingsClick = { showProfileScreen = true },
|
||||
onInviteFriendsClick = {
|
||||
// TODO: Share invite link
|
||||
},
|
||||
onSearchClick = { showSearchScreen = true },
|
||||
onNewChat = {
|
||||
// TODO: Show new chat screen
|
||||
},
|
||||
onUserSelect = { selectedChatUser -> selectedUser = selectedChatUser },
|
||||
onLogout = onLogout
|
||||
}
|
||||
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showOtherProfileScreen,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
if (showOtherProfileScreen && selectedOtherUser != null) {
|
||||
OtherProfileScreen(
|
||||
user = selectedOtherUser!!,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = {
|
||||
showOtherProfileScreen = false
|
||||
selectedOtherUser = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ fun ChatDetailScreen(
|
||||
currentUserPrivateKey: String,
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onUserProfileClick: () -> Unit = {},
|
||||
onUserProfileClick: (SearchUser) -> Unit = {},
|
||||
onNavigateToChat: (String) -> Unit = {}, // 📨 Callback для навигации в другой чат (Forward)
|
||||
viewModel: ChatViewModel = viewModel()
|
||||
) {
|
||||
@@ -964,7 +964,7 @@ fun ChatDetailScreen(
|
||||
?.hide()
|
||||
focusManager
|
||||
.clearFocus()
|
||||
onUserProfileClick()
|
||||
onUserProfileClick(user)
|
||||
},
|
||||
contentAlignment =
|
||||
Alignment.Center
|
||||
@@ -1032,7 +1032,7 @@ fun ChatDetailScreen(
|
||||
?.hide()
|
||||
focusManager
|
||||
.clearFocus()
|
||||
onUserProfileClick()
|
||||
onUserProfileClick(user)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
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.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun BackupScreen(
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onVerifyPassword: (String) -> String? // Returns seed phrase if password is correct
|
||||
) {
|
||||
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)
|
||||
|
||||
var password by remember { mutableStateOf("") }
|
||||
var seedPhrase by remember { mutableStateOf<String?>(null) }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = backgroundColor
|
||||
) {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Warning Card
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFEF4444).copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFEF4444),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(top = 2.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
BasicTextField(
|
||||
value = password,
|
||||
onValueChange = { newValue ->
|
||||
password = newValue
|
||||
// Try to verify password
|
||||
val result = onVerifyPassword(newValue)
|
||||
if (result != null) {
|
||||
seedPhrase = result
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
textStyle = TextStyle(
|
||||
color = textColor,
|
||||
fontSize = 16.sp
|
||||
),
|
||||
visualTransformation = if (passwordVisible)
|
||||
VisualTransformation.None
|
||||
else
|
||||
PasswordVisualTransformation(),
|
||||
decorationBox = { innerTextField ->
|
||||
if (password.isEmpty()) {
|
||||
Text(
|
||||
text = "Enter your password",
|
||||
color = secondaryTextColor,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = { passwordVisible = !passwordVisible }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (passwordVisible)
|
||||
Icons.Filled.Visibility
|
||||
else
|
||||
Icons.Filled.VisibilityOff,
|
||||
contentDescription = if (passwordVisible) "Hide password" else "Show password",
|
||||
tint = secondaryTextColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "To view your recovery phrase, enter the password you specified when creating your account.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.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)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(20.dp)) {
|
||||
val words = seedPhrase!!.split(" ")
|
||||
words.chunked(3).forEach { rowWords ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
rowWords.forEachIndexed { index, word ->
|
||||
Text(
|
||||
text = "${words.indexOf(word) + 1}. $word",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
// TODO: Copy to clipboard
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFF007AFF)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Copy to Clipboard",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.outlined.Block
|
||||
import androidx.compose.material.icons.outlined.ContentCopy
|
||||
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.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun OtherProfileScreen(
|
||||
isDarkTheme: Boolean,
|
||||
userName: String,
|
||||
userUsername: String,
|
||||
userPublicKey: String,
|
||||
isBlocked: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onBlockUser: () -> Unit,
|
||||
onUnblockUser: () -> 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)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
// Profile Card with avatar
|
||||
ProfileCard(
|
||||
name = userName,
|
||||
username = userUsername,
|
||||
publicKey = userPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = onBack,
|
||||
hasChanges = false,
|
||||
onSave = {}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
// Username Section (if available)
|
||||
if (userUsername.isNotBlank()) {
|
||||
Text(
|
||||
text = "Username",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "@$userUsername",
|
||||
fontSize = 16.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 = "Username for search user or send message.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
// Public Key Section
|
||||
Text(
|
||||
text = "Public Key",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = userPublicKey.take(20) + "..." + userPublicKey.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 user public key. If user haven't set a @username yet, you can send message using public key.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Block/Unblock Section
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
if (isBlocked) {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Block,
|
||||
iconBackground = Color(0xFF2E7D32), // green
|
||||
title = "Unblock User",
|
||||
subtitle = "Allow this user to message you",
|
||||
onClick = onUnblockUser,
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = Color(0xFF2E7D32)
|
||||
)
|
||||
} else {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Block,
|
||||
iconBackground = Color(0xFFEF4444), // red
|
||||
title = "Block this user",
|
||||
subtitle = "Prevent this user from messaging you",
|
||||
onClick = onBlockUser,
|
||||
isDarkTheme = isDarkTheme,
|
||||
hideChevron = true,
|
||||
textColor = Color(0xFFEF4444)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = if (isBlocked) {
|
||||
"If you want the user to be able to send you messages again, you can unblock them. You can block them later."
|
||||
} else {
|
||||
"The person will no longer be able to message you if you block them. You can unblock them later."
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,8 +101,7 @@ fun ProfileScreen(
|
||||
onSaveProfile: (name: String, username: String) -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
onNavigateToTheme: () -> Unit = {},
|
||||
onNavigateToSafety: () -> Unit = {},
|
||||
onNavigateToUpdates: () -> Unit = {}
|
||||
onNavigateToSafety: () -> Unit = {}
|
||||
) {
|
||||
// Цвета в зависимости от темы - такие же как в ChatsListScreen
|
||||
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
|
||||
@@ -210,15 +209,6 @@ fun ProfileScreen(
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Refresh,
|
||||
iconBackground = Color(0xFF84CC16), // lime-600
|
||||
title = "Updates",
|
||||
subtitle = "Check for new versions",
|
||||
onClick = onNavigateToUpdates,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showDivider = true
|
||||
)
|
||||
ProfileNavigationItem(
|
||||
icon = Icons.Outlined.Brush,
|
||||
iconBackground = Color(0xFF6366F1), // indigo
|
||||
@@ -558,7 +548,7 @@ private fun ProfileCopyField(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileNavigationItem(
|
||||
fun ProfileNavigationItem(
|
||||
icon: ImageVector,
|
||||
iconBackground: Color,
|
||||
title: String,
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun SafetyScreen(
|
||||
isDarkTheme: Boolean,
|
||||
accountPublicKey: String,
|
||||
onBack: () -> Unit,
|
||||
onBackupClick: () -> Unit,
|
||||
onDeleteAccount: () -> 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)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = backgroundColor
|
||||
) {
|
||||
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 = "Safety & Security",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Warning Card
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFFF9800).copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
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,
|
||||
color = textColor,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
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(
|
||||
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,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.Check
|
||||
import androidx.compose.material.icons.filled.DarkMode
|
||||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.material.icons.filled.Smartphone
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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
|
||||
|
||||
@Composable
|
||||
fun ThemeScreen(
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onThemeChange: (Boolean) -> Unit
|
||||
) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF),
|
||||
animationSpec = tween(300),
|
||||
label = "backgroundColor"
|
||||
)
|
||||
val surfaceColor by animateColorAsState(
|
||||
targetValue = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7),
|
||||
animationSpec = tween(300),
|
||||
label = "surfaceColor"
|
||||
)
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = if (isDarkTheme) Color.White else Color.Black,
|
||||
animationSpec = tween(300),
|
||||
label = "textColor"
|
||||
)
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
|
||||
// Theme mode: "light", "dark", "auto"
|
||||
var themeMode by remember { mutableStateOf(if (isDarkTheme) "dark" else "light") }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = backgroundColor
|
||||
) {
|
||||
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 = textColor
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Theme",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Column(
|
||||
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))
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 🔘 THEME MODE SELECTOR
|
||||
// ═══════════════════════════════════════════════════════
|
||||
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
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 💬 MESSAGE PREVIEW
|
||||
// ═══════════════════════════════════════════════════════
|
||||
Text(
|
||||
text = "Preview",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(bottom = 12.dp, start = 4.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(220.dp),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
// Received message
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
MessageBubble(
|
||||
text = "Hey! How are you? 👋",
|
||||
isFromMe = false,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Sent message
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
MessageBubble(
|
||||
text = "I'm great, thanks! 🎉",
|
||||
isFromMe = true,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Another received message
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
MessageBubble(
|
||||
text = "Love the new theme! 🌙",
|
||||
isFromMe = false,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThemeModeOption(
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
textColor: Color,
|
||||
secondaryTextColor: Color,
|
||||
showDivider: Boolean,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.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 = Color.White,
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageBubble(
|
||||
text: String,
|
||||
isFromMe: Boolean,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
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
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = textColorMsg,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun UpdatesScreen(
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> 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)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
// Top Bar
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = backgroundColor
|
||||
) {
|
||||
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 = "Updates",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Info Card
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFF2E7D32).copy(alpha = 0.2f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "✓ App is up to date",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Color(0xFF2E7D32)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "You're using the latest version",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Version Info
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "Application Version",
|
||||
fontSize = 14.sp,
|
||||
color = textColor
|
||||
)
|
||||
Text(
|
||||
text = "1.0.0",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Divider(color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "Build Number",
|
||||
fontSize = 14.sp,
|
||||
color = textColor
|
||||
)
|
||||
Text(
|
||||
text = "100",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "We recommend always keeping the app up to date to improve visual effects and have the latest features.",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Check for updates button
|
||||
Button(
|
||||
onClick = { /* TODO: Implement update check */ },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFF007AFF)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Check for Updates",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user