Доработки интерфейса и поведения настроек, профиля и групп
This commit is contained in:
@@ -16,10 +16,15 @@ import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -67,6 +72,7 @@ import com.rosetta.messenger.ui.crashlogs.CrashLogsScreen
|
||||
import com.rosetta.messenger.ui.onboarding.OnboardingScreen
|
||||
import com.rosetta.messenger.ui.settings.BackupScreen
|
||||
import com.rosetta.messenger.ui.settings.BiometricEnableScreen
|
||||
import com.rosetta.messenger.ui.settings.NotificationsScreen
|
||||
import com.rosetta.messenger.ui.settings.OtherProfileScreen
|
||||
import com.rosetta.messenger.ui.settings.ProfileScreen
|
||||
import com.rosetta.messenger.ui.settings.SafetyScreen
|
||||
@@ -203,6 +209,8 @@ class MainActivity : FragmentActivity() {
|
||||
var currentAccount by remember { mutableStateOf(getCachedSessionAccount()) }
|
||||
var accountInfoList by remember { mutableStateOf<List<AccountInfo>>(emptyList()) }
|
||||
var startCreateAccountFlow by remember { mutableStateOf(false) }
|
||||
var preservedMainNavStack by remember { mutableStateOf<List<Screen>>(emptyList()) }
|
||||
var preservedMainNavAccountKey by remember { mutableStateOf("") }
|
||||
|
||||
// Check for existing accounts and build AccountInfo list
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -303,6 +311,8 @@ class MainActivity : FragmentActivity() {
|
||||
},
|
||||
onLogout = {
|
||||
startCreateAccountFlow = false
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
// Set currentAccount to null immediately to prevent UI
|
||||
// lag
|
||||
currentAccount = null
|
||||
@@ -316,8 +326,27 @@ class MainActivity : FragmentActivity() {
|
||||
)
|
||||
}
|
||||
"main" -> {
|
||||
val activeAccountKey = currentAccount?.publicKey.orEmpty()
|
||||
MainScreen(
|
||||
account = currentAccount,
|
||||
initialNavStack =
|
||||
if (
|
||||
activeAccountKey.isNotBlank() &&
|
||||
preservedMainNavAccountKey.equals(
|
||||
activeAccountKey,
|
||||
ignoreCase = true
|
||||
)
|
||||
) {
|
||||
preservedMainNavStack
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
onNavStackChanged = { stack ->
|
||||
if (activeAccountKey.isNotBlank()) {
|
||||
preservedMainNavAccountKey = activeAccountKey
|
||||
preservedMainNavStack = stack
|
||||
}
|
||||
},
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeMode = themeMode,
|
||||
onToggleTheme = {
|
||||
@@ -331,6 +360,8 @@ class MainActivity : FragmentActivity() {
|
||||
},
|
||||
onLogout = {
|
||||
startCreateAccountFlow = false
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
// Set currentAccount to null immediately to prevent UI
|
||||
// lag
|
||||
currentAccount = null
|
||||
@@ -343,6 +374,8 @@ class MainActivity : FragmentActivity() {
|
||||
},
|
||||
onDeleteAccount = {
|
||||
startCreateAccountFlow = false
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
val publicKey = currentAccount?.publicKey ?: return@MainScreen
|
||||
scope.launch {
|
||||
try {
|
||||
@@ -405,6 +438,8 @@ class MainActivity : FragmentActivity() {
|
||||
hasExistingAccount = accounts.isNotEmpty()
|
||||
// 9. If current account is deleted, return to main login screen
|
||||
if (currentAccount?.publicKey == targetPublicKey) {
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
}
|
||||
@@ -415,6 +450,8 @@ class MainActivity : FragmentActivity() {
|
||||
},
|
||||
onSwitchAccount = { targetPublicKey ->
|
||||
startCreateAccountFlow = false
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
// Save target account before leaving main screen so Unlock
|
||||
// screen preselects the account the user tapped.
|
||||
accountManager.setLastLoggedPublicKey(targetPublicKey)
|
||||
@@ -429,6 +466,8 @@ class MainActivity : FragmentActivity() {
|
||||
},
|
||||
onAddAccount = {
|
||||
startCreateAccountFlow = true
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
@@ -442,6 +481,8 @@ class MainActivity : FragmentActivity() {
|
||||
DeviceConfirmScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onExit = {
|
||||
preservedMainNavStack = emptyList()
|
||||
preservedMainNavAccountKey = ""
|
||||
currentAccount = null
|
||||
clearCachedSessionAccount()
|
||||
scope.launch {
|
||||
@@ -614,6 +655,7 @@ private fun EncryptedAccount.toAccountInfo(): AccountInfo {
|
||||
sealed class Screen {
|
||||
data object Profile : Screen()
|
||||
data object ProfileFromChat : Screen()
|
||||
data object Notifications : Screen()
|
||||
data object Requests : Screen()
|
||||
data object Search : Screen()
|
||||
data object GroupSetup : Screen()
|
||||
@@ -634,6 +676,8 @@ sealed class Screen {
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
account: DecryptedAccount? = null,
|
||||
initialNavStack: List<Screen> = emptyList(),
|
||||
onNavStackChanged: (List<Screen>) -> Unit = {},
|
||||
isDarkTheme: Boolean = true,
|
||||
themeMode: String = "dark",
|
||||
onToggleTheme: () -> Unit = {},
|
||||
@@ -941,7 +985,8 @@ fun MainScreen(
|
||||
// navigation change. This eliminates the massive recomposition
|
||||
// that happened when ANY boolean flag changed.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
var navStack by remember { mutableStateOf<List<Screen>>(emptyList()) }
|
||||
var navStack by remember(accountPublicKey) { mutableStateOf(initialNavStack) }
|
||||
LaunchedEffect(navStack) { onNavStackChanged(navStack) }
|
||||
|
||||
// Derived visibility — only triggers recomposition when THIS screen changes
|
||||
val isProfileVisible by remember { derivedStateOf { navStack.any { it is Screen.Profile } } }
|
||||
@@ -949,6 +994,9 @@ fun MainScreen(
|
||||
derivedStateOf { navStack.any { it is Screen.ProfileFromChat } }
|
||||
}
|
||||
val isRequestsVisible by remember { derivedStateOf { navStack.any { it is Screen.Requests } } }
|
||||
val isNotificationsVisible by remember {
|
||||
derivedStateOf { navStack.any { it is Screen.Notifications } }
|
||||
}
|
||||
val isSearchVisible by remember { derivedStateOf { navStack.any { it is Screen.Search } } }
|
||||
val isGroupSetupVisible by remember { derivedStateOf { navStack.any { it is Screen.GroupSetup } } }
|
||||
val chatDetailScreen by remember {
|
||||
@@ -980,6 +1028,9 @@ fun MainScreen(
|
||||
val isAppearanceVisible by remember {
|
||||
derivedStateOf { navStack.any { it is Screen.Appearance } }
|
||||
}
|
||||
var profileHasUnsavedChanges by remember(accountPublicKey) { mutableStateOf(false) }
|
||||
var showDiscardProfileChangesDialog by remember { mutableStateOf(false) }
|
||||
var discardProfileChangesFromChat by remember { mutableStateOf(false) }
|
||||
|
||||
// Navigation helpers
|
||||
fun pushScreen(screen: Screen) {
|
||||
@@ -1027,7 +1078,8 @@ fun MainScreen(
|
||||
navStack =
|
||||
navStack.filterNot {
|
||||
it is Screen.Profile ||
|
||||
it is Screen.Theme ||
|
||||
it is Screen.Theme ||
|
||||
it is Screen.Notifications ||
|
||||
it is Screen.Safety ||
|
||||
it is Screen.Backup ||
|
||||
it is Screen.Logs ||
|
||||
@@ -1036,6 +1088,21 @@ fun MainScreen(
|
||||
it is Screen.Appearance
|
||||
}
|
||||
}
|
||||
fun performProfileBack(fromChat: Boolean) {
|
||||
if (fromChat) {
|
||||
navStack = navStack.filterNot { it is Screen.ProfileFromChat }
|
||||
} else {
|
||||
popProfileAndChildren()
|
||||
}
|
||||
}
|
||||
fun requestProfileBack(fromChat: Boolean) {
|
||||
if (profileHasUnsavedChanges) {
|
||||
discardProfileChangesFromChat = fromChat
|
||||
showDiscardProfileChangesDialog = true
|
||||
} else {
|
||||
performProfileBack(fromChat)
|
||||
}
|
||||
}
|
||||
fun popChatAndChildren() {
|
||||
navStack =
|
||||
navStack.filterNot {
|
||||
@@ -1043,6 +1110,13 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(isProfileVisible, isProfileFromChatVisible) {
|
||||
if (!isProfileVisible && !isProfileFromChatVisible) {
|
||||
profileHasUnsavedChanges = false
|
||||
showDiscardProfileChangesDialog = false
|
||||
}
|
||||
}
|
||||
|
||||
// ProfileViewModel для логов
|
||||
val profileViewModel: com.rosetta.messenger.ui.settings.ProfileViewModel =
|
||||
androidx.lifecycle.viewmodel.compose.viewModel()
|
||||
@@ -1196,9 +1270,10 @@ fun MainScreen(
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
SwipeBackContainer(
|
||||
isVisible = isProfileVisible,
|
||||
onBack = { popProfileAndChildren() },
|
||||
onBack = { requestProfileBack(fromChat = false) },
|
||||
isDarkTheme = isDarkTheme,
|
||||
layer = 1,
|
||||
swipeEnabled = !profileHasUnsavedChanges,
|
||||
propagateBackgroundProgress = false
|
||||
) {
|
||||
// Экран профиля
|
||||
@@ -1209,13 +1284,15 @@ fun MainScreen(
|
||||
accountVerified = accountVerified,
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKeyHash = privateKeyHash,
|
||||
onBack = { popProfileAndChildren() },
|
||||
onBack = { requestProfileBack(fromChat = false) },
|
||||
onHasChangesChanged = { profileHasUnsavedChanges = it },
|
||||
onSaveProfile = { name, username ->
|
||||
accountName = name
|
||||
accountUsername = username
|
||||
mainScreenScope.launch { onAccountInfoUpdated() }
|
||||
},
|
||||
onLogout = onLogout,
|
||||
onNavigateToNotifications = { pushScreen(Screen.Notifications) },
|
||||
onNavigateToTheme = { pushScreen(Screen.Theme) },
|
||||
onNavigateToAppearance = { pushScreen(Screen.Appearance) },
|
||||
onNavigateToSafety = { pushScreen(Screen.Safety) },
|
||||
@@ -1229,6 +1306,18 @@ fun MainScreen(
|
||||
}
|
||||
|
||||
// Other screens with swipe back
|
||||
SwipeBackContainer(
|
||||
isVisible = isNotificationsVisible,
|
||||
onBack = { navStack = navStack.filterNot { it is Screen.Notifications } },
|
||||
isDarkTheme = isDarkTheme,
|
||||
layer = 2
|
||||
) {
|
||||
NotificationsScreen(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onBack = { navStack = navStack.filterNot { it is Screen.Notifications } }
|
||||
)
|
||||
}
|
||||
|
||||
SwipeBackContainer(
|
||||
isVisible = isSafetyVisible,
|
||||
onBack = { navStack = navStack.filterNot { it is Screen.Safety } },
|
||||
@@ -1420,9 +1509,10 @@ fun MainScreen(
|
||||
|
||||
SwipeBackContainer(
|
||||
isVisible = isProfileFromChatVisible,
|
||||
onBack = { navStack = navStack.filterNot { it is Screen.ProfileFromChat } },
|
||||
onBack = { requestProfileBack(fromChat = true) },
|
||||
isDarkTheme = isDarkTheme,
|
||||
layer = 1,
|
||||
swipeEnabled = !profileHasUnsavedChanges,
|
||||
propagateBackgroundProgress = false
|
||||
) {
|
||||
ProfileScreen(
|
||||
@@ -1432,13 +1522,15 @@ fun MainScreen(
|
||||
accountVerified = accountVerified,
|
||||
accountPublicKey = accountPublicKey,
|
||||
accountPrivateKeyHash = privateKeyHash,
|
||||
onBack = { navStack = navStack.filterNot { it is Screen.ProfileFromChat } },
|
||||
onBack = { requestProfileBack(fromChat = true) },
|
||||
onHasChangesChanged = { profileHasUnsavedChanges = it },
|
||||
onSaveProfile = { name, username ->
|
||||
accountName = name
|
||||
accountUsername = username
|
||||
mainScreenScope.launch { onAccountInfoUpdated() }
|
||||
},
|
||||
onLogout = onLogout,
|
||||
onNavigateToNotifications = { pushScreen(Screen.Notifications) },
|
||||
onNavigateToTheme = { pushScreen(Screen.Theme) },
|
||||
onNavigateToAppearance = { pushScreen(Screen.Appearance) },
|
||||
onNavigateToSafety = { pushScreen(Screen.Safety) },
|
||||
@@ -1690,6 +1782,21 @@ fun MainScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (isCallScreenVisible) {
|
||||
// Блокируем любой ввод по нижележащим экранам, пока открыт полноэкранный CallOverlay.
|
||||
// Иначе тапы могут "пробивать" в чат (иконка звонка, kebab, input и т.д.).
|
||||
val blockerInteraction = remember { MutableInteractionSource() }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(
|
||||
interactionSource = blockerInteraction,
|
||||
indication = null,
|
||||
onClick = {}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
CallOverlay(
|
||||
state = callUiState,
|
||||
isDarkTheme = isDarkTheme,
|
||||
@@ -1706,5 +1813,43 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (showDiscardProfileChangesDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDiscardProfileChangesDialog = false },
|
||||
containerColor = if (isDarkTheme) Color(0xFF1E1E20) else Color.White,
|
||||
title = {
|
||||
Text(
|
||||
text = "Discard changes?",
|
||||
color = if (isDarkTheme) Color.White else Color.Black
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = "You have unsaved profile changes. If you leave now, they will be lost.",
|
||||
color = if (isDarkTheme) Color(0xFFB5B5BC) else Color(0xFF5A5A66)
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showDiscardProfileChangesDialog = false
|
||||
profileHasUnsavedChanges = false
|
||||
performProfileBack(discardProfileChangesFromChat)
|
||||
}
|
||||
) {
|
||||
Text("Discard", color = Color(0xFFFF3B30))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDiscardProfileChangesDialog = false }) {
|
||||
Text(
|
||||
"Stay",
|
||||
color = if (isDarkTheme) Color(0xFF5FA8FF) else Color(0xFF0D8CF4)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user