mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-08 06:46:11 +00:00
wear: fix private message navigation
This commit is contained in:
parent
5b79da2db6
commit
d465f427a4
@ -66,13 +66,17 @@ class MainActivity : ComponentActivity() {
|
||||
private var nicknameChosen by mutableStateOf(false)
|
||||
private var notificationsGranted by mutableStateOf(false)
|
||||
private var notificationPromptDismissed by mutableStateOf(false)
|
||||
private var pendingDmPeer by mutableStateOf<String?>(null)
|
||||
private var pendingLaunchRequest by mutableStateOf<WearLaunchRequest?>(null)
|
||||
private var nextLaunchRequestID = 0L
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
nicknameChosen = getSharedPreferences("bitchat_watch_prefs", Context.MODE_PRIVATE)
|
||||
.getBoolean("nickname_chosen", false)
|
||||
pendingDmPeer = privateMessagePeerFromIntent(intent)
|
||||
val notificationPeer = consumePrivateMessagePeer(intent)
|
||||
if (savedInstanceState == null && notificationPeer != null) {
|
||||
requestLaunch(WearLaunchTarget.Dm(notificationPeer))
|
||||
}
|
||||
refreshState()
|
||||
setContent {
|
||||
BitchatWearTheme {
|
||||
@ -96,8 +100,12 @@ class MainActivity : ComponentActivity() {
|
||||
onSkip = { notificationPromptDismissed = true }
|
||||
)
|
||||
else -> WearNavHost(
|
||||
openDmPeer = pendingDmPeer,
|
||||
onOpenDmHandled = { pendingDmPeer = null }
|
||||
launchRequest = pendingLaunchRequest,
|
||||
onLaunchRequestHandled = { requestID ->
|
||||
if (pendingLaunchRequest?.id == requestID) {
|
||||
pendingLaunchRequest = null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -122,7 +130,10 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
privateMessagePeerFromIntent(intent)?.let { pendingDmPeer = it }
|
||||
val notificationPeer = consumePrivateMessagePeer(intent)
|
||||
requestLaunch(
|
||||
notificationPeer?.let(WearLaunchTarget::Dm) ?: WearLaunchTarget.Chat
|
||||
)
|
||||
}
|
||||
|
||||
private fun refreshState() {
|
||||
@ -142,6 +153,13 @@ class MainActivity : ComponentActivity() {
|
||||
startForegroundService(Intent(this, WearMeshForegroundService::class.java))
|
||||
}
|
||||
|
||||
private fun consumePrivateMessagePeer(intent: Intent?): String? {
|
||||
val peerID = privateMessagePeerFromIntent(intent)
|
||||
intent?.removeExtra(WearNotificationCoordinator.EXTRA_OPEN_DM)
|
||||
intent?.removeExtra(WearNotificationCoordinator.EXTRA_PEER_ID)
|
||||
return peerID
|
||||
}
|
||||
|
||||
private fun privateMessagePeerFromIntent(intent: Intent?): String? {
|
||||
if (intent?.getBooleanExtra(WearNotificationCoordinator.EXTRA_OPEN_DM, false) != true) {
|
||||
return null
|
||||
@ -150,6 +168,13 @@ class MainActivity : ComponentActivity() {
|
||||
?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
private fun requestLaunch(target: WearLaunchTarget) {
|
||||
pendingLaunchRequest = WearLaunchRequest(
|
||||
id = ++nextLaunchRequestID,
|
||||
target = target
|
||||
)
|
||||
}
|
||||
|
||||
private fun notificationPermissionGranted(): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||
ContextCompat.checkSelfPermission(
|
||||
@ -169,36 +194,74 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
||||
var screen by remember { mutableStateOf<WearScreen>(WearScreen.Chat) }
|
||||
val backStack = remember { mutableStateListOf<WearScreen>() }
|
||||
internal sealed interface WearLaunchTarget {
|
||||
data object Chat : WearLaunchTarget
|
||||
data class Dm(val peerID: String) : WearLaunchTarget
|
||||
}
|
||||
|
||||
internal data class WearLaunchRequest(
|
||||
val id: Long,
|
||||
val target: WearLaunchTarget
|
||||
)
|
||||
|
||||
internal class WearNavigationState {
|
||||
private val backStack = mutableStateListOf<WearScreen>()
|
||||
|
||||
var screen by mutableStateOf<WearScreen>(WearScreen.Chat)
|
||||
private set
|
||||
|
||||
val canGoBack: Boolean
|
||||
get() = screen != WearScreen.Chat || backStack.isNotEmpty()
|
||||
|
||||
fun navigate(to: WearScreen) {
|
||||
backStack.add(screen)
|
||||
screen = to
|
||||
}
|
||||
|
||||
fun goBack(): Boolean {
|
||||
val previous = backStack.removeLastOrNull()
|
||||
return if (previous != null) {
|
||||
screen = previous
|
||||
true
|
||||
} else false
|
||||
fun openChat() {
|
||||
backStack.clear()
|
||||
screen = WearScreen.Chat
|
||||
}
|
||||
|
||||
BackHandler(enabled = backStack.isNotEmpty()) { goBack() }
|
||||
fun openDmFromNotification(peerID: String) {
|
||||
backStack.clear()
|
||||
backStack.add(WearScreen.Chat)
|
||||
screen = WearScreen.Dm(peerID)
|
||||
}
|
||||
|
||||
LaunchedEffect(openDmPeer) {
|
||||
openDmPeer?.let { peerID ->
|
||||
backStack.clear()
|
||||
screen = WearScreen.Dm(peerID)
|
||||
onOpenDmHandled()
|
||||
fun goBack(): Boolean {
|
||||
if (screen is WearScreen.Dm) {
|
||||
openChat()
|
||||
return true
|
||||
}
|
||||
|
||||
val previous = backStack.removeLastOrNull() ?: return false
|
||||
screen = previous
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WearNavHost(
|
||||
launchRequest: WearLaunchRequest?,
|
||||
onLaunchRequestHandled: (Long) -> Unit
|
||||
) {
|
||||
val navigation = remember { WearNavigationState() }
|
||||
|
||||
BackHandler(enabled = navigation.canGoBack) { navigation.goBack() }
|
||||
|
||||
LaunchedEffect(launchRequest) {
|
||||
launchRequest?.let { request ->
|
||||
when (val target = request.target) {
|
||||
WearLaunchTarget.Chat -> navigation.openChat()
|
||||
is WearLaunchTarget.Dm -> navigation.openDmFromNotification(target.peerID)
|
||||
}
|
||||
onLaunchRequestHandled(request.id)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
targetState = screen,
|
||||
targetState = navigation.screen,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(com.bitchat.watch.ui.theme.BitchatMotion.EMPHASIZED_MS)) togetherWith
|
||||
fadeOut(tween(com.bitchat.watch.ui.theme.BitchatMotion.QUICK_MS))
|
||||
@ -207,12 +270,12 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
||||
) { current ->
|
||||
when (current) {
|
||||
is WearScreen.Chat -> ChatScreen(
|
||||
onOpenPeople = { navigate(WearScreen.People) },
|
||||
onOpenTextInput = { navigate(WearScreen.TextInput(null)) }
|
||||
onOpenPeople = { navigation.navigate(WearScreen.People) },
|
||||
onOpenTextInput = { navigation.navigate(WearScreen.TextInput(null)) }
|
||||
)
|
||||
is WearScreen.People -> PeopleScreen(
|
||||
onOpenDm = { navigate(WearScreen.Dm(it)) },
|
||||
onEditNickname = { navigate(WearScreen.Nickname) }
|
||||
onOpenDm = { navigation.navigate(WearScreen.Dm(it)) },
|
||||
onEditNickname = { navigation.navigate(WearScreen.Nickname) }
|
||||
)
|
||||
is WearScreen.Nickname -> {
|
||||
val mesh = WearMeshService.peek()
|
||||
@ -223,13 +286,15 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
||||
confirmLabel = "Save",
|
||||
onConfirm = { name ->
|
||||
mesh?.setNickname(name)
|
||||
goBack()
|
||||
navigation.goBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
is WearScreen.Dm -> DmScreen(
|
||||
peerID = current.peerID,
|
||||
onOpenTextInput = { navigate(WearScreen.TextInput(current.peerID)) }
|
||||
onOpenTextInput = {
|
||||
navigation.navigate(WearScreen.TextInput(current.peerID))
|
||||
}
|
||||
)
|
||||
is WearScreen.TextInput -> {
|
||||
val mesh = WearMeshService.peek()
|
||||
@ -244,7 +309,7 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
||||
sendPrivateMessage(m, current.peerID, nick, text, sendScope)
|
||||
}
|
||||
}
|
||||
goBack()
|
||||
navigation.goBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
package com.bitchat.watch
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class WearNavigationStateTest {
|
||||
|
||||
@Test
|
||||
fun `notification dm goes back to main chat`() {
|
||||
val navigation = WearNavigationState()
|
||||
navigation.navigate(WearScreen.People)
|
||||
navigation.navigate(WearScreen.Nickname)
|
||||
|
||||
navigation.openDmFromNotification("peer-a")
|
||||
|
||||
assertEquals(WearScreen.Dm("peer-a"), navigation.screen)
|
||||
assertTrue(navigation.goBack())
|
||||
assertEquals(WearScreen.Chat, navigation.screen)
|
||||
assertFalse(navigation.canGoBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dm opened from people also goes back to main chat`() {
|
||||
val navigation = WearNavigationState()
|
||||
navigation.navigate(WearScreen.People)
|
||||
navigation.navigate(WearScreen.Dm("peer-a"))
|
||||
|
||||
assertTrue(navigation.goBack())
|
||||
|
||||
assertEquals(WearScreen.Chat, navigation.screen)
|
||||
assertFalse(navigation.canGoBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dm text input returns to dm before main chat`() {
|
||||
val navigation = WearNavigationState()
|
||||
navigation.navigate(WearScreen.People)
|
||||
navigation.navigate(WearScreen.Dm("peer-a"))
|
||||
navigation.navigate(WearScreen.TextInput("peer-a"))
|
||||
|
||||
assertTrue(navigation.goBack())
|
||||
assertEquals(WearScreen.Dm("peer-a"), navigation.screen)
|
||||
|
||||
assertTrue(navigation.goBack())
|
||||
assertEquals(WearScreen.Chat, navigation.screen)
|
||||
assertFalse(navigation.canGoBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `normal app launch resets an open dm to main chat`() {
|
||||
val navigation = WearNavigationState()
|
||||
navigation.openDmFromNotification("peer-a")
|
||||
|
||||
navigation.openChat()
|
||||
|
||||
assertEquals(WearScreen.Chat, navigation.screen)
|
||||
assertFalse(navigation.canGoBack)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user