feat: implement swipe back navigation and integrate VerifiedBadge in chat dialogs

This commit is contained in:
2026-02-05 03:25:20 +05:00
parent a03e267050
commit 9010d1c975
6 changed files with 555 additions and 295 deletions

View File

@@ -41,6 +41,7 @@ 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.components.SwipeBackContainer
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
import com.rosetta.messenger.ui.settings.BackupScreen
import com.rosetta.messenger.ui.settings.BiometricEnableScreen
@@ -551,18 +552,10 @@ fun MainScreen(
// Coroutine scope for profile updates
val mainScreenScope = rememberCoroutineScope()
// 🔥 Простая навигация с fade-in анимацией
// 🔥 Простая навигация с swipe back
Box(modifier = Modifier.fillMaxSize()) {
// Base layer - chats list
androidx.compose.animation.AnimatedVisibility(
visible = !showBackupScreen && !showSafetyScreen && !showThemeScreen &&
!showUpdatesScreen && selectedUser == null && !showSearchScreen &&
!showProfileScreen && !showOtherProfileScreen && !showLogsScreen &&
!showCrashLogsScreen && !showBiometricScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
) {
ChatsListScreen(
// Base layer - chats list (всегда видимый, чтобы его было видно при свайпе)
ChatsListScreen(
isDarkTheme = isDarkTheme,
accountName = accountName,
accountUsername = accountUsername,
@@ -606,115 +599,115 @@ fun MainScreen(
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))
// Other screens with swipe back
SwipeBackContainer(
isVisible = showBackupScreen,
onBack = {
showBackupScreen = false
showSafetyScreen = true
},
isDarkTheme = isDarkTheme
) {
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,
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
)
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) {
} else {
null
}
} catch (e: Exception) {
null
}
)
}
}
)
}
androidx.compose.animation.AnimatedVisibility(
visible = showSafetyScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
SwipeBackContainer(
isVisible = showSafetyScreen,
onBack = {
showSafetyScreen = false
showProfileScreen = true
},
isDarkTheme = isDarkTheme
) {
if (showSafetyScreen) {
SafetyScreen(
isDarkTheme = isDarkTheme,
accountPublicKey = accountPublicKey,
accountPrivateKey = accountPrivateKey,
onBack = {
showSafetyScreen = false
showProfileScreen = true
},
onBackupClick = {
showSafetyScreen = false
showBackupScreen = true
},
onDeleteAccount = {
// TODO: Implement account deletion
}
)
}
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))
SwipeBackContainer(
isVisible = showThemeScreen,
onBack = {
showThemeScreen = false
showProfileScreen = true
},
isDarkTheme = isDarkTheme
) {
if (showThemeScreen) {
ThemeScreen(
isDarkTheme = isDarkTheme,
currentThemeMode = themeMode,
onBack = {
showThemeScreen = false
showProfileScreen = true
},
onThemeModeChange = onThemeModeChange
)
}
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))
SwipeBackContainer(
isVisible = showUpdatesScreen,
onBack = { showUpdatesScreen = false },
isDarkTheme = isDarkTheme
) {
if (showUpdatesScreen) {
UpdatesScreen(
isDarkTheme = isDarkTheme,
onBack = { showUpdatesScreen = false }
)
}
UpdatesScreen(
isDarkTheme = isDarkTheme,
onBack = { showUpdatesScreen = false }
)
}
androidx.compose.animation.AnimatedVisibility(
visible = selectedUser != null,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
SwipeBackContainer(
isVisible = selectedUser != null,
onBack = { selectedUser = null },
isDarkTheme = isDarkTheme
) {
if (selectedUser != null) {
// Экран чата
@@ -738,122 +731,123 @@ fun MainScreen(
}
}
androidx.compose.animation.AnimatedVisibility(
visible = showSearchScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
SwipeBackContainer(
isVisible = showSearchScreen,
onBack = { showSearchScreen = false },
isDarkTheme = isDarkTheme
) {
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
},
onNavigateToBiometric = {
showProfileScreen = false
showBiometricScreen = 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(
// Экран поиска
SearchScreen(
privateKeyHash = privateKeyHash,
currentUserPublicKey = accountPublicKey,
isDarkTheme = isDarkTheme,
logs = profileState.logs,
onBack = {
showLogsScreen = false
showProfileScreen = true
protocolState = protocolState,
onBackClick = { showSearchScreen = false },
onUserSelect = { selectedSearchUser ->
showSearchScreen = false
selectedUser = selectedSearchUser
}
)
}
SwipeBackContainer(
isVisible = showProfileScreen,
onBack = { showProfileScreen = false },
isDarkTheme = isDarkTheme
) {
// Экран профиля
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()
}
},
onClearLogs = {
profileViewModel.clearLogs()
}
)
}
onLogout = onLogout,
onNavigateToTheme = {
showProfileScreen = false
showThemeScreen = true
},
onNavigateToSafety = {
showProfileScreen = false
showSafetyScreen = true
},
onNavigateToLogs = {
showProfileScreen = false
showLogsScreen = true
},
onNavigateToCrashLogs = {
showProfileScreen = false
showCrashLogsScreen = true
},
onNavigateToBiometric = {
showProfileScreen = false
showBiometricScreen = true
},
viewModel = profileViewModel,
avatarRepository = avatarRepository,
dialogDao = RosettaDatabase.getDatabase(context).dialogDao()
)
}
androidx.compose.animation.AnimatedVisibility(
visible = showCrashLogsScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
SwipeBackContainer(
isVisible = showLogsScreen,
onBack = {
showLogsScreen = false
showProfileScreen = true
},
isDarkTheme = isDarkTheme
) {
if (showCrashLogsScreen) {
CrashLogsScreen(
onBackClick = {
showCrashLogsScreen = false
showProfileScreen = true
}
)
}
com.rosetta.messenger.ui.settings.ProfileLogsScreen(
isDarkTheme = isDarkTheme,
logs = profileState.logs,
onBack = {
showLogsScreen = false
showProfileScreen = true
},
onClearLogs = {
profileViewModel.clearLogs()
}
)
}
androidx.compose.animation.AnimatedVisibility(
visible = showOtherProfileScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
SwipeBackContainer(
isVisible = showCrashLogsScreen,
onBack = {
showCrashLogsScreen = false
showProfileScreen = true
},
isDarkTheme = isDarkTheme
) {
if (showOtherProfileScreen && selectedOtherUser != null) {
CrashLogsScreen(
onBackClick = {
showCrashLogsScreen = false
showProfileScreen = true
}
)
}
SwipeBackContainer(
isVisible = showOtherProfileScreen,
onBack = {
showOtherProfileScreen = false
selectedOtherUser = null
},
isDarkTheme = isDarkTheme
) {
if (selectedOtherUser != null) {
OtherProfileScreen(
user = selectedOtherUser!!,
isDarkTheme = isDarkTheme,
@@ -867,47 +861,48 @@ fun MainScreen(
}
// Biometric Enable Screen
androidx.compose.animation.AnimatedVisibility(
visible = showBiometricScreen,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200))
SwipeBackContainer(
isVisible = showBiometricScreen,
onBack = {
showBiometricScreen = false
showProfileScreen = true
},
isDarkTheme = isDarkTheme
) {
if (showBiometricScreen) {
val biometricManager = remember { com.rosetta.messenger.biometric.BiometricAuthManager(context) }
val biometricPrefs = remember { com.rosetta.messenger.biometric.BiometricPreferences(context) }
val activity = context as? FragmentActivity
val biometricManager = remember { com.rosetta.messenger.biometric.BiometricAuthManager(context) }
val biometricPrefs = remember { com.rosetta.messenger.biometric.BiometricPreferences(context) }
val activity = context as? FragmentActivity
BiometricEnableScreen(
isDarkTheme = isDarkTheme,
onBack = {
showBiometricScreen = false
showProfileScreen = true
},
onEnable = { password, onSuccess, onError ->
if (activity == null) {
onError("Activity not available")
return@BiometricEnableScreen
}
biometricManager.encryptPassword(
activity = activity,
password = password,
onSuccess = { encryptedPassword ->
mainScreenScope.launch {
biometricPrefs.saveEncryptedPassword(accountPublicKey, encryptedPassword)
biometricPrefs.enableBiometric()
onSuccess()
}
},
onError = { error -> onError(error) },
onCancel = {
showBiometricScreen = false
showProfileScreen = true
}
)
BiometricEnableScreen(
isDarkTheme = isDarkTheme,
onBack = {
showBiometricScreen = false
showProfileScreen = true
},
onEnable = { password, onSuccess, onError ->
if (activity == null) {
onError("Activity not available")
return@BiometricEnableScreen
}
)
}
biometricManager.encryptPassword(
activity = activity,
password = password,
onSuccess = { encryptedPassword ->
mainScreenScope.launch {
biometricPrefs.saveEncryptedPassword(accountPublicKey, encryptedPassword)
biometricPrefs.enableBiometric()
onSuccess()
}
},
onError = { error -> onError(error) },
onCancel = {
showBiometricScreen = false
showProfileScreen = true
}
)
}
)
}
}
}