mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: implement unified deep link routing for Kotlin Multiplatform (#4910)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
553ca2f8ed
commit
b0e91a390c
23 changed files with 325 additions and 75 deletions
|
|
@ -57,11 +57,9 @@ import org.meshtastic.core.ui.component.MainAppBar
|
|||
import org.meshtastic.core.ui.component.MeshtasticImportFAB
|
||||
import org.meshtastic.core.ui.component.ScrollToTopEvent
|
||||
import org.meshtastic.core.ui.component.smartScrollToTop
|
||||
import org.meshtastic.core.ui.qr.ScannedQrCodeDialog
|
||||
import org.meshtastic.feature.node.component.NodeContextMenu
|
||||
import org.meshtastic.feature.node.component.NodeFilterTextField
|
||||
import org.meshtastic.feature.node.component.NodeItem
|
||||
import org.meshtastic.proto.SharedContact
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
@ -71,6 +69,7 @@ fun NodeListScreen(
|
|||
onNavigateToChannels: () -> Unit = {},
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent>? = null,
|
||||
activeNodeId: Int? = null,
|
||||
onHandleDeepLink: (org.meshtastic.core.common.util.MeshtasticUri, onInvalid: () -> Unit) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val showToast = org.meshtastic.core.ui.util.rememberShowToastResource()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -100,9 +99,6 @@ fun NodeListScreen(
|
|||
derivedStateOf { listState.isScrollInProgress && (listState.canScrollForward || listState.canScrollBackward) }
|
||||
}
|
||||
|
||||
val requestChannelSet by viewModel.requestChannelSet.collectAsStateWithLifecycle()
|
||||
requestChannelSet?.let { ScannedQrCodeDialog(it, onDismiss = { viewModel.clearRequestChannelSet() }) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
|
|
@ -118,14 +114,13 @@ fun NodeListScreen(
|
|||
},
|
||||
floatingActionButton = {
|
||||
val shareCapable = ourNode?.capabilities?.supportsQrCodeSharing ?: false
|
||||
val sharedContact: SharedContact? by viewModel.sharedContactRequested.collectAsStateWithLifecycle(null)
|
||||
if (!isScrollInProgress && connectionState == ConnectionState.Connected && shareCapable) {
|
||||
MeshtasticImportFAB(
|
||||
sharedContact = sharedContact,
|
||||
onImport = { uriString ->
|
||||
viewModel.handleScannedUri(uriString) { scope.launch { showToast(Res.string.channel_invalid) } }
|
||||
onHandleDeepLink(org.meshtastic.core.common.util.MeshtasticUri(uriString)) {
|
||||
scope.launch { showToast(Res.string.channel_invalid) }
|
||||
}
|
||||
},
|
||||
onDismissSharedContact = { viewModel.setSharedContactRequested(null) },
|
||||
isContactContext = true,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,18 +20,14 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.util.dispatchMeshtasticUri
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
|
|
@ -40,7 +36,6 @@ import org.meshtastic.feature.node.detail.NodeManagementActions
|
|||
import org.meshtastic.feature.node.domain.usecase.GetFilteredNodesUseCase
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.proto.SharedContact
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@KoinViewModel
|
||||
|
|
@ -63,12 +58,6 @@ class NodeListViewModel(
|
|||
|
||||
val connectionState = serviceRepository.connectionState
|
||||
|
||||
private val _sharedContactRequested: MutableStateFlow<SharedContact?> = MutableStateFlow(null)
|
||||
val sharedContactRequested = _sharedContactRequested.asStateFlow()
|
||||
|
||||
private val _requestChannelSet = MutableStateFlow<ChannelSet?>(null)
|
||||
val requestChannelSet = _requestChannelSet.asStateFlow()
|
||||
|
||||
private val nodeSortOption = nodeFilterPreferences.nodeSortOption
|
||||
|
||||
private val _nodeFilterText = savedStateHandle.getStateFlow(KEY_FILTER_TEXT, "")
|
||||
|
|
@ -135,24 +124,6 @@ class NodeListViewModel(
|
|||
nodeFilterPreferences.setNodeSort(sort)
|
||||
}
|
||||
|
||||
fun setSharedContactRequested(sharedContact: SharedContact?) {
|
||||
_sharedContactRequested.value = sharedContact
|
||||
}
|
||||
|
||||
/** Unified handler for scanned Meshtastic URIs (contacts or channels). */
|
||||
fun handleScannedUri(uriString: String, onInvalid: () -> Unit) {
|
||||
val uri = CommonUri.parse(uriString)
|
||||
uri.dispatchMeshtasticUri(
|
||||
onContact = { _sharedContactRequested.value = it },
|
||||
onChannel = { _requestChannelSet.value = it },
|
||||
onInvalid = onInvalid,
|
||||
)
|
||||
}
|
||||
|
||||
fun clearRequestChannelSet() {
|
||||
_requestChannelSet.value = null
|
||||
}
|
||||
|
||||
fun setChannels(channelSet: ChannelSet) = viewModelScope.launch {
|
||||
radioConfigRepository.replaceAllSettings(channelSet.settings)
|
||||
val newLoraConfig = channelSet.lora_config
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ fun AdaptiveNodeListScreen(
|
|||
initialNodeId: Int? = null,
|
||||
onNavigate: (Route) -> Unit = {},
|
||||
onNavigateToMessages: (String) -> Unit = {},
|
||||
onHandleDeepLink: (org.meshtastic.core.common.util.MeshtasticUri, onInvalid: () -> Unit) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val nodeListViewModel: NodeListViewModel = koinViewModel()
|
||||
val navigator = rememberListDetailPaneScaffoldNavigator<Int>()
|
||||
|
|
@ -85,6 +86,7 @@ fun AdaptiveNodeListScreen(
|
|||
onNavigateToChannels = { backStack.add(ChannelsRoutes.ChannelsGraph) },
|
||||
scrollToTopEvents = scrollToTopEvents,
|
||||
activeNodeId = activeNodeId,
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
)
|
||||
},
|
||||
detailPane = { contentKey, handleBack ->
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import kotlin.reflect.KClass
|
|||
fun EntryProviderScope<NavKey>.nodesGraph(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent> = MutableSharedFlow(),
|
||||
onHandleDeepLink: (org.meshtastic.core.common.util.MeshtasticUri, onInvalid: () -> Unit) -> Unit = { _, _ -> },
|
||||
nodeMapScreen: @Composable (destNum: Int, onNavigateUp: () -> Unit) -> Unit = { _, _ -> },
|
||||
) {
|
||||
entry<NodesRoutes.NodesGraph> {
|
||||
|
|
@ -75,6 +76,7 @@ fun EntryProviderScope<NavKey>.nodesGraph(
|
|||
scrollToTopEvents = scrollToTopEvents,
|
||||
onNavigate = { backStack.add(it) },
|
||||
onNavigateToMessages = { backStack.add(ContactsRoutes.Messages(it)) },
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -84,16 +86,18 @@ fun EntryProviderScope<NavKey>.nodesGraph(
|
|||
scrollToTopEvents = scrollToTopEvents,
|
||||
onNavigate = { backStack.add(it) },
|
||||
onNavigateToMessages = { backStack.add(ContactsRoutes.Messages(it)) },
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
)
|
||||
}
|
||||
|
||||
nodeDetailGraph(backStack, scrollToTopEvents, nodeMapScreen)
|
||||
nodeDetailGraph(backStack, scrollToTopEvents, onHandleDeepLink, nodeMapScreen)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun EntryProviderScope<NavKey>.nodeDetailGraph(
|
||||
backStack: NavBackStack<NavKey>,
|
||||
scrollToTopEvents: Flow<ScrollToTopEvent>,
|
||||
onHandleDeepLink: (org.meshtastic.core.common.util.MeshtasticUri, onInvalid: () -> Unit) -> Unit = { _, _ -> },
|
||||
nodeMapScreen: @Composable (destNum: Int, onNavigateUp: () -> Unit) -> Unit,
|
||||
) {
|
||||
entry<NodesRoutes.NodeDetailGraph> { args ->
|
||||
|
|
@ -103,6 +107,7 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
|
|||
initialNodeId = args.destNum,
|
||||
onNavigate = { backStack.add(it) },
|
||||
onNavigateToMessages = { backStack.add(ContactsRoutes.Messages(it)) },
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +118,7 @@ fun EntryProviderScope<NavKey>.nodeDetailGraph(
|
|||
initialNodeId = args.destNum,
|
||||
onNavigate = { backStack.add(it) },
|
||||
onNavigateToMessages = { backStack.add(ContactsRoutes.Messages(it)) },
|
||||
onHandleDeepLink = onHandleDeepLink,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue