diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index e07873e8..2323c7fd 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -18,6 +18,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.lifecycleScope import androidx.lifecycle.ViewModelProvider +import androidx.activity.OnBackPressedCallback +import androidx.activity.addCallback import com.bitchat.android.mesh.BluetoothMeshService import com.bitchat.android.onboarding.* import com.bitchat.android.ui.ChatScreen @@ -138,6 +140,24 @@ class MainActivity : ComponentActivity() { } OnboardingState.COMPLETE -> { + // Set up back navigation handling for the chat screen + val backCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + // Let ChatViewModel handle navigation state + val handled = chatViewModel.handleBackPressed() + if (!handled) { + // If ChatViewModel doesn't handle it, disable this callback + // and let the system handle it (which will exit the app) + this.isEnabled = false + onBackPressedDispatcher.onBackPressed() + this.isEnabled = true + } + } + } + + // Add the callback - this will be automatically removed when the activity is destroyed + onBackPressedDispatcher.addCallback(this, backCallback) + ChatScreen(viewModel = chatViewModel) } diff --git a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt index 4fe40704..4c9e30dc 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatScreen.kt @@ -60,15 +60,15 @@ fun ChatScreen(viewModel: ChatViewModel) { val hasUnreadPrivateMessages by viewModel.unreadPrivateMessages.observeAsState(emptySet()) val privateChats by viewModel.privateChats.observeAsState(emptyMap()) val channelMessages by viewModel.channelMessages.observeAsState(emptyMap()) - var showSidebar by remember { mutableStateOf(false) } + val showSidebar by viewModel.showSidebar.observeAsState(false) val showCommandSuggestions by viewModel.showCommandSuggestions.observeAsState(false) val commandSuggestions by viewModel.commandSuggestions.observeAsState(emptyList()) + val showAppInfo by viewModel.showAppInfo.observeAsState(false) var messageText by remember { mutableStateOf("") } var showPasswordPrompt by remember { mutableStateOf(false) } var showPasswordDialog by remember { mutableStateOf(false) } var passwordInput by remember { mutableStateOf("") } - var showAppInfo by remember { mutableStateOf(false) } // Show password dialog when needed LaunchedEffect(showPasswordPrompt) { @@ -142,8 +142,8 @@ fun ChatScreen(viewModel: ChatViewModel) { nickname = nickname, viewModel = viewModel, colorScheme = colorScheme, - onSidebarToggle = { showSidebar = true }, - onShowAppInfo = { showAppInfo = true }, + onSidebarToggle = { viewModel.showSidebar() }, + onShowAppInfo = { viewModel.showAppInfo() }, onPanicClear = { viewModel.panicClearAllData() } ) @@ -162,7 +162,7 @@ fun ChatScreen(viewModel: ChatViewModel) { ) { SidebarOverlay( viewModel = viewModel, - onDismiss = { showSidebar = false }, + onDismiss = { viewModel.hideSidebar() }, modifier = Modifier.fillMaxSize() ) } @@ -188,7 +188,7 @@ fun ChatScreen(viewModel: ChatViewModel) { passwordInput = "" }, showAppInfo = showAppInfo, - onAppInfoDismiss = { showAppInfo = false } + onAppInfoDismiss = { viewModel.hideAppInfo() } ) } diff --git a/app/src/main/java/com/bitchat/android/ui/ChatState.kt b/app/src/main/java/com/bitchat/android/ui/ChatState.kt index 748062b1..4a2e9bda 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatState.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatState.kt @@ -85,6 +85,10 @@ class ChatState { val peerIDToPublicKeyFingerprint = mutableMapOf() + // Navigation state + private val _showAppInfo = MutableLiveData(false) + val showAppInfo: LiveData = _showAppInfo + // Unread state computed properties val hasUnreadChannels: MediatorLiveData = MediatorLiveData() val hasUnreadPrivateMessages: MediatorLiveData = MediatorLiveData() @@ -118,6 +122,7 @@ class ChatState { fun getShowCommandSuggestionsValue() = _showCommandSuggestions.value ?: false fun getCommandSuggestionsValue() = _commandSuggestions.value ?: emptyList() fun getFavoritePeersValue() = _favoritePeers.value ?: emptySet() + fun getShowAppInfoValue() = _showAppInfo.value ?: false // Setters for state updates fun setMessages(messages: List) { @@ -201,5 +206,9 @@ class ChatState { Log.d("ChatState", "LiveData value after set: ${_favoritePeers.value}") Log.d("ChatState", "LiveData has active observers: ${_favoritePeers.hasActiveObservers()}") } + + fun setShowAppInfo(show: Boolean) { + _showAppInfo.value = show + } } diff --git a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt index e8ad9c8d..190f696d 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt @@ -71,6 +71,7 @@ class ChatViewModel( val showCommandSuggestions: LiveData = state.showCommandSuggestions val commandSuggestions: LiveData> = state.commandSuggestions val favoritePeers: LiveData> = state.favoritePeers + val showAppInfo: LiveData = state.showAppInfo init { // Note: Mesh service delegate is now set by MainActivity @@ -366,4 +367,59 @@ class ChatViewModel( // Note: Mesh service restart is now handled by MainActivity // This method now only clears data, not mesh service lifecycle } + + // MARK: - Navigation Management + + fun showAppInfo() { + state.setShowAppInfo(true) + } + + fun hideAppInfo() { + state.setShowAppInfo(false) + } + + fun showSidebar() { + state.setShowSidebar(true) + } + + fun hideSidebar() { + state.setShowSidebar(false) + } + + /** + * Handle Android back navigation + * Returns true if the back press was handled, false if it should be passed to the system + */ + fun handleBackPressed(): Boolean { + return when { + // Close app info dialog + state.getShowAppInfoValue() -> { + hideAppInfo() + true + } + // Close sidebar + state.getShowSidebarValue() -> { + hideSidebar() + true + } + // Close password dialog + state.getShowPasswordPromptValue() -> { + state.setShowPasswordPrompt(false) + state.setPasswordPromptChannel(null) + true + } + // Exit private chat + state.getSelectedPrivateChatPeerValue() != null -> { + endPrivateChat() + true + } + // Exit channel view + state.getCurrentChannelValue() != null -> { + switchToChannel(null) + true + } + // No special navigation state - let system handle (usually exits app) + else -> false + } + } }