mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Merge 11097a629a19637e8df3aa5aec40e7e1ad6777f4 into c127eb83ab94c069c32d37530d2faecd381cd2a8
This commit is contained in:
commit
fcbc8734ae
@ -4,7 +4,7 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
@ -313,23 +313,13 @@ class MainActivity : OrientationAwareActivity() {
|
||||
}
|
||||
|
||||
OnboardingState.CHECKING, OnboardingState.INITIALIZING, 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
|
||||
}
|
||||
}
|
||||
val canHandleBack by chatViewModel.canHandleBack.collectAsState()
|
||||
// Disabled rather than always-on so predictive back can preview
|
||||
// the exit instead of the app claiming every gesture.
|
||||
BackHandler(enabled = canHandleBack) {
|
||||
// enabled trails the state by a dispatch and a recomposition.
|
||||
if (!chatViewModel.handleBackPressed()) finish()
|
||||
}
|
||||
|
||||
// Add the callback - this will be automatically removed when the activity is destroyed
|
||||
onBackPressedDispatcher.addCallback(this, backCallback)
|
||||
ChatScreen(viewModel = chatViewModel)
|
||||
}
|
||||
|
||||
|
||||
@ -170,6 +170,26 @@ class ChatState(
|
||||
initialValue = false
|
||||
)
|
||||
|
||||
// Mirrors the branches of ChatViewModel.handleBackPressed. The back handler
|
||||
// is enabled from this and the press consumed by that, so they must agree.
|
||||
val canHandleBack: StateFlow<Boolean> = combine(
|
||||
_showAppInfo,
|
||||
_showPasswordPrompt,
|
||||
_selectedPrivateChatPeer,
|
||||
_privateChatSheetPeer,
|
||||
_currentChannel
|
||||
) { showAppInfo, showPasswordPrompt, privateChatPeer, privateChatSheetPeer, channel ->
|
||||
showAppInfo ||
|
||||
showPasswordPrompt ||
|
||||
privateChatPeer != null ||
|
||||
privateChatSheetPeer != null ||
|
||||
channel != null
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = WhileSubscribed(5_000),
|
||||
initialValue = false
|
||||
)
|
||||
|
||||
// Getters for internal state access
|
||||
fun getMessagesValue() = _messages.value
|
||||
fun getConnectedPeersValue() = _connectedPeers.value
|
||||
|
||||
@ -387,6 +387,7 @@ class ChatViewModel(
|
||||
val passwordPromptChannel: StateFlow<String?> = state.passwordPromptChannel
|
||||
val hasUnreadChannels = state.hasUnreadChannels
|
||||
val hasUnreadPrivateMessages = state.hasUnreadPrivateMessages
|
||||
val canHandleBack = state.canHandleBack
|
||||
val showCommandSuggestions: StateFlow<Boolean> = state.showCommandSuggestions
|
||||
val commandSuggestions: StateFlow<List<CommandSuggestion>> = state.commandSuggestions
|
||||
val showMentionSuggestions: StateFlow<Boolean> = state.showMentionSuggestions
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* `canHandleBack` must agree with the branches of
|
||||
* [ChatViewModel.handleBackPressed]: the back handler is enabled from one and
|
||||
* the press consumed by the other.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ChatStateBackNavigationTest {
|
||||
|
||||
private lateinit var scope: TestScope
|
||||
private lateinit var state: ChatState
|
||||
private lateinit var subscription: Job
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
scope = TestScope(UnconfinedTestDispatcher())
|
||||
state = ChatState(scope)
|
||||
// Shared WhileSubscribed, so it only tracks its sources while collected.
|
||||
subscription = scope.launch { state.canHandleBack.collect { } }
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is false on the bare chat screen`() {
|
||||
assertFalse(state.canHandleBack.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is true while the app info dialog is open`() {
|
||||
state.setShowAppInfo(true)
|
||||
|
||||
assertTrue(state.canHandleBack.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is true while the password prompt is open`() {
|
||||
state.setShowPasswordPrompt(true)
|
||||
|
||||
assertTrue(state.canHandleBack.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is true while a private chat is selected`() {
|
||||
state.setSelectedPrivateChatPeer("peer-a")
|
||||
|
||||
assertTrue(state.canHandleBack.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is true while the private chat sheet is open`() {
|
||||
state.setPrivateChatSheetPeer("peer-a")
|
||||
|
||||
assertTrue(state.canHandleBack.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `is true while a channel is open`() {
|
||||
state.setCurrentChannel("#bitchat")
|
||||
|
||||
assertTrue(state.canHandleBack.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns to false once the last overlay closes`() {
|
||||
state.setCurrentChannel("#bitchat")
|
||||
state.setShowAppInfo(true)
|
||||
|
||||
state.setShowAppInfo(false)
|
||||
assertTrue("the channel is still open", state.canHandleBack.value)
|
||||
|
||||
state.setCurrentChannel(null)
|
||||
assertFalse("nothing is left to unwind", state.canHandleBack.value)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user