mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Decouple MapView from UiViewModel (#3213)
This commit is contained in:
parent
3d94391bb1
commit
af8e1daa5d
10 changed files with 102 additions and 86 deletions
|
|
@ -19,6 +19,7 @@ package com.geeksville.mesh.ui.map
|
|||
|
||||
import android.Manifest // Added for Accompanist
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -61,13 +62,11 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geeksville.mesh.MeshProtos.Waypoint
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.gpsDisabled
|
||||
import com.geeksville.mesh.android.hasGps
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.map.components.CacheLayout
|
||||
import com.geeksville.mesh.ui.map.components.DownloadButton
|
||||
import com.geeksville.mesh.ui.map.components.EditWaypointDialog
|
||||
|
|
@ -204,21 +203,17 @@ private fun Context.purgeTileSource(onResult: (String) -> Unit) {
|
|||
* Main composable for displaying the map view, including nodes, waypoints, and user location. It handles user
|
||||
* interactions for map manipulation, filtering, and offline caching.
|
||||
*
|
||||
* @param model The [UIViewModel] providing data and state for the map.
|
||||
* @param mapViewModel The [MapViewModel] providing data and state for the map.
|
||||
* @param navigateToNodeDetails Callback to navigate to the details screen of a selected node.
|
||||
*/
|
||||
@OptIn(ExperimentalPermissionsApi::class) // Added for Accompanist
|
||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
||||
@Composable
|
||||
fun MapView(
|
||||
uiViewModel: UIViewModel = viewModel(),
|
||||
mapViewModel: MapViewModel = hiltViewModel(),
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
) {
|
||||
fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails: (Int) -> Unit) {
|
||||
var mapFilterExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
val mapFilterState by mapViewModel.mapFilterStateFlow.collectAsStateWithLifecycle()
|
||||
val isConnected by uiViewModel.isConnectedStateFlow.collectAsStateWithLifecycle()
|
||||
val isConnected by mapViewModel.isConnected.collectAsStateWithLifecycle()
|
||||
|
||||
var cacheEstimate by remember { mutableStateOf("") }
|
||||
|
||||
|
|
@ -267,7 +262,7 @@ fun MapView(
|
|||
fun MapView.toggleMyLocation() {
|
||||
if (context.gpsDisabled()) {
|
||||
debug("Telling user we need location turned on for MyLocationNewOverlay")
|
||||
uiViewModel.showSnackBar(R.string.location_disabled)
|
||||
Toast.makeText(context, R.string.location_disabled, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
debug("user clicked MyLocationNewOverlay ${myLocationOverlay == null}")
|
||||
|
|
@ -313,8 +308,8 @@ fun MapView(
|
|||
|
||||
fun MapView.onNodesChanged(nodes: Collection<Node>): List<MarkerWithLabel> {
|
||||
val nodesWithPosition = nodes.filter { it.validPosition != null }
|
||||
val ourNode = uiViewModel.ourNodeInfo.value
|
||||
val displayUnits = uiViewModel.config.display.units
|
||||
val ourNode = mapViewModel.ourNodeInfo.value
|
||||
val displayUnits = mapViewModel.config.display.units
|
||||
val mapFilterStateValue = mapViewModel.mapFilterStateFlow.value // Access mapFilterState directly
|
||||
return nodesWithPosition.mapNotNull { node ->
|
||||
if (mapFilterStateValue.onlyFavorites && !node.isFavorite && !node.equals(ourNode)) {
|
||||
|
|
@ -360,13 +355,13 @@ fun MapView(
|
|||
builder.setNeutralButton(R.string.cancel) { _, _ -> debug("User canceled marker delete dialog") }
|
||||
builder.setNegativeButton(R.string.delete_for_me) { _, _ ->
|
||||
debug("User deleted waypoint ${waypoint.id} for me")
|
||||
uiViewModel.deleteWaypoint(waypoint.id)
|
||||
mapViewModel.deleteWaypoint(waypoint.id)
|
||||
}
|
||||
if (waypoint.lockedTo in setOf(0, uiViewModel.myNodeNum ?: 0) && isConnected) {
|
||||
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
|
||||
builder.setPositiveButton(R.string.delete_for_everyone) { _, _ ->
|
||||
debug("User deleted waypoint ${waypoint.id} for everyone")
|
||||
uiViewModel.sendWaypoint(waypoint.copy { expire = 1 })
|
||||
uiViewModel.deleteWaypoint(waypoint.id)
|
||||
mapViewModel.sendWaypoint(waypoint.copy { expire = 1 })
|
||||
mapViewModel.deleteWaypoint(waypoint.id)
|
||||
}
|
||||
}
|
||||
val dialog = builder.show()
|
||||
|
|
@ -390,7 +385,7 @@ fun MapView(
|
|||
debug("marker long pressed id=$id")
|
||||
val waypoint = waypoints[id]?.data?.waypoint ?: return
|
||||
// edit only when unlocked or lockedTo myNodeNum
|
||||
if (waypoint.lockedTo in setOf(0, uiViewModel.myNodeNum ?: 0) && isConnected) {
|
||||
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
|
||||
showEditWaypointDialog = waypoint
|
||||
} else {
|
||||
showDeleteMarkerDialog(waypoint)
|
||||
|
|
@ -400,7 +395,7 @@ fun MapView(
|
|||
fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL) {
|
||||
context.getString(R.string.you)
|
||||
} else {
|
||||
uiViewModel.getUser(id).longName
|
||||
mapViewModel.getUser(id).longName
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -451,7 +446,7 @@ fun MapView(
|
|||
|
||||
LaunchedEffect(showCurrentCacheInfo) {
|
||||
if (!showCurrentCacheInfo) return@LaunchedEffect
|
||||
uiViewModel.showSnackBar(R.string.calculating)
|
||||
Toast.makeText(context, R.string.calculating, Toast.LENGTH_SHORT).show()
|
||||
val cacheManager = CacheManager(map)
|
||||
val cacheCapacity = cacheManager.cacheCapacity()
|
||||
val currentCacheUsage = cacheManager.currentCacheUsage()
|
||||
|
|
@ -560,11 +555,16 @@ fun MapView(
|
|||
zoomLevelMax.toInt(),
|
||||
cacheManagerCallback(
|
||||
onTaskComplete = {
|
||||
uiViewModel.showSnackBar(R.string.map_download_complete)
|
||||
Toast.makeText(context, R.string.map_download_complete, Toast.LENGTH_SHORT).show()
|
||||
writer.onDetach()
|
||||
},
|
||||
onTaskFailed = { errors ->
|
||||
uiViewModel.showSnackBar(context.getString(R.string.map_download_errors, errors))
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.map_download_errors, errors),
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
writer.onDetach()
|
||||
},
|
||||
),
|
||||
|
|
@ -609,7 +609,7 @@ fun MapView(
|
|||
dialog.dismiss()
|
||||
}
|
||||
|
||||
2 -> purgeTileSource { uiViewModel.showSnackBar(it) }
|
||||
2 -> purgeTileSource { Toast.makeText(this, it, Toast.LENGTH_SHORT).show() }
|
||||
else -> dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -770,12 +770,12 @@ fun MapView(
|
|||
onSendClicked = { waypoint ->
|
||||
debug("User clicked send waypoint ${waypoint.id}")
|
||||
showEditWaypointDialog = null
|
||||
uiViewModel.sendWaypoint(
|
||||
mapViewModel.sendWaypoint(
|
||||
waypoint.copy {
|
||||
if (id == 0) id = uiViewModel.generatePacketId() ?: return@EditWaypointDialog
|
||||
if (id == 0) id = mapViewModel.generatePacketId() ?: return@EditWaypointDialog
|
||||
if (name == "") name = "Dropped Pin"
|
||||
if (expire == 0) expire = Int.MAX_VALUE
|
||||
lockedTo = if (waypoint.lockedTo != 0) uiViewModel.myNodeNum ?: 0 else 0
|
||||
lockedTo = if (waypoint.lockedTo != 0) mapViewModel.myNodeNum ?: 0 else 0
|
||||
if (waypoint.icon == 0) icon = 128205
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,10 +17,16 @@
|
|||
|
||||
package com.geeksville.mesh.ui.map
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import com.geeksville.mesh.database.NodeRepository
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
import com.geeksville.mesh.service.ServiceRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -30,8 +36,9 @@ class MapViewModel
|
|||
constructor(
|
||||
mapPrefs: MapPrefs,
|
||||
packetRepository: PacketRepository,
|
||||
nodeRepository: NodeRepository,
|
||||
private val nodeRepository: NodeRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, serviceRepository) {
|
||||
|
||||
var mapStyleId: Int
|
||||
|
|
@ -39,4 +46,16 @@ constructor(
|
|||
set(value) {
|
||||
mapPrefs.mapStyle = value
|
||||
}
|
||||
|
||||
val localConfig =
|
||||
radioConfigRepository.localConfigFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000L),
|
||||
LocalConfig.getDefaultInstance(),
|
||||
)
|
||||
|
||||
val config
|
||||
get() = localConfig.value
|
||||
|
||||
fun getUser(userId: String?) = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ import com.geeksville.mesh.MeshProtos.Waypoint
|
|||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.BuildUtils.warn
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.map.components.ClusterItemsListDialog
|
||||
import com.geeksville.mesh.ui.map.components.CustomMapLayersSheet
|
||||
import com.geeksville.mesh.ui.map.components.CustomTileProviderManagerSheet
|
||||
|
|
@ -178,7 +177,6 @@ private fun filterNodeTrack(nodeTrack: List<Position>?): List<Position> {
|
|||
@OptIn(MapsComposeExperimentalApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun MapView(
|
||||
uiViewModel: UIViewModel,
|
||||
mapViewModel: MapViewModel = hiltViewModel(),
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
focusedNodeNum: Int? = null,
|
||||
|
|
@ -208,7 +206,7 @@ fun MapView(
|
|||
|
||||
var mapFilterMenuExpanded by remember { mutableStateOf(false) }
|
||||
val mapFilterState by mapViewModel.mapFilterStateFlow.collectAsStateWithLifecycle()
|
||||
val ourNodeInfo by uiViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val ourNodeInfo by mapViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
var editingWaypoint by remember { mutableStateOf<Waypoint?>(null) }
|
||||
|
||||
val selectedGoogleMapType by mapViewModel.selectedGoogleMapType.collectAsStateWithLifecycle()
|
||||
|
|
@ -319,8 +317,8 @@ fun MapView(
|
|||
nodeSnippet = "${node.user.longName}",
|
||||
)
|
||||
}
|
||||
val isConnected by uiViewModel.isConnectedStateFlow.collectAsStateWithLifecycle()
|
||||
val theme by uiViewModel.theme.collectAsStateWithLifecycle()
|
||||
val isConnected by mapViewModel.isConnected.collectAsStateWithLifecycle()
|
||||
val theme by mapViewModel.theme.collectAsStateWithLifecycle()
|
||||
val dark =
|
||||
when (theme) {
|
||||
AppCompatDelegate.MODE_NIGHT_YES -> true
|
||||
|
|
@ -531,7 +529,7 @@ fun MapView(
|
|||
WaypointMarkers(
|
||||
displayableWaypoints = displayableWaypoints,
|
||||
mapFilterState = mapFilterState,
|
||||
myNodeNum = uiViewModel.myNodeNum ?: 0,
|
||||
myNodeNum = mapViewModel.myNodeNum ?: 0,
|
||||
isConnected = isConnected,
|
||||
unicodeEmojiToBitmapProvider = ::unicodeEmojiToBitmap,
|
||||
onEditWaypointRequest = { waypointToEdit -> editingWaypoint = waypointToEdit },
|
||||
|
|
@ -577,21 +575,21 @@ fun MapView(
|
|||
onSendClicked = { updatedWp ->
|
||||
var finalWp = updatedWp
|
||||
if (updatedWp.id == 0) {
|
||||
finalWp = finalWp.copy { id = uiViewModel.generatePacketId() ?: 0 }
|
||||
finalWp = finalWp.copy { id = mapViewModel.generatePacketId() ?: 0 }
|
||||
}
|
||||
if (updatedWp.icon == 0) {
|
||||
finalWp = finalWp.copy { icon = 0x1F4CD }
|
||||
}
|
||||
|
||||
uiViewModel.sendWaypoint(finalWp)
|
||||
mapViewModel.sendWaypoint(finalWp)
|
||||
editingWaypoint = null
|
||||
},
|
||||
onDeleteClicked = { wpToDelete ->
|
||||
if (wpToDelete.lockedTo == 0 && isConnected && wpToDelete.id != 0) {
|
||||
val deleteMarkerWp = wpToDelete.copy { expire = 1 }
|
||||
uiViewModel.sendWaypoint(deleteMarkerWp)
|
||||
mapViewModel.sendWaypoint(deleteMarkerWp)
|
||||
}
|
||||
uiViewModel.deleteWaypoint(wpToDelete.id)
|
||||
mapViewModel.deleteWaypoint(wpToDelete.id)
|
||||
editingWaypoint = null
|
||||
},
|
||||
onDismissRequest = { editingWaypoint = null },
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import kotlinx.serialization.Serializable
|
|||
import org.json.JSONObject
|
||||
import org.meshtastic.core.data.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.data.repository.CustomTileProviderRepository
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.prefs.map.GoogleMapsPrefs
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import timber.log.Timber
|
||||
|
|
@ -88,8 +89,11 @@ constructor(
|
|||
radioConfigRepository: RadioConfigRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
private val customTileProviderRepository: CustomTileProviderRepository,
|
||||
uiPreferencesDataSource: UiPreferencesDataSource,
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, serviceRepository) {
|
||||
|
||||
val theme: StateFlow<Int> = uiPreferencesDataSource.theme
|
||||
|
||||
private val _errorFlow = MutableSharedFlow<String>()
|
||||
val errorFlow: SharedFlow<String> = _errorFlow.asSharedFlow()
|
||||
|
||||
|
|
|
|||
|
|
@ -61,12 +61,7 @@ fun NodeMapScreen(
|
|||
},
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
MapView(
|
||||
uiViewModel = uiViewModel,
|
||||
focusedNodeNum = destNum,
|
||||
nodeTrack = positions,
|
||||
navigateToNodeDetails = {},
|
||||
)
|
||||
MapView(focusedNodeNum = destNum, nodeTrack = positions, navigateToNodeDetails = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -386,32 +386,6 @@ constructor(
|
|||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
fun generatePacketId(): Int? {
|
||||
return try {
|
||||
meshService?.packetId
|
||||
} catch (ex: RemoteException) {
|
||||
errormsg("RemoteException: ${ex.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun sendWaypoint(wpt: MeshProtos.Waypoint, contactKey: String = "0${DataPacket.ID_BROADCAST}") {
|
||||
// contactKey: unique contact key filter (channel)+(nodeId)
|
||||
val channel = contactKey[0].digitToIntOrNull()
|
||||
val dest = if (channel != null) contactKey.substring(1) else contactKey
|
||||
|
||||
val p = DataPacket(dest, channel ?: 0, wpt)
|
||||
if (wpt.id != 0) sendDataPacket(p)
|
||||
}
|
||||
|
||||
private fun sendDataPacket(p: DataPacket) {
|
||||
try {
|
||||
meshService?.send(p)
|
||||
} catch (ex: RemoteException) {
|
||||
errormsg("Send DataPacket error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private val _sharedContactRequested: MutableStateFlow<AdminProtos.SharedContact?> = MutableStateFlow(null)
|
||||
val sharedContactRequested: StateFlow<AdminProtos.SharedContact?>
|
||||
get() = _sharedContactRequested.asStateFlow()
|
||||
|
|
@ -426,8 +400,6 @@ constructor(
|
|||
fun deleteContacts(contacts: List<String>) =
|
||||
viewModelScope.launch(Dispatchers.IO) { packetRepository.deleteContacts(contacts) }
|
||||
|
||||
fun deleteWaypoint(id: Int) = viewModelScope.launch(Dispatchers.IO) { packetRepository.deleteWaypoint(id) }
|
||||
|
||||
// Connection state to our radio device
|
||||
val connectionState
|
||||
get() = serviceRepository.connectionState
|
||||
|
|
@ -470,9 +442,6 @@ constructor(
|
|||
val isManaged: Boolean
|
||||
get() = config.device.isManaged || config.security.isManaged
|
||||
|
||||
val myNodeNum
|
||||
get() = myNodeInfo.value?.myNodeNum
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
debug("ViewModel cleared")
|
||||
|
|
|
|||
|
|
@ -21,16 +21,14 @@ import androidx.navigation.NavGraphBuilder
|
|||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.map.MapScreen
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.navigation.MapRoutes
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
|
||||
fun NavGraphBuilder.mapGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
fun NavGraphBuilder.mapGraph(navController: NavHostController) {
|
||||
composable<MapRoutes.Map>(deepLinks = listOf(navDeepLink<MapRoutes.Map>(basePath = "$DEEP_LINK_BASE_URI/map"))) {
|
||||
MapScreen(
|
||||
uiViewModel = uiViewModel,
|
||||
onClickNodeChip = {
|
||||
navController.navigate(NodesRoutes.NodeDetailGraph(it)) {
|
||||
launchSingleTop = true
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode
|
|||
) {
|
||||
contactsGraph(navController, uiViewModel = uIViewModel)
|
||||
nodesGraph(navController, uiViewModel = uIViewModel)
|
||||
mapGraph(navController, uiViewModel = uIViewModel)
|
||||
mapGraph(navController)
|
||||
channelsGraph(navController, uiViewModel = uIViewModel)
|
||||
connectionsGraph(navController)
|
||||
settingsGraph(navController)
|
||||
|
|
|
|||
|
|
@ -17,11 +17,14 @@
|
|||
|
||||
package com.geeksville.mesh.ui.map
|
||||
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.database.NodeRepository
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.service.ServiceRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -29,18 +32,26 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
abstract class BaseMapViewModel(
|
||||
protected val mapPrefs: MapPrefs,
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
val myNodeInfo = nodeRepository.myNodeInfo
|
||||
|
||||
val myNodeNum
|
||||
get() = myNodeInfo.value?.myNodeNum
|
||||
|
||||
val nodes: StateFlow<List<Node>> =
|
||||
nodeRepository
|
||||
.getNodes()
|
||||
|
|
@ -94,6 +105,34 @@ abstract class BaseMapViewModel(
|
|||
showPrecisionCircleOnMap.value = !current
|
||||
}
|
||||
|
||||
fun generatePacketId(): Int? {
|
||||
return try {
|
||||
serviceRepository.meshService?.packetId
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("RemoteException: ${ex.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteWaypoint(id: Int) = viewModelScope.launch(Dispatchers.IO) { packetRepository.deleteWaypoint(id) }
|
||||
|
||||
fun sendWaypoint(wpt: MeshProtos.Waypoint, contactKey: String = "0${DataPacket.ID_BROADCAST}") {
|
||||
// contactKey: unique contact key filter (channel)+(nodeId)
|
||||
val channel = contactKey[0].digitToIntOrNull()
|
||||
val dest = if (channel != null) contactKey.substring(1) else contactKey
|
||||
|
||||
val p = DataPacket(dest, channel ?: 0, wpt)
|
||||
if (wpt.id != 0) sendDataPacket(p)
|
||||
}
|
||||
|
||||
private fun sendDataPacket(p: DataPacket) {
|
||||
try {
|
||||
serviceRepository.meshService?.send(p)
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Send DataPacket error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
data class MapFilterState(val onlyFavorites: Boolean, val showWaypoints: Boolean, val showPrecisionCircle: Boolean)
|
||||
|
||||
val mapFilterStateFlow: StateFlow<MapFilterState> =
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.strings.R
|
||||
|
|
@ -35,7 +34,6 @@ import org.meshtastic.core.strings.R
|
|||
fun MapScreen(
|
||||
onClickNodeChip: (Int) -> Unit,
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
uiViewModel: UIViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
mapViewModel: MapViewModel = hiltViewModel(),
|
||||
) {
|
||||
|
|
@ -64,11 +62,7 @@ fun MapScreen(
|
|||
},
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
MapView(
|
||||
uiViewModel = uiViewModel,
|
||||
mapViewModel = mapViewModel,
|
||||
navigateToNodeDetails = navigateToNodeDetails,
|
||||
)
|
||||
MapView(mapViewModel = mapViewModel, navigateToNodeDetails = navigateToNodeDetails)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue