feat: implement swipe back navigation and integrate VerifiedBadge in chat dialogs
This commit is contained in:
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user