Files
mobile-android/app/src/main/java/com/rosetta/messenger/MainActivity.kt

849 lines
41 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.rosetta.messenger
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
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.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<String>()
val fcmLogs: List<String>
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)
}
Log.d(TAG, "FCM: $message")
}
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 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<Boolean?>(null) }
var currentAccount by remember { mutableStateOf<DecryptedAccount?>(null) }
var accountInfoList by remember { mutableStateOf<List<AccountInfo>>(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,
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()
}
},
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,
onToggleTheme: () -> 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
Log.d("MainActivity", "Loaded from DB: name=$accountName, username=$accountUsername")
}
}
// Состояние протокола для передачи в SearchScreen
val protocolState by ProtocolManager.state.collectAsState()
// Навигация между экранами
var selectedUser by remember { mutableStateOf<SearchUser?>(null) }
var showSearchScreen by remember { mutableStateOf(false) }
var showProfileScreen by remember { mutableStateOf(false) }
var showOtherProfileScreen by remember { mutableStateOf(false) }
var selectedOtherUser by remember { mutableStateOf<SearchUser?>(null) }
// Дополнительные экраны настроек
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) {
Log.e("MainActivity", "Error verifying password", e)
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
Log.d("MainActivity", "Delete account requested")
}
)
}
}
androidx.compose.animation.AnimatedVisibility(
visible = showThemeScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
) {
if (showThemeScreen) {
ThemeScreen(
isDarkTheme = isDarkTheme,
onBack = {
showThemeScreen = false
showProfileScreen = true
},
onThemeChange = { isDark ->
onToggleTheme()
}
)
}
}
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
}
)
}
}
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()
}
Log.d("MainActivity", "Profile saved: name=$name, username=$username, UI updated")
},
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
}
)
}
}
}
}