package com.rosetta.messenger import android.Manifest import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import com.google.firebase.FirebaseApp import com.google.firebase.messaging.FirebaseMessaging import com.rosetta.messenger.data.AccountManager import com.rosetta.messenger.data.DecryptedAccount import com.rosetta.messenger.data.PreferencesManager import com.rosetta.messenger.data.RecentSearchesManager import com.rosetta.messenger.database.RosettaDatabase import com.rosetta.messenger.repository.AvatarRepository import com.rosetta.messenger.network.PacketPushNotification import com.rosetta.messenger.network.ProtocolManager import com.rosetta.messenger.network.ProtocolState import com.rosetta.messenger.network.PushNotificationAction import com.rosetta.messenger.network.SearchUser import com.rosetta.messenger.ui.auth.AccountInfo import com.rosetta.messenger.ui.auth.AuthFlow import com.rosetta.messenger.ui.chats.ChatDetailScreen 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.crashlogs.CrashLogsScreen import com.rosetta.messenger.ui.splash.SplashScreen import com.rosetta.messenger.ui.theme.RosettaAndroidTheme import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import kotlinx.coroutines.delay import kotlinx.coroutines.launch import androidx.fragment.app.FragmentActivity class MainActivity : FragmentActivity() { private lateinit var preferencesManager: PreferencesManager private lateinit var accountManager: AccountManager companion object { private const val TAG = "MainActivity" // πŸ”” FCM Π›ΠΎΠ³ΠΈ для отобраТСния Π² UI private val _fcmLogs = mutableStateListOf() val fcmLogs: List get() = _fcmLogs fun addFcmLog(message: String) { val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date()) _fcmLogs.add(0, "[$timestamp] $message") // ДобавляСм Π² Π½Π°Ρ‡Π°Π»ΠΎ списка // ΠžΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ количСство Π»ΠΎΠ³ΠΎΠ² if (_fcmLogs.size > 20) { _fcmLogs.removeAt(_fcmLogs.size - 1) } } fun clearFcmLogs() { _fcmLogs.clear() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() preferencesManager = PreferencesManager(this) accountManager = AccountManager(this) RecentSearchesManager.init(this) // πŸ”₯ Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ ProtocolManager для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΎΠ½Π»Π°ΠΉΠ½ статусов ProtocolManager.initialize(this) // πŸ”” Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ Firebase для push-ΡƒΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ initializeFirebase() // πŸ”₯ ΠŸΠΎΠΌΠ΅Ρ‡Π°Π΅ΠΌ Ρ‡Ρ‚ΠΎ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π² foreground com.rosetta.messenger.push.RosettaFirebaseMessagingService.isAppInForeground = true // πŸ“± ΠŸΡ€Π΅Π΄Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ эмодзи Π² Ρ„ΠΎΠ½Π΅ для ΠΌΠ³Π½ΠΎΠ²Π΅Π½Π½ΠΎΠ³ΠΎ открытия ΠΏΠΈΠΊΠ΅Ρ€Π° // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ Π½ΠΎΠ²Ρ‹ΠΉ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ кэш OptimizedEmojiCache.preload(this) setContent { // πŸ”” Запрос Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ Π½Π° увСдомлСния для Android 13+ val notificationPermissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), onResult = { isGranted -> } ) // Π—Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅ΠΌ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΌ запускС (Android 13+) LaunchedEffect(Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val hasPermission = ContextCompat.checkSelfPermission( this@MainActivity, Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED if (!hasPermission) { notificationPermissionLauncher.launch( Manifest.permission.POST_NOTIFICATIONS ) } } } val scope = rememberCoroutineScope() val themeMode by preferencesManager.themeMode.collectAsState(initial = "dark") val systemInDarkTheme = isSystemInDarkTheme() val isDarkTheme = when (themeMode) { "light" -> false "dark" -> true "auto" -> systemInDarkTheme else -> true } val isLoggedIn by accountManager.isLoggedIn.collectAsState(initial = null) var showSplash by remember { mutableStateOf(true) } var showOnboarding by remember { mutableStateOf(true) } var hasExistingAccount by remember { mutableStateOf(null) } var currentAccount by remember { mutableStateOf(null) } var accountInfoList by remember { mutableStateOf>(emptyList()) } // Check for existing accounts and build AccountInfo list // Also force logout so user always sees unlock screen on app restart LaunchedEffect(Unit) { accountManager.logout() // Always start logged out val accounts = accountManager.getAllAccounts() hasExistingAccount = accounts.isNotEmpty() accountInfoList = accounts.map { account -> val shortKey = account.publicKey.take(7) val displayName = account.name ?: shortKey val initials = displayName .trim() .split(Regex("\\s+")) .filter { it.isNotEmpty() } .let { words -> when { words.isEmpty() -> "??" words.size == 1 -> words[0].take(2).uppercase() else -> "${words[0].first()}${words[1].first()}".uppercase() } } AccountInfo( id = account.publicKey, name = displayName, username = account.username ?: "", initials = initials, publicKey = account.publicKey ) } } // Wait for initial load if (hasExistingAccount == null) { Box( modifier = Modifier.fillMaxSize() .background( if (isDarkTheme) Color(0xFF1B1B1B) else Color.White ) ) return@setContent } RosettaAndroidTheme(darkTheme = isDarkTheme, animated = true) { Surface( modifier = Modifier.fillMaxSize(), color = if (isDarkTheme) Color(0xFF1B1B1B) else Color.White ) { AnimatedContent( targetState = when { showSplash -> "splash" showOnboarding && hasExistingAccount == false -> "onboarding" isLoggedIn != true && hasExistingAccount == false -> "auth_new" isLoggedIn != true && hasExistingAccount == true -> "auth_unlock" else -> "main" }, transitionSpec = { fadeIn(animationSpec = tween(600)) togetherWith fadeOut(animationSpec = tween(600)) }, label = "screenTransition" ) { screen -> when (screen) { "splash" -> { SplashScreen( isDarkTheme = isDarkTheme, onSplashComplete = { showSplash = false } ) } "onboarding" -> { OnboardingScreen( isDarkTheme = isDarkTheme, onThemeToggle = { scope.launch { preferencesManager.setDarkTheme(!isDarkTheme) } }, onStartMessaging = { showOnboarding = false } ) } "auth_new", "auth_unlock" -> { AuthFlow( isDarkTheme = isDarkTheme, hasExistingAccount = screen == "auth_unlock", accounts = accountInfoList, accountManager = accountManager, onAuthComplete = { account -> currentAccount = account hasExistingAccount = true // Save as last logged account account?.let { accountManager.setLastLoggedPublicKey(it.publicKey) } // πŸ“€ ΠžΡ‚ΠΏΡ€Π°Π²Π»ΡΠ΅ΠΌ FCM Ρ‚ΠΎΠΊΠ΅Π½ Π½Π° сСрвСр послС ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠΉ // Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ account?.let { sendFcmTokenToServer(it) } // Reload accounts list scope.launch { val accounts = accountManager.getAllAccounts() accountInfoList = accounts.map { acc -> val shortKey = acc.publicKey.take(7) val displayName = acc.name ?: shortKey val initials = displayName .trim() .split(Regex("\\s+")) .filter { it.isNotEmpty() } .let { words -> when { words.isEmpty() -> "??" words.size == 1 -> words[0] .take( 2 ) .uppercase() else -> "${words[0].first()}${words[1].first()}".uppercase() } } AccountInfo( id = acc.publicKey, name = displayName, username = acc.username ?: "", initials = initials, publicKey = acc.publicKey ) } } }, onLogout = { // Set currentAccount to null immediately to prevent UI // lag currentAccount = null scope.launch { com.rosetta.messenger.network.ProtocolManager .disconnect() accountManager.logout() } } ) } "main" -> { MainScreen( account = currentAccount, isDarkTheme = isDarkTheme, themeMode = themeMode, onToggleTheme = { scope.launch { preferencesManager.setDarkTheme(!isDarkTheme) } }, onThemeModeChange = { mode -> scope.launch { preferencesManager.setThemeMode(mode) } }, onLogout = { // Set currentAccount to null immediately to prevent UI // lag currentAccount = null scope.launch { com.rosetta.messenger.network.ProtocolManager .disconnect() accountManager.logout() } }, onAccountInfoUpdated = { // Reload account list when profile is updated val accounts = accountManager.getAllAccounts() accountInfoList = accounts.map { acc -> val shortKey = acc.publicKey.take(7) val displayName = acc.name ?: shortKey val initials = displayName .trim() .split(Regex("\\s+")) .filter { it.isNotEmpty() } .let { words -> when { words.isEmpty() -> "??" words.size == 1 -> words[0] .take(2) .uppercase() else -> "${words[0].first()}${words[1].first()}".uppercase() } } AccountInfo( id = acc.publicKey, name = displayName, username = acc.username ?: "", initials = initials, publicKey = acc.publicKey ) } } ) } } } } } } } override fun onResume() { super.onResume() // πŸ”₯ ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ стало Π²ΠΈΠ΄ΠΈΠΌΡ‹ΠΌ - ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ увСдомлСния com.rosetta.messenger.push.RosettaFirebaseMessagingService.isAppInForeground = true } override fun onPause() { super.onPause() // πŸ”₯ ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΡƒΡˆΠ»ΠΎ Π² background - Π²ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ увСдомлСния com.rosetta.messenger.push.RosettaFirebaseMessagingService.isAppInForeground = false } /** πŸ”” Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ Firebase Cloud Messaging */ private fun initializeFirebase() { try { addFcmLog("πŸ”” Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ Firebase...") // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ Firebase FirebaseApp.initializeApp(this) addFcmLog("βœ… Firebase ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½") // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ FCM Ρ‚ΠΎΠΊΠ΅Π½ addFcmLog("πŸ“² Запрос FCM Ρ‚ΠΎΠΊΠ΅Π½Π°...") FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (!task.isSuccessful) { addFcmLog("❌ Ошибка получСния Ρ‚ΠΎΠΊΠ΅Π½Π°: ${task.exception?.message}") return@addOnCompleteListener } val token = task.result if (token != null) { val shortToken = "${token.take(12)}...${token.takeLast(8)}" addFcmLog("βœ… FCM Ρ‚ΠΎΠΊΠ΅Π½ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½: $shortToken") // БохраняСм Ρ‚ΠΎΠΊΠ΅Π½ локально saveFcmToken(token) addFcmLog("πŸ’Ύ Π’ΠΎΠΊΠ΅Π½ сохранСн локально") } else { addFcmLog("⚠️ Π’ΠΎΠΊΠ΅Π½ пустой") } // Π’ΠΎΠΊΠ΅Π½ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ Π½Π° сСрвСр послС ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠΉ Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ // (см. Π²Ρ‹Π·ΠΎΠ² sendFcmTokenToServer Π² onAccountLogin) } } catch (e: Exception) { addFcmLog("❌ Ошибка Firebase: ${e.message}") } } /** Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ FCM Ρ‚ΠΎΠΊΠ΅Π½ Π² SharedPreferences */ private fun saveFcmToken(token: String) { val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE) prefs.edit().putString("fcm_token", token).apply() } /** * ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ FCM Ρ‚ΠΎΠΊΠ΅Π½ Π½Π° сСрвСр ВызываСтся послС ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠΉ Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ, ΠΊΠΎΠ³Π΄Π° Π°ΠΊΠΊΠ°ΡƒΠ½Ρ‚ ΡƒΠΆΠ΅ * Ρ€Π°ΡΡˆΠΈΡ„Ρ€ΠΎΠ²Π°Π½ */ private fun sendFcmTokenToServer(account: DecryptedAccount) { lifecycleScope.launch { try { val prefs = getSharedPreferences("rosetta_prefs", MODE_PRIVATE) val token = prefs.getString("fcm_token", null) if (token == null) { addFcmLog("⚠️ НСт сохранСнного Ρ‚ΠΎΠΊΠ΅Π½Π° для ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ") return@launch } val shortToken = "${token.take(12)}...${token.takeLast(8)}" addFcmLog("πŸ“€ ΠŸΠΎΠ΄Π³ΠΎΡ‚ΠΎΠ²ΠΊΠ° ΠΊ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠ΅ Ρ‚ΠΎΠΊΠ΅Π½Π° Π½Π° сСрвСр") addFcmLog("⏳ ОТиданиС Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ...") // πŸ”₯ КРИВИЧНО: Π–Π΄Π΅ΠΌ ΠΏΠΎΠΊΠ° ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» станСт AUTHENTICATED var waitAttempts = 0 while (ProtocolManager.state.value != ProtocolState.AUTHENTICATED && waitAttempts < 50) { delay(100) // Π–Π΄Π΅ΠΌ 100ms waitAttempts++ } if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) { addFcmLog("❌ Π’Π°ΠΉΠΌΠ°ΡƒΡ‚ Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ (${waitAttempts * 100}ms)") return@launch } addFcmLog("βœ… АутСнтификация ΡƒΡΠΏΠ΅ΡˆΠ½Π°") addFcmLog("πŸ“¨ ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° Ρ‚ΠΎΠΊΠ΅Π½Π°: $shortToken") val packet = PacketPushNotification().apply { this.notificationsToken = token this.action = PushNotificationAction.SUBSCRIBE } ProtocolManager.send(packet) addFcmLog("βœ… ΠŸΠ°ΠΊΠ΅Ρ‚ ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ Π½Π° сСрвСр (ID: 0x10)") addFcmLog("πŸŽ‰ FCM Ρ‚ΠΎΠΊΠ΅Π½ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ зарСгистрирован!") } catch (e: Exception) { addFcmLog("❌ Ошибка ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ: ${e.message}") } } } } @Composable fun MainScreen( account: DecryptedAccount? = null, isDarkTheme: Boolean = true, themeMode: String = "dark", onToggleTheme: () -> Unit = {}, onThemeModeChange: (String) -> Unit = {}, onLogout: () -> Unit = {}, onAccountInfoUpdated: suspend () -> Unit = {} ) { // Reactive state for account name and username var accountName by remember { mutableStateOf(account?.name ?: "Account") } val accountPhone = account?.publicKey?.take(16)?.let { "+${it.take(1)} ${it.substring(1, 4)} ${it.substring(4, 7)}${it.substring(7)}" } ?: "+7 775 9932587" val accountPublicKey = account?.publicKey ?: "04c266b98ae5" val accountPrivateKey = account?.privateKey ?: "" val privateKeyHash = account?.privateKeyHash ?: "" // Username state - загруТаСтся ΠΈΠ· EncryptedAccount // Following desktop version pattern: username is stored locally and loaded on app start var accountUsername by remember { mutableStateOf("") } var reloadTrigger by remember { mutableIntStateOf(0) } // Load username AND name from AccountManager (persisted in DataStore) val context = LocalContext.current LaunchedEffect(accountPublicKey, reloadTrigger) { if (accountPublicKey.isNotBlank() && accountPublicKey != "04c266b98ae5") { val accountManager = AccountManager(context) val encryptedAccount = accountManager.getAccount(accountPublicKey) accountUsername = encryptedAccount?.username ?: "" accountName = encryptedAccount?.name ?: accountName } } // БостояниС ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ»Π° для ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π² SearchScreen val protocolState by ProtocolManager.state.collectAsState() // Навигация ΠΌΠ΅ΠΆΠ΄Ρƒ экранами var selectedUser by remember { mutableStateOf(null) } var showSearchScreen by remember { mutableStateOf(false) } var showProfileScreen by remember { mutableStateOf(false) } var showOtherProfileScreen by remember { mutableStateOf(false) } var selectedOtherUser by remember { mutableStateOf(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) } var showLogsScreen by remember { mutableStateOf(false) } var showCrashLogsScreen by remember { mutableStateOf(false) } // ProfileViewModel для Π»ΠΎΠ³ΠΎΠ² val profileViewModel: com.rosetta.messenger.ui.settings.ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val profileState by profileViewModel.state.collectAsState() // AvatarRepository для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Π°Π²Π°Ρ‚Π°Ρ€Π°ΠΌΠΈ val avatarRepository = remember(accountPublicKey) { if (accountPublicKey.isNotBlank() && accountPublicKey != "04c266b98ae5") { val database = RosettaDatabase.getDatabase(context) AvatarRepository( context = context, avatarDao = database.avatarDao(), currentPublicKey = accountPublicKey ) } else { null } } // Coroutine scope for profile updates val mainScreenScope = rememberCoroutineScope() // πŸ”₯ ΠŸΡ€ΠΎΡΡ‚Π°Ρ навигация с fade-in Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠ΅ΠΉ Box(modifier = Modifier.fillMaxSize()) { // Base layer - chats list androidx.compose.animation.AnimatedVisibility( visible = !showBackupScreen && !showSafetyScreen && !showThemeScreen && !showUpdatesScreen && selectedUser == null && !showSearchScreen && !showProfileScreen && !showOtherProfileScreen && !showLogsScreen && !showCrashLogsScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { ChatsListScreen( isDarkTheme = isDarkTheme, accountName = accountName, accountUsername = accountUsername, 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 }, avatarRepository = avatarRepository, 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 showSafetyScreen = true }, onVerifyPassword = { password -> // Verify password by trying to decrypt the private key try { val publicKey = account?.publicKey ?: return@BackupScreen null val accountManager = AccountManager(context) val encryptedAccount = accountManager.getAccount(publicKey) if (encryptedAccount != null) { // Try to decrypt private key with password val decryptedPrivateKey = com.rosetta.messenger.crypto.CryptoManager.decryptWithPassword( encryptedAccount.encryptedPrivateKey, password ) if (decryptedPrivateKey != null) { // Password is correct, decrypt seed phrase com.rosetta.messenger.crypto.CryptoManager.decryptWithPassword( encryptedAccount.encryptedSeedPhrase, password ) } else { null } } else { null } } catch (e: Exception) { null } } ) } } androidx.compose.animation.AnimatedVisibility( visible = showSafetyScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showSafetyScreen) { SafetyScreen( isDarkTheme = isDarkTheme, accountPublicKey = accountPublicKey, accountPrivateKey = accountPrivateKey, onBack = { showSafetyScreen = false showProfileScreen = true }, onBackupClick = { showSafetyScreen = false showBackupScreen = true }, onDeleteAccount = { // TODO: Implement account deletion } ) } } androidx.compose.animation.AnimatedVisibility( visible = showThemeScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showThemeScreen) { ThemeScreen( isDarkTheme = isDarkTheme, currentThemeMode = themeMode, onBack = { showThemeScreen = false showProfileScreen = true }, onThemeModeChange = onThemeModeChange ) } } androidx.compose.animation.AnimatedVisibility( visible = showUpdatesScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showUpdatesScreen) { UpdatesScreen( isDarkTheme = isDarkTheme, onBack = { showUpdatesScreen = false } ) } } androidx.compose.animation.AnimatedVisibility( visible = selectedUser != null, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (selectedUser != null) { // Π­ΠΊΡ€Π°Π½ Ρ‡Π°Ρ‚Π° ChatDetailScreen( user = selectedUser!!, currentUserPublicKey = accountPublicKey, currentUserPrivateKey = accountPrivateKey, onBack = { selectedUser = null }, onUserProfileClick = { user -> // ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅ΠΌ ΠΏΡ€ΠΎΡ„ΠΈΠ»ΡŒ Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ selectedOtherUser = user showOtherProfileScreen = true }, onNavigateToChat = { forwardUser -> // πŸ“¨ Forward: ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ Π² Π²Ρ‹Π±Ρ€Π°Π½Π½Ρ‹ΠΉ Ρ‡Π°Ρ‚ с ΠΏΠΎΠ»Π½Ρ‹ΠΌΠΈ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ selectedUser = forwardUser }, isDarkTheme = isDarkTheme, avatarRepository = avatarRepository ) } } androidx.compose.animation.AnimatedVisibility( visible = showSearchScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showSearchScreen) { // Π­ΠΊΡ€Π°Π½ поиска SearchScreen( privateKeyHash = privateKeyHash, currentUserPublicKey = accountPublicKey, isDarkTheme = isDarkTheme, protocolState = protocolState, onBackClick = { showSearchScreen = false }, onUserSelect = { selectedSearchUser -> showSearchScreen = false selectedUser = selectedSearchUser } ) } } androidx.compose.animation.AnimatedVisibility( visible = showProfileScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showProfileScreen) { // Π­ΠΊΡ€Π°Π½ профиля ProfileScreen( isDarkTheme = isDarkTheme, accountName = accountName, accountUsername = accountUsername, accountPublicKey = accountPublicKey, accountPrivateKeyHash = privateKeyHash, onBack = { showProfileScreen = false }, onSaveProfile = { name, username -> // Following desktop version pattern: // 1. Server confirms save (handled in ProfileViewModel) // 2. Local DB updated (handled in ProfileScreen LaunchedEffect) // 3. This callback updates UI state immediately accountName = name accountUsername = username // Reload account list so auth screen shows updated name mainScreenScope.launch { onAccountInfoUpdated() } }, onLogout = onLogout, onNavigateToTheme = { showProfileScreen = false showThemeScreen = true }, onNavigateToSafety = { showProfileScreen = false showSafetyScreen = true }, onNavigateToLogs = { showProfileScreen = false showLogsScreen = true }, onNavigateToCrashLogs = { showProfileScreen = false showCrashLogsScreen = true }, viewModel = profileViewModel, avatarRepository = avatarRepository, dialogDao = RosettaDatabase.getDatabase(context).dialogDao() ) } } androidx.compose.animation.AnimatedVisibility( visible = showLogsScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showLogsScreen) { com.rosetta.messenger.ui.settings.ProfileLogsScreen( isDarkTheme = isDarkTheme, logs = profileState.logs, onBack = { showLogsScreen = false showProfileScreen = true }, onClearLogs = { profileViewModel.clearLogs() } ) } } androidx.compose.animation.AnimatedVisibility( visible = showCrashLogsScreen, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) ) { if (showCrashLogsScreen) { CrashLogsScreen( onBackClick = { showCrashLogsScreen = false showProfileScreen = true } ) } } 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 }, avatarRepository = avatarRepository ) } } } }