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.chats.SearchScreen
|
||||||
import com.rosetta.messenger.ui.components.OptimizedEmojiCache
|
import com.rosetta.messenger.ui.components.OptimizedEmojiCache
|
||||||
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
|
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.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.splash.SplashScreen
|
||||||
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
import com.rosetta.messenger.ui.theme.RosettaAndroidTheme
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -439,79 +444,152 @@ fun MainScreen(
|
|||||||
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
|
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
|
||||||
var showSearchScreen by remember { mutableStateOf(false) }
|
var showSearchScreen by remember { mutableStateOf(false) }
|
||||||
var showProfileScreen by remember { mutableStateOf(false) }
|
var showProfileScreen by remember { mutableStateOf(false) }
|
||||||
|
var showOtherProfileScreen by remember { mutableStateOf(false) }
|
||||||
|
var selectedOtherUser by remember { mutableStateOf<SearchUser?>(null) }
|
||||||
|
|
||||||
|
// Дополнительные экраны настроек
|
||||||
|
var showUpdatesScreen by remember { mutableStateOf(false) }
|
||||||
|
var showThemeScreen by remember { mutableStateOf(false) }
|
||||||
|
var showSafetyScreen by remember { mutableStateOf(false) }
|
||||||
|
var showBackupScreen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 🔥 TELEGRAM-STYLE анимация - чистый slide БЕЗ прозрачности
|
// 🔥 Простая навигация с fade-in анимацией
|
||||||
AnimatedContent(
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
targetState = Pair(Pair(selectedUser, showSearchScreen), showProfileScreen),
|
// Base layer - chats list
|
||||||
transitionSpec = {
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
val isEnteringChat = targetState.first.first != null && initialState.first.first == null
|
visible = !showBackupScreen && !showSafetyScreen && !showThemeScreen &&
|
||||||
val isExitingChat = targetState.first.first == null && initialState.first.first != null
|
!showUpdatesScreen && selectedUser == null && !showSearchScreen &&
|
||||||
val isEnteringSearch = targetState.first.second && !initialState.first.second
|
!showProfileScreen && !showOtherProfileScreen,
|
||||||
val isExitingSearch = !targetState.first.second && initialState.first.second
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
val isEnteringProfile = targetState.second && !initialState.second
|
exit = fadeOut(animationSpec = tween(200))
|
||||||
val isExitingProfile = !targetState.second && initialState.second
|
) {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
// Other screens with fade animation
|
||||||
// 🚀 Вход в чат - плавный fade
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
isEnteringChat -> {
|
visible = showBackupScreen,
|
||||||
fadeIn(animationSpec = tween(200)) togetherWith
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
fadeOut(animationSpec = tween(150))
|
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
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
isExitingChat -> {
|
visible = showSafetyScreen,
|
||||||
fadeIn(animationSpec = tween(200)) togetherWith
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
fadeOut(animationSpec = tween(150))
|
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
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
isEnteringSearch -> {
|
visible = showThemeScreen,
|
||||||
fadeIn(animationSpec = tween(200)) togetherWith
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
fadeOut(animationSpec = tween(150))
|
exit = fadeOut(animationSpec = tween(200))
|
||||||
|
) {
|
||||||
|
if (showThemeScreen) {
|
||||||
|
ThemeScreen(
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
onBack = { showThemeScreen = false },
|
||||||
|
onThemeChange = { isDark ->
|
||||||
|
onToggleTheme()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔙 Выход из Search - плавный fade
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
isEnteringSearch -> {
|
visible = showUpdatesScreen,
|
||||||
fadeIn(animationSpec = tween(200)) togetherWith
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
fadeOut(animationSpec = tween(150))
|
exit = fadeOut(animationSpec = tween(200))
|
||||||
}
|
) {
|
||||||
|
if (showUpdatesScreen) {
|
||||||
|
UpdatesScreen(
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
onBack = { showUpdatesScreen = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔙 Выход из Search - плавный fade
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
isExitingSearch -> {
|
visible = selectedUser != null,
|
||||||
fadeIn(animationSpec = tween(200)) togetherWith
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
fadeOut(animationSpec = tween(150))
|
exit = fadeOut(animationSpec = tween(200))
|
||||||
}
|
) {
|
||||||
|
if (selectedUser != null) {
|
||||||
// 👤 Вход в 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 -> {
|
|
||||||
// Экран чата
|
// Экран чата
|
||||||
ChatDetailScreen(
|
ChatDetailScreen(
|
||||||
user = currentUser,
|
user = selectedUser!!,
|
||||||
currentUserPublicKey = accountPublicKey,
|
currentUserPublicKey = accountPublicKey,
|
||||||
currentUserPrivateKey = accountPrivateKey,
|
currentUserPrivateKey = accountPrivateKey,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
onBack = { selectedUser = null },
|
onBack = { selectedUser = null },
|
||||||
|
onUserProfileClick = { user ->
|
||||||
|
// Открываем профиль другого пользователя
|
||||||
|
selectedOtherUser = user
|
||||||
|
showOtherProfileScreen = true
|
||||||
|
},
|
||||||
onNavigateToChat = { publicKey ->
|
onNavigateToChat = { publicKey ->
|
||||||
// 📨 Forward: переход в выбранный чат
|
// 📨 Forward: переход в выбранный чат
|
||||||
// Нужно получить SearchUser из публичного ключа
|
// Нужно получить 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(
|
SearchScreen(
|
||||||
privateKeyHash = privateKeyHash,
|
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(
|
ProfileScreen(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
@@ -554,67 +646,30 @@ fun MainScreen(
|
|||||||
// TODO: Save profile changes
|
// TODO: Save profile changes
|
||||||
Log.d("MainActivity", "Saving profile: name=$name, username=$username")
|
Log.d("MainActivity", "Saving profile: name=$name, username=$username")
|
||||||
},
|
},
|
||||||
onLogout = {
|
onLogout = onLogout,
|
||||||
// TODO: Implement logout
|
|
||||||
Log.d("MainActivity", "Logout requested")
|
|
||||||
},
|
|
||||||
onNavigateToTheme = {
|
onNavigateToTheme = {
|
||||||
// Toggle theme for now
|
showThemeScreen = true
|
||||||
onToggleTheme()
|
|
||||||
},
|
},
|
||||||
onNavigateToSafety = {
|
onNavigateToSafety = {
|
||||||
// TODO: Navigate to safety screen
|
showSafetyScreen = true
|
||||||
Log.d("MainActivity", "Navigate to safety")
|
|
||||||
},
|
|
||||||
onNavigateToUpdates = {
|
|
||||||
// TODO: Navigate to updates screen
|
|
||||||
Log.d("MainActivity", "Navigate to updates")
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
}
|
||||||
// Список чатов
|
|
||||||
ChatsListScreen(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
isDarkTheme = isDarkTheme,
|
visible = showOtherProfileScreen,
|
||||||
accountName = accountName,
|
enter = fadeIn(animationSpec = tween(300)),
|
||||||
accountPhone = accountPhone,
|
exit = fadeOut(animationSpec = tween(200))
|
||||||
accountPublicKey = accountPublicKey,
|
) {
|
||||||
accountPrivateKey = accountPrivateKey,
|
if (showOtherProfileScreen && selectedOtherUser != null) {
|
||||||
privateKeyHash = privateKeyHash,
|
OtherProfileScreen(
|
||||||
onToggleTheme = onToggleTheme,
|
user = selectedOtherUser!!,
|
||||||
onProfileClick = {
|
isDarkTheme = isDarkTheme,
|
||||||
showProfileScreen = true
|
onBack = {
|
||||||
},
|
showOtherProfileScreen = false
|
||||||
onNewGroupClick = {
|
selectedOtherUser = null
|
||||||
// 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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ fun ChatDetailScreen(
|
|||||||
currentUserPrivateKey: String,
|
currentUserPrivateKey: String,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onUserProfileClick: () -> Unit = {},
|
onUserProfileClick: (SearchUser) -> Unit = {},
|
||||||
onNavigateToChat: (String) -> Unit = {}, // 📨 Callback для навигации в другой чат (Forward)
|
onNavigateToChat: (String) -> Unit = {}, // 📨 Callback для навигации в другой чат (Forward)
|
||||||
viewModel: ChatViewModel = viewModel()
|
viewModel: ChatViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
@@ -964,7 +964,7 @@ fun ChatDetailScreen(
|
|||||||
?.hide()
|
?.hide()
|
||||||
focusManager
|
focusManager
|
||||||
.clearFocus()
|
.clearFocus()
|
||||||
onUserProfileClick()
|
onUserProfileClick(user)
|
||||||
},
|
},
|
||||||
contentAlignment =
|
contentAlignment =
|
||||||
Alignment.Center
|
Alignment.Center
|
||||||
@@ -1032,7 +1032,7 @@ fun ChatDetailScreen(
|
|||||||
?.hide()
|
?.hide()
|
||||||
focusManager
|
focusManager
|
||||||
.clearFocus()
|
.clearFocus()
|
||||||
onUserProfileClick()
|
onUserProfileClick(user)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Row(
|
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,
|
onSaveProfile: (name: String, username: String) -> Unit,
|
||||||
onLogout: () -> Unit,
|
onLogout: () -> Unit,
|
||||||
onNavigateToTheme: () -> Unit = {},
|
onNavigateToTheme: () -> Unit = {},
|
||||||
onNavigateToSafety: () -> Unit = {},
|
onNavigateToSafety: () -> Unit = {}
|
||||||
onNavigateToUpdates: () -> Unit = {}
|
|
||||||
) {
|
) {
|
||||||
// Цвета в зависимости от темы - такие же как в ChatsListScreen
|
// Цвета в зависимости от темы - такие же как в ChatsListScreen
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
|
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
|
||||||
@@ -210,15 +209,6 @@ fun ProfileScreen(
|
|||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
) {
|
) {
|
||||||
Column {
|
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(
|
ProfileNavigationItem(
|
||||||
icon = Icons.Outlined.Brush,
|
icon = Icons.Outlined.Brush,
|
||||||
iconBackground = Color(0xFF6366F1), // indigo
|
iconBackground = Color(0xFF6366F1), // indigo
|
||||||
@@ -558,7 +548,7 @@ private fun ProfileCopyField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ProfileNavigationItem(
|
fun ProfileNavigationItem(
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
iconBackground: Color,
|
iconBackground: Color,
|
||||||
title: String,
|
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