package com.rosetta.messenger import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.animation.* import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutLinearInEasing import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text 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.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp 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.network.ProtocolManager import com.rosetta.messenger.ui.auth.AccountInfo import com.rosetta.messenger.ui.auth.AuthFlow import com.rosetta.messenger.ui.chats.ChatsListScreen import com.rosetta.messenger.ui.chats.ChatDetailScreen import com.rosetta.messenger.ui.chats.SearchScreen import com.rosetta.messenger.network.SearchUser import com.rosetta.messenger.ui.components.EmojiCache import com.rosetta.messenger.ui.components.OptimizedEmojiCache import com.rosetta.messenger.ui.onboarding.OnboardingScreen import com.rosetta.messenger.ui.splash.SplashScreen import com.rosetta.messenger.ui.theme.RosettaAndroidTheme import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { private lateinit var preferencesManager: PreferencesManager private lateinit var accountManager: AccountManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() preferencesManager = PreferencesManager(this) accountManager = AccountManager(this) RecentSearchesManager.init(this) // 🔥 Инициализируем ProtocolManager для обработки онлайн статусов ProtocolManager.initialize(this) // 🚀 Предзагружаем эмодзи в фоне для мгновенного открытия пикера // Используем новый оптимизированный кэш OptimizedEmojiCache.preload(this) setContent { val scope = rememberCoroutineScope() val isDarkTheme by preferencesManager.isDarkTheme.collectAsState(initial = 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, 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_new", "auth_unlock" -> { AuthFlow( isDarkTheme = isDarkTheme, hasExistingAccount = screen == "auth_unlock", accounts = accountInfoList, onAuthComplete = { account -> currentAccount = account hasExistingAccount = true // Save as last logged account account?.let { accountManager.setLastLoggedPublicKey(it.publicKey) } // 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, 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, onToggleTheme = { scope.launch { preferencesManager.setDarkTheme(!isDarkTheme) } }, onLogout = { // Set currentAccount to null immediately to prevent UI lag currentAccount = null scope.launch { com.rosetta.messenger.network.ProtocolManager.disconnect() accountManager.logout() } } ) } } } } } } } } @Composable fun MainScreen( account: DecryptedAccount? = null, isDarkTheme: Boolean = true, onToggleTheme: () -> Unit = {}, onLogout: () -> Unit = {} ) { val accountName = account?.publicKey ?: "04c266b98ae5" 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 ?: "" // Состояние протокола для передачи в SearchScreen val protocolState by ProtocolManager.state.collectAsState() // Навигация между экранами var selectedUser by remember { mutableStateOf(null) } var showSearchScreen by remember { mutableStateOf(false) } // 🔥 TELEGRAM-STYLE анимация - чистый slide БЕЗ прозрачности AnimatedContent( targetState = Triple(selectedUser, showSearchScreen, Unit), transitionSpec = { val isEnteringChat = targetState.first != null && initialState.first == null val isExitingChat = targetState.first == null && initialState.first != null val isEnteringSearch = targetState.second && !initialState.second val isExitingSearch = !targetState.second && initialState.second when { // 🚀 Вход в чат - чистый slide справа (как Telegram/iOS) // Новый экран полностью покрывает старый, никакой прозрачности isEnteringChat -> { slideInHorizontally( initialOffsetX = { fullWidth -> fullWidth }, // Начинаем за экраном справа animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow // Плавно но быстро ) ) togetherWith slideOutHorizontally( targetOffsetX = { fullWidth -> -fullWidth / 4 }, // Старый экран уходит влево на 25% animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow ) ) } // 🔙 Выход из чата - обратный slide isExitingChat -> { slideInHorizontally( initialOffsetX = { fullWidth -> -fullWidth / 4 }, // Список возвращается слева animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium // Чуть быстрее при выходе ) ) togetherWith slideOutHorizontally( targetOffsetX = { fullWidth -> fullWidth }, // Чат уходит за экран вправо animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium ) ) } // 🔍 Вход в поиск - slide справа (как чаты) isEnteringSearch -> { slideInHorizontally( initialOffsetX = { fullWidth -> fullWidth }, // Начинаем за экраном справа animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow ) ) togetherWith slideOutHorizontally( targetOffsetX = { fullWidth -> -fullWidth / 4 }, // Список уходит влево на 25% animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow ) ) } // ❌ Выход из поиска - обратный slide isExitingSearch -> { slideInHorizontally( initialOffsetX = { fullWidth -> -fullWidth / 4 }, // Список возвращается слева animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium ) ) togetherWith slideOutHorizontally( targetOffsetX = { fullWidth -> fullWidth }, // Поиск уходит за экран вправо animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium ) ) } // Default - мгновенный переход else -> { EnterTransition.None togetherWith ExitTransition.None } } }, label = "screenNavigation" ) { (user, isSearchOpen, _) -> when { user != null -> { // Экран чата ChatDetailScreen( user = user, currentUserPublicKey = accountPublicKey, currentUserPrivateKey = accountPrivateKey, isDarkTheme = isDarkTheme, onBack = { selectedUser = null } ) } isSearchOpen -> { // Экран поиска SearchScreen( privateKeyHash = privateKeyHash, currentUserPublicKey = accountPublicKey, isDarkTheme = isDarkTheme, protocolState = protocolState, onBackClick = { showSearchScreen = false }, onUserSelect = { user -> showSearchScreen = false selectedUser = user } ) } else -> { // Список чатов ChatsListScreen( isDarkTheme = isDarkTheme, accountName = accountName, accountPhone = accountPhone, accountPublicKey = accountPublicKey, accountPrivateKey = accountPrivateKey, privateKeyHash = privateKeyHash, onToggleTheme = onToggleTheme, onProfileClick = { // TODO: Navigate to profile }, onNewGroupClick = { // TODO: Navigate to new group }, onContactsClick = { // TODO: Navigate to contacts }, onCallsClick = { // TODO: Navigate to calls }, onSavedMessagesClick = { // TODO: Navigate to saved messages }, onSettingsClick = { // TODO: Navigate to settings }, onInviteFriendsClick = { // TODO: Share invite link }, onSearchClick = { showSearchScreen = true }, onNewChat = { // TODO: Show new chat screen }, onUserSelect = { user -> selectedUser = user }, onLogout = onLogout ) } } } }