mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-29 07:16:08 +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 nicknameChosen by mutableStateOf(false)
|
||||||
private var notificationsGranted by mutableStateOf(false)
|
private var notificationsGranted by mutableStateOf(false)
|
||||||
private var notificationPromptDismissed 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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
nicknameChosen = getSharedPreferences("bitchat_watch_prefs", Context.MODE_PRIVATE)
|
nicknameChosen = getSharedPreferences("bitchat_watch_prefs", Context.MODE_PRIVATE)
|
||||||
.getBoolean("nickname_chosen", false)
|
.getBoolean("nickname_chosen", false)
|
||||||
pendingDmPeer = privateMessagePeerFromIntent(intent)
|
val notificationPeer = consumePrivateMessagePeer(intent)
|
||||||
|
if (savedInstanceState == null && notificationPeer != null) {
|
||||||
|
requestLaunch(WearLaunchTarget.Dm(notificationPeer))
|
||||||
|
}
|
||||||
refreshState()
|
refreshState()
|
||||||
setContent {
|
setContent {
|
||||||
BitchatWearTheme {
|
BitchatWearTheme {
|
||||||
@ -96,8 +100,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
onSkip = { notificationPromptDismissed = true }
|
onSkip = { notificationPromptDismissed = true }
|
||||||
)
|
)
|
||||||
else -> WearNavHost(
|
else -> WearNavHost(
|
||||||
openDmPeer = pendingDmPeer,
|
launchRequest = pendingLaunchRequest,
|
||||||
onOpenDmHandled = { pendingDmPeer = null }
|
onLaunchRequestHandled = { requestID ->
|
||||||
|
if (pendingLaunchRequest?.id == requestID) {
|
||||||
|
pendingLaunchRequest = null
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +130,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
privateMessagePeerFromIntent(intent)?.let { pendingDmPeer = it }
|
val notificationPeer = consumePrivateMessagePeer(intent)
|
||||||
|
requestLaunch(
|
||||||
|
notificationPeer?.let(WearLaunchTarget::Dm) ?: WearLaunchTarget.Chat
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshState() {
|
private fun refreshState() {
|
||||||
@ -142,6 +153,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
startForegroundService(Intent(this, WearMeshForegroundService::class.java))
|
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? {
|
private fun privateMessagePeerFromIntent(intent: Intent?): String? {
|
||||||
if (intent?.getBooleanExtra(WearNotificationCoordinator.EXTRA_OPEN_DM, false) != true) {
|
if (intent?.getBooleanExtra(WearNotificationCoordinator.EXTRA_OPEN_DM, false) != true) {
|
||||||
return null
|
return null
|
||||||
@ -150,6 +168,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun requestLaunch(target: WearLaunchTarget) {
|
||||||
|
pendingLaunchRequest = WearLaunchRequest(
|
||||||
|
id = ++nextLaunchRequestID,
|
||||||
|
target = target
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun notificationPermissionGranted(): Boolean {
|
private fun notificationPermissionGranted(): Boolean {
|
||||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
return Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||||
ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
@ -169,36 +194,74 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
internal sealed interface WearLaunchTarget {
|
||||||
fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
data object Chat : WearLaunchTarget
|
||||||
var screen by remember { mutableStateOf<WearScreen>(WearScreen.Chat) }
|
data class Dm(val peerID: String) : WearLaunchTarget
|
||||||
val backStack = remember { mutableStateListOf<WearScreen>() }
|
}
|
||||||
|
|
||||||
|
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) {
|
fun navigate(to: WearScreen) {
|
||||||
backStack.add(screen)
|
backStack.add(screen)
|
||||||
screen = to
|
screen = to
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goBack(): Boolean {
|
fun openChat() {
|
||||||
val previous = backStack.removeLastOrNull()
|
backStack.clear()
|
||||||
return if (previous != null) {
|
screen = WearScreen.Chat
|
||||||
screen = previous
|
|
||||||
true
|
|
||||||
} else false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(enabled = backStack.isNotEmpty()) { goBack() }
|
fun openDmFromNotification(peerID: String) {
|
||||||
|
backStack.clear()
|
||||||
|
backStack.add(WearScreen.Chat)
|
||||||
|
screen = WearScreen.Dm(peerID)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(openDmPeer) {
|
fun goBack(): Boolean {
|
||||||
openDmPeer?.let { peerID ->
|
if (screen is WearScreen.Dm) {
|
||||||
backStack.clear()
|
openChat()
|
||||||
screen = WearScreen.Dm(peerID)
|
return true
|
||||||
onOpenDmHandled()
|
}
|
||||||
|
|
||||||
|
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(
|
AnimatedContent(
|
||||||
targetState = screen,
|
targetState = navigation.screen,
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
fadeIn(tween(com.bitchat.watch.ui.theme.BitchatMotion.EMPHASIZED_MS)) togetherWith
|
fadeIn(tween(com.bitchat.watch.ui.theme.BitchatMotion.EMPHASIZED_MS)) togetherWith
|
||||||
fadeOut(tween(com.bitchat.watch.ui.theme.BitchatMotion.QUICK_MS))
|
fadeOut(tween(com.bitchat.watch.ui.theme.BitchatMotion.QUICK_MS))
|
||||||
@ -207,12 +270,12 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
|||||||
) { current ->
|
) { current ->
|
||||||
when (current) {
|
when (current) {
|
||||||
is WearScreen.Chat -> ChatScreen(
|
is WearScreen.Chat -> ChatScreen(
|
||||||
onOpenPeople = { navigate(WearScreen.People) },
|
onOpenPeople = { navigation.navigate(WearScreen.People) },
|
||||||
onOpenTextInput = { navigate(WearScreen.TextInput(null)) }
|
onOpenTextInput = { navigation.navigate(WearScreen.TextInput(null)) }
|
||||||
)
|
)
|
||||||
is WearScreen.People -> PeopleScreen(
|
is WearScreen.People -> PeopleScreen(
|
||||||
onOpenDm = { navigate(WearScreen.Dm(it)) },
|
onOpenDm = { navigation.navigate(WearScreen.Dm(it)) },
|
||||||
onEditNickname = { navigate(WearScreen.Nickname) }
|
onEditNickname = { navigation.navigate(WearScreen.Nickname) }
|
||||||
)
|
)
|
||||||
is WearScreen.Nickname -> {
|
is WearScreen.Nickname -> {
|
||||||
val mesh = WearMeshService.peek()
|
val mesh = WearMeshService.peek()
|
||||||
@ -223,13 +286,15 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
|||||||
confirmLabel = "Save",
|
confirmLabel = "Save",
|
||||||
onConfirm = { name ->
|
onConfirm = { name ->
|
||||||
mesh?.setNickname(name)
|
mesh?.setNickname(name)
|
||||||
goBack()
|
navigation.goBack()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is WearScreen.Dm -> DmScreen(
|
is WearScreen.Dm -> DmScreen(
|
||||||
peerID = current.peerID,
|
peerID = current.peerID,
|
||||||
onOpenTextInput = { navigate(WearScreen.TextInput(current.peerID)) }
|
onOpenTextInput = {
|
||||||
|
navigation.navigate(WearScreen.TextInput(current.peerID))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
is WearScreen.TextInput -> {
|
is WearScreen.TextInput -> {
|
||||||
val mesh = WearMeshService.peek()
|
val mesh = WearMeshService.peek()
|
||||||
@ -244,7 +309,7 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) {
|
|||||||
sendPrivateMessage(m, current.peerID, nick, text, sendScope)
|
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