diff --git a/app/src/fdroid/kotlin/org/meshtastic/app/map/FdroidMapViewProvider.kt b/app/src/fdroid/kotlin/org/meshtastic/app/map/FdroidMapViewProvider.kt index 290ea8667..99f184efc 100644 --- a/app/src/fdroid/kotlin/org/meshtastic/app/map/FdroidMapViewProvider.kt +++ b/app/src/fdroid/kotlin/org/meshtastic/app/map/FdroidMapViewProvider.kt @@ -17,6 +17,7 @@ package org.meshtastic.app.map import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import org.koin.compose.viewmodel.koinViewModel import org.koin.core.annotation.Single @@ -34,8 +35,10 @@ class FdroidMapViewProvider : MapViewProvider { tracerouteOverlay: Any?, tracerouteNodePositions: Map, onTracerouteMappableCountChanged: (Int, Int) -> Unit, + waypointId: Int?, ) { val mapViewModel: MapViewModel = koinViewModel() + LaunchedEffect(waypointId) { mapViewModel.setWaypointId(waypointId) } org.meshtastic.app.map.MapView( modifier = modifier, mapViewModel = mapViewModel, diff --git a/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt b/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt index aea48c26e..ab891cbc6 100644 --- a/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt +++ b/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt @@ -47,6 +47,12 @@ class MapViewModel( private val _selectedWaypointId = MutableStateFlow(savedStateHandle.get("waypointId")) val selectedWaypointId: StateFlow = _selectedWaypointId.asStateFlow() + fun setWaypointId(id: Int?) { + if (id != null) { + _selectedWaypointId.value = id + } + } + var mapStyleId: Int get() = mapPrefs.mapStyle.value set(value) { diff --git a/app/src/google/kotlin/org/meshtastic/app/map/GoogleMapViewProvider.kt b/app/src/google/kotlin/org/meshtastic/app/map/GoogleMapViewProvider.kt index 96680ce88..c228297a3 100644 --- a/app/src/google/kotlin/org/meshtastic/app/map/GoogleMapViewProvider.kt +++ b/app/src/google/kotlin/org/meshtastic/app/map/GoogleMapViewProvider.kt @@ -17,6 +17,7 @@ package org.meshtastic.app.map import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import org.koin.compose.viewmodel.koinViewModel import org.koin.core.annotation.Single @@ -34,8 +35,10 @@ class GoogleMapViewProvider : MapViewProvider { tracerouteOverlay: Any?, tracerouteNodePositions: Map, onTracerouteMappableCountChanged: (Int, Int) -> Unit, + waypointId: Int?, ) { val mapViewModel: MapViewModel = koinViewModel() + LaunchedEffect(waypointId) { mapViewModel.setWaypointId(waypointId) } org.meshtastic.app.map.MapView( modifier = modifier, mapViewModel = mapViewModel, diff --git a/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt b/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt index 756afe928..8e448ce80 100644 --- a/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt +++ b/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt @@ -91,6 +91,20 @@ class MapViewModel( private val _selectedWaypointId = MutableStateFlow(savedStateHandle.get("waypointId")) val selectedWaypointId: StateFlow = _selectedWaypointId.asStateFlow() + fun setWaypointId(id: Int?) { + if (id != null && _selectedWaypointId.value != id) { + _selectedWaypointId.value = id + viewModelScope.launch { + val wpMap = waypoints.first { it.containsKey(id) } + wpMap[id]?.let { packet -> + val waypoint = packet.waypoint!! + val latLng = LatLng((waypoint.latitude_i ?: 0) / 1e7, (waypoint.longitude_i ?: 0) / 1e7) + cameraPositionState.position = CameraPosition.fromLatLngZoom(latLng, 15f) + } + } + } + } + private val targetLatLng = googleMapsPrefs.cameraTargetLat.value .takeIf { it != 0.0 } diff --git a/app/src/google/kotlin/org/meshtastic/app/map/prefs/map/GoogleMapsPrefs.kt b/app/src/google/kotlin/org/meshtastic/app/map/prefs/map/GoogleMapsPrefs.kt index 0beba5e92..6cf6091b1 100644 --- a/app/src/google/kotlin/org/meshtastic/app/map/prefs/map/GoogleMapsPrefs.kt +++ b/app/src/google/kotlin/org/meshtastic/app/map/prefs/map/GoogleMapsPrefs.kt @@ -123,14 +123,30 @@ class GoogleMapsPrefsImpl( } override val cameraTargetLat: StateFlow = - dataStore.data.map { it[KEY_CAMERA_TARGET_LAT_PREF] ?: 0.0 }.stateIn(scope, SharingStarted.Eagerly, 0.0) + dataStore.data + .map { + try { + it[KEY_CAMERA_TARGET_LAT_PREF] ?: 0.0 + } catch (_: ClassCastException) { + it[floatPreferencesKey(KEY_CAMERA_TARGET_LAT_PREF.name)]?.toDouble() ?: 0.0 + } + } + .stateIn(scope, SharingStarted.Eagerly, 0.0) override fun setCameraTargetLat(value: Double) { scope.launch { dataStore.edit { it[KEY_CAMERA_TARGET_LAT_PREF] = value } } } override val cameraTargetLng: StateFlow = - dataStore.data.map { it[KEY_CAMERA_TARGET_LNG_PREF] ?: 0.0 }.stateIn(scope, SharingStarted.Eagerly, 0.0) + dataStore.data + .map { + try { + it[KEY_CAMERA_TARGET_LNG_PREF] ?: 0.0 + } catch (_: ClassCastException) { + it[floatPreferencesKey(KEY_CAMERA_TARGET_LNG_PREF.name)]?.toDouble() ?: 0.0 + } + } + .stateIn(scope, SharingStarted.Eagerly, 0.0) override fun setCameraTargetLng(value: Double) { scope.launch { dataStore.edit { it[KEY_CAMERA_TARGET_LNG_PREF] = value } } diff --git a/app/src/main/kotlin/org/meshtastic/app/navigation/ContactsNavigation.kt b/app/src/main/kotlin/org/meshtastic/app/navigation/ContactsNavigation.kt index 84b1eeec5..84d9e2cf1 100644 --- a/app/src/main/kotlin/org/meshtastic/app/navigation/ContactsNavigation.kt +++ b/app/src/main/kotlin/org/meshtastic/app/navigation/ContactsNavigation.kt @@ -88,6 +88,7 @@ private fun ContactsEntryContent( val requestChannelSet by uiViewModel.requestChannelSet.collectAsStateWithLifecycle() val contactsViewModel = koinViewModel() val messageViewModel = koinViewModel() + initialContactKey?.let { messageViewModel.setContactKey(it) } AdaptiveContactsScreen( backStack = backStack, diff --git a/app/src/main/kotlin/org/meshtastic/app/navigation/MapNavigation.kt b/app/src/main/kotlin/org/meshtastic/app/navigation/MapNavigation.kt index 26b1313f2..0360f8f6c 100644 --- a/app/src/main/kotlin/org/meshtastic/app/navigation/MapNavigation.kt +++ b/app/src/main/kotlin/org/meshtastic/app/navigation/MapNavigation.kt @@ -26,12 +26,13 @@ import org.meshtastic.feature.map.MapScreen import org.meshtastic.feature.map.SharedMapViewModel fun EntryProviderScope.mapGraph(backStack: NavBackStack) { - entry { + entry { args -> val viewModel = koinViewModel() MapScreen( viewModel = viewModel, onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, navigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) }, + waypointId = args.waypointId, ) } } diff --git a/app/src/main/kotlin/org/meshtastic/app/navigation/NodesNavigation.kt b/app/src/main/kotlin/org/meshtastic/app/navigation/NodesNavigation.kt index 1a121b9ba..9161b113a 100644 --- a/app/src/main/kotlin/org/meshtastic/app/navigation/NodesNavigation.kt +++ b/app/src/main/kotlin/org/meshtastic/app/navigation/NodesNavigation.kt @@ -111,6 +111,7 @@ fun EntryProviderScope.nodeDetailGraph( entry { args -> val vm = koinViewModel() + vm.setDestNum(args.destNum) NodeMapScreen(vm, onNavigateUp = { backStack.removeLastOrNull() }) } diff --git a/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt b/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt index 6656064bc..f6828c280 100644 --- a/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt +++ b/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt @@ -299,24 +299,36 @@ fun MainScreen(uIViewModel: UIViewModel = koinViewModel(), scanModel: ScannerVie TopLevelDestination.Nodes -> { val onNodesList = currentKey is NodesRoutes.Nodes if (!onNodesList) { - backStack.clear() - backStack.add(destination.route) + if (backStack.isNotEmpty()) { + backStack[0] = destination.route + while (backStack.size > 1) backStack.removeAt(backStack.lastIndex) + } else { + backStack.add(destination.route) + } } uIViewModel.emitScrollToTopEvent(ScrollToTopEvent.NodesTabPressed) } TopLevelDestination.Conversations -> { val onConversationsList = currentKey is ContactsRoutes.Contacts if (!onConversationsList) { - backStack.clear() - backStack.add(destination.route) + if (backStack.isNotEmpty()) { + backStack[0] = destination.route + while (backStack.size > 1) backStack.removeAt(backStack.lastIndex) + } else { + backStack.add(destination.route) + } } uIViewModel.emitScrollToTopEvent(ScrollToTopEvent.ConversationsTabPressed) } else -> Unit } } else { - backStack.clear() - backStack.add(destination.route) + if (backStack.isNotEmpty()) { + backStack[0] = destination.route + while (backStack.size > 1) backStack.removeAt(backStack.lastIndex) + } else { + backStack.add(destination.route) + } } }, ) diff --git a/app/src/main/kotlin/org/meshtastic/app/ui/node/AdaptiveNodeListScreen.kt b/app/src/main/kotlin/org/meshtastic/app/ui/node/AdaptiveNodeListScreen.kt index fed52eb6e..36b4a269f 100644 --- a/app/src/main/kotlin/org/meshtastic/app/ui/node/AdaptiveNodeListScreen.kt +++ b/app/src/main/kotlin/org/meshtastic/app/ui/node/AdaptiveNodeListScreen.kt @@ -66,7 +66,8 @@ fun AdaptiveNodeListScreen( val currentKey = backStack.lastOrNull() val isNodesRoute = currentKey is NodesRoutes.Nodes || currentKey is NodesRoutes.NodesGraph val previousKey = if (backStack.size > 1) backStack[backStack.size - 2] else null - val isFromDifferentGraph = previousKey !is NodesRoutes.NodesGraph && previousKey !is NodesRoutes.Nodes + val isFromDifferentGraph = + previousKey != null && previousKey !is NodesRoutes.NodesGraph && previousKey !is NodesRoutes.Nodes if (isFromDifferentGraph && !isNodesRoute) { // Navigate back via NavController to return to the previous screen diff --git a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt index 319755d42..4561886e2 100644 --- a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt +++ b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt @@ -37,6 +37,7 @@ interface MapViewProvider { tracerouteOverlay: Any? = null, tracerouteNodePositions: Map = emptyMap(), onTracerouteMappableCountChanged: (Int, Int) -> Unit = { _, _ -> }, + waypointId: Int? = null, ) } diff --git a/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt b/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt index 666ae7438..a018ca8e6 100644 --- a/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt +++ b/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt @@ -35,6 +35,7 @@ fun MapScreen( navigateToNodeDetails: (Int) -> Unit, modifier: Modifier = Modifier, viewModel: SharedMapViewModel, + waypointId: Int? = null, ) { val ourNodeInfo by viewModel.ourNodeInfo.collectAsStateWithLifecycle() val isConnected by viewModel.isConnected.collectAsStateWithLifecycle() @@ -58,6 +59,7 @@ fun MapScreen( modifier = Modifier.fillMaxSize().padding(paddingValues), viewModel = viewModel, navigateToNodeDetails = navigateToNodeDetails, + waypointId = waypointId, ) } } diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt index 7a81a22d5..ea37d1008 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt @@ -18,8 +18,10 @@ package org.meshtastic.feature.map.node import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map @@ -44,11 +46,19 @@ class NodeMapViewModel( buildConfigProvider: BuildConfigProvider, private val mapPrefs: MapPrefs, ) : ViewModel() { - private val destNum = savedStateHandle.get("destNum") ?: 0 + private val destNumFromRoute = savedStateHandle.get("destNum") + private val manualDestNum = MutableStateFlow(null) + + private val destNumFlow = + combine(MutableStateFlow(destNumFromRoute), manualDestNum) { route, manual -> manual ?: route ?: 0 } + + fun setDestNum(num: Int) { + manualDestNum.value = num + } val node = - nodeRepository.nodeDBbyNum - .mapLatest { it[destNum] } + destNumFlow + .flatMapLatest { destNum -> nodeRepository.nodeDBbyNum.mapLatest { it[destNum] } } .distinctUntilChanged() .stateInWhileSubscribed(initialValue = null) @@ -57,8 +67,9 @@ class NodeMapViewModel( private val ourNodeNumFlow = nodeRepository.myNodeInfo.map { it?.myNodeNum }.distinctUntilChanged() val positionLogs: StateFlow> = - ourNodeNumFlow - .map { if (destNum == it) MeshLog.NODE_NUM_LOCAL else destNum } + combine(ourNodeNumFlow, destNumFlow) { ourNodeNum, destNum -> + if (destNum == ourNodeNum) MeshLog.NODE_NUM_LOCAL else destNum + } .distinctUntilChanged() .flatMapLatest { logId -> meshLogRepository.getMeshPacketsFrom(logId, PortNum.POSITION_APP.value).map { packets -> diff --git a/feature/messaging/src/androidMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt b/feature/messaging/src/androidMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt index 76b78a532..3086e8d1e 100644 --- a/feature/messaging/src/androidMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt +++ b/feature/messaging/src/androidMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt @@ -78,7 +78,8 @@ fun AdaptiveContactsScreen( // 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 !is ContactsRoutes.ContactsGraph && + previousKey != null && + previousKey !is ContactsRoutes.ContactsGraph && previousKey !is ContactsRoutes.Contacts && previousKey !is ContactsRoutes.Messages diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt index 8cf0004ed..e7ebda5c6 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt @@ -151,6 +151,12 @@ class MessageViewModel( } } + fun setContactKey(contactKey: String) { + if (contactKeyForPagedMessages.value != contactKey) { + contactKeyForPagedMessages.value = contactKey + } + } + fun setTitle(title: String) { viewModelScope.launch { _title.value = title } }