mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Refactor nav3 architecture and enhance adaptive layouts (#4944)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
3feec759a1
commit
f2d09ff79d
29 changed files with 740 additions and 617 deletions
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.feature.messaging.navigation
|
||||
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
|
@ -26,6 +28,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.replaceLast
|
||||
import org.meshtastic.core.ui.component.ScrollToTopEvent
|
||||
import org.meshtastic.feature.messaging.QuickChatScreen
|
||||
import org.meshtastic.feature.messaging.QuickChatViewModel
|
||||
|
|
@ -33,55 +36,54 @@ import org.meshtastic.feature.messaging.ui.contact.AdaptiveContactsScreen
|
|||
import org.meshtastic.feature.messaging.ui.contact.ContactsViewModel
|
||||
import org.meshtastic.feature.messaging.ui.sharing.ShareScreen
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Suppress("LongMethod")
|
||||
fun EntryProviderScope<NavKey>.contactsGraph(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent> = MutableSharedFlow(),
|
||||
) {
|
||||
entry<ContactsRoutes.ContactsGraph> {
|
||||
entry<ContactsRoutes.ContactsGraph>(metadata = { ListDetailSceneStrategy.listPane() }) {
|
||||
ContactsEntryContent(backStack = backStack, scrollToTopEvents = scrollToTopEvents)
|
||||
}
|
||||
|
||||
entry<ContactsRoutes.Contacts> {
|
||||
entry<ContactsRoutes.Contacts>(metadata = { ListDetailSceneStrategy.listPane() }) {
|
||||
ContactsEntryContent(backStack = backStack, scrollToTopEvents = scrollToTopEvents)
|
||||
}
|
||||
|
||||
entry<ContactsRoutes.Messages> { args ->
|
||||
ContactsEntryContent(
|
||||
backStack = backStack,
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
initialContactKey = args.contactKey,
|
||||
initialMessage = args.message,
|
||||
entry<ContactsRoutes.Messages>(metadata = { ListDetailSceneStrategy.detailPane() }) { args ->
|
||||
val contactKey = args.contactKey
|
||||
val messageViewModel: org.meshtastic.feature.messaging.MessageViewModel =
|
||||
koinViewModel(key = "messages-$contactKey")
|
||||
messageViewModel.setContactKey(contactKey)
|
||||
|
||||
org.meshtastic.feature.messaging.MessageScreen(
|
||||
contactKey = contactKey,
|
||||
message = args.message,
|
||||
viewModel = messageViewModel,
|
||||
navigateToNodeDetails = { backStack.add(org.meshtastic.core.navigation.NodesRoutes.NodeDetailGraph(it)) },
|
||||
navigateToQuickChatOptions = { backStack.add(org.meshtastic.core.navigation.ContactsRoutes.QuickChat) },
|
||||
onNavigateBack = { backStack.removeLastOrNull() },
|
||||
)
|
||||
}
|
||||
|
||||
entry<ContactsRoutes.Share> { args ->
|
||||
entry<ContactsRoutes.Share>(metadata = { ListDetailSceneStrategy.extraPane() }) { args ->
|
||||
val message = args.message
|
||||
val viewModel = koinViewModel<ContactsViewModel>()
|
||||
ShareScreen(
|
||||
viewModel = viewModel,
|
||||
onConfirm = {
|
||||
// Navigation 3 - replace Top with Messages manually, but for now we just pop and add
|
||||
backStack.removeLastOrNull()
|
||||
backStack.add(ContactsRoutes.Messages(it, message))
|
||||
},
|
||||
onConfirm = { contactKey -> backStack.replaceLast(ContactsRoutes.Messages(contactKey, message)) },
|
||||
onNavigateUp = { backStack.removeLastOrNull() },
|
||||
)
|
||||
}
|
||||
|
||||
entry<ContactsRoutes.QuickChat> {
|
||||
entry<ContactsRoutes.QuickChat>(metadata = { ListDetailSceneStrategy.extraPane() }) {
|
||||
val viewModel = koinViewModel<QuickChatViewModel>()
|
||||
QuickChatScreen(viewModel = viewModel, onNavigateUp = { backStack.removeLastOrNull() })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContactsEntryContent(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent>,
|
||||
initialContactKey: String? = null,
|
||||
initialMessage: String = "",
|
||||
) {
|
||||
fun ContactsEntryContent(backStack: NavBackStack<NavKey>, scrollToTopEvents: Flow<ScrollToTopEvent>) {
|
||||
val uiViewModel: org.meshtastic.core.ui.viewmodel.UIViewModel = koinViewModel()
|
||||
val sharedContactRequested by uiViewModel.sharedContactRequested.collectAsStateWithLifecycle()
|
||||
val requestChannelSet by uiViewModel.requestChannelSet.collectAsStateWithLifecycle()
|
||||
|
|
@ -90,30 +92,11 @@ fun ContactsEntryContent(
|
|||
AdaptiveContactsScreen(
|
||||
backStack = backStack,
|
||||
contactsViewModel = contactsViewModel,
|
||||
messageViewModel = koinViewModel(), // Ignored by custom detail pane below
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
sharedContactRequested = sharedContactRequested,
|
||||
requestChannelSet = requestChannelSet,
|
||||
onHandleDeepLink = uiViewModel::handleDeepLink,
|
||||
onClearSharedContactRequested = uiViewModel::clearSharedContactRequested,
|
||||
onClearRequestChannelUrl = uiViewModel::clearRequestChannelUrl,
|
||||
initialContactKey = initialContactKey,
|
||||
initialMessage = initialMessage,
|
||||
detailPaneCustom = { contactKey ->
|
||||
val messageViewModel: org.meshtastic.feature.messaging.MessageViewModel =
|
||||
koinViewModel(key = "messages-$contactKey")
|
||||
messageViewModel.setContactKey(contactKey)
|
||||
|
||||
org.meshtastic.feature.messaging.MessageScreen(
|
||||
contactKey = contactKey,
|
||||
message = if (contactKey == initialContactKey) initialMessage else "",
|
||||
viewModel = messageViewModel,
|
||||
navigateToNodeDetails = {
|
||||
backStack.add(org.meshtastic.core.navigation.NodesRoutes.NodeDetailGraph(it))
|
||||
},
|
||||
navigateToQuickChatOptions = { backStack.add(org.meshtastic.core.navigation.ContactsRoutes.QuickChat) },
|
||||
onNavigateBack = { backStack.removeLastOrNull() },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,118 +16,41 @@
|
|||
*/
|
||||
package org.meshtastic.feature.messaging.ui.contact
|
||||
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
||||
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
import org.meshtastic.core.navigation.ChannelsRoutes
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.conversations
|
||||
import org.meshtastic.core.ui.component.AdaptiveListDetailScaffold
|
||||
import org.meshtastic.core.ui.component.EmptyDetailPlaceholder
|
||||
import org.meshtastic.core.ui.component.ScrollToTopEvent
|
||||
import org.meshtastic.core.ui.icon.Conversations
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.feature.messaging.MessageScreen
|
||||
import org.meshtastic.feature.messaging.MessageViewModel
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.SharedContact
|
||||
|
||||
@Suppress("LongMethod", "LongParameterList", "CyclomaticComplexMethod")
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
fun AdaptiveContactsScreen(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
contactsViewModel: ContactsViewModel,
|
||||
messageViewModel: MessageViewModel,
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent>,
|
||||
sharedContactRequested: SharedContact?,
|
||||
requestChannelSet: ChannelSet?,
|
||||
onHandleDeepLink: (MeshtasticUri, onInvalid: () -> Unit) -> Unit,
|
||||
onClearSharedContactRequested: () -> Unit,
|
||||
onClearRequestChannelUrl: () -> Unit,
|
||||
initialContactKey: String? = null,
|
||||
initialMessage: String = "",
|
||||
detailPaneCustom: @Composable ((contactKey: String) -> Unit)? = null,
|
||||
) {
|
||||
val navigator = rememberListDetailPaneScaffoldNavigator<String>()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val onBackToGraph: () -> Unit = {
|
||||
val currentKey = backStack.lastOrNull()
|
||||
|
||||
if (
|
||||
currentKey is ContactsRoutes.Messages ||
|
||||
currentKey is ContactsRoutes.Contacts ||
|
||||
currentKey is ContactsRoutes.ContactsGraph
|
||||
) {
|
||||
// Check if we navigated here from another screen (e.g., from Nodes or Map)
|
||||
val previousKey = if (backStack.size > 1) backStack[backStack.size - 2] else null
|
||||
val isFromDifferentGraph =
|
||||
previousKey != null &&
|
||||
previousKey !is ContactsRoutes.ContactsGraph &&
|
||||
previousKey !is ContactsRoutes.Contacts &&
|
||||
previousKey !is ContactsRoutes.Messages
|
||||
|
||||
if (isFromDifferentGraph) {
|
||||
// Navigate back via NavController to return to the previous screen (e.g. Node Details)
|
||||
backStack.removeLastOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AdaptiveListDetailScaffold(
|
||||
navigator = navigator,
|
||||
ContactsScreen(
|
||||
onNavigateToShare = { backStack.add(ChannelsRoutes.ChannelsGraph) },
|
||||
sharedContactRequested = sharedContactRequested,
|
||||
requestChannelSet = requestChannelSet,
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
onClearSharedContactRequested = onClearSharedContactRequested,
|
||||
onClearRequestChannelUrl = onClearRequestChannelUrl,
|
||||
viewModel = contactsViewModel,
|
||||
onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
onNavigateToMessages = { contactKey -> backStack.add(ContactsRoutes.Messages(contactKey)) },
|
||||
onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
onBackToGraph = onBackToGraph,
|
||||
onTabPressedEvent = { it is ScrollToTopEvent.ConversationsTabPressed },
|
||||
initialKey = initialContactKey,
|
||||
listPane = { isActive, activeContactKey ->
|
||||
ContactsScreen(
|
||||
onNavigateToShare = { backStack.add(ChannelsRoutes.ChannelsGraph) },
|
||||
sharedContactRequested = sharedContactRequested,
|
||||
requestChannelSet = requestChannelSet,
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
onClearSharedContactRequested = onClearSharedContactRequested,
|
||||
onClearRequestChannelUrl = onClearRequestChannelUrl,
|
||||
viewModel = contactsViewModel,
|
||||
onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
onNavigateToMessages = { contactKey ->
|
||||
scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, contactKey) }
|
||||
},
|
||||
onNavigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
activeContactKey = activeContactKey,
|
||||
)
|
||||
},
|
||||
detailPane = { contentKey, handleBack ->
|
||||
if (detailPaneCustom != null) {
|
||||
detailPaneCustom(contentKey)
|
||||
} else {
|
||||
MessageScreen(
|
||||
contactKey = contentKey,
|
||||
message = if (contentKey == initialContactKey) initialMessage else "",
|
||||
viewModel = messageViewModel,
|
||||
navigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
|
||||
navigateToQuickChatOptions = { backStack.add(ContactsRoutes.QuickChat) },
|
||||
onNavigateBack = handleBack,
|
||||
)
|
||||
}
|
||||
},
|
||||
emptyDetailPane = {
|
||||
EmptyDetailPlaceholder(
|
||||
icon = MeshtasticIcons.Conversations,
|
||||
title = stringResource(Res.string.conversations),
|
||||
)
|
||||
},
|
||||
activeContactKey = null,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue