feat(wire): migrate from protobuf -> wire (#4401)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-03 18:01:12 -06:00 committed by GitHub
parent 9dbc8b7fbf
commit 25657e8f8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
239 changed files with 7149 additions and 6144 deletions

View file

@ -133,10 +133,8 @@ import org.meshtastic.feature.map.component.MapButton
import org.meshtastic.feature.map.model.CustomTileSource
import org.meshtastic.feature.map.model.MarkerWithLabel
import org.meshtastic.feature.map.model.TracerouteOverlay
import org.meshtastic.proto.MeshProtos.Position
import org.meshtastic.proto.MeshProtos.Waypoint
import org.meshtastic.proto.copy
import org.meshtastic.proto.waypoint
import org.meshtastic.proto.Position
import org.meshtastic.proto.Waypoint
import org.osmdroid.bonuspack.utils.BonusPackHelper.getBitmapFromVectorDrawable
import org.osmdroid.config.Configuration
import org.osmdroid.events.MapEventsReceiver
@ -325,7 +323,7 @@ fun MapView(
LaunchedEffect(selectedWaypointId, waypoints) {
if (selectedWaypointId != null && waypoints.containsKey(selectedWaypointId)) {
waypoints[selectedWaypointId]?.data?.waypoint?.let { pt ->
val geoPoint = GeoPoint(pt.latitudeI * 1e-7, pt.longitudeI * 1e-7)
val geoPoint = GeoPoint((pt.latitude_i ?: 0) * 1e-7, (pt.longitude_i ?: 0) * 1e-7)
map.controller.setCenter(geoPoint)
map.controller.setZoom(WAYPOINT_ZOOM)
}
@ -396,7 +394,8 @@ fun MapView(
fun MapView.onNodesChanged(nodes: Collection<Node>): List<MarkerWithLabel> {
val nodesWithPosition = nodes.filter { it.validPosition != null }
val ourNode = mapViewModel.ourNodeInfo.value
val displayUnits = mapViewModel.config.display.units
val displayUnits =
mapViewModel.config.display?.units ?: org.meshtastic.proto.Config.DisplayConfig.DisplayUnits.METRIC
val mapFilterStateValue = mapViewModel.mapFilterStateFlow.value // Access mapFilterState directly
return nodesWithPosition.mapNotNull { node ->
if (
@ -410,9 +409,9 @@ fun MapView(
val (p, u) = node.position to node.user
val nodePosition = GeoPoint(node.latitude, node.longitude)
MarkerWithLabel(mapView = this, label = "${u.shortName} ${formatAgo(p.time)}").apply {
MarkerWithLabel(mapView = this, label = "${u.short_name} ${formatAgo(p.time)}").apply {
id = u.id
title = u.longName
title = u.long_name
snippet =
com.meshtastic.core.strings.getString(
Res.string.map_node_popup_details,
@ -436,7 +435,7 @@ fun MapView(
if (!mapFilterStateValue.showPrecisionCircle) {
setPrecisionBits(0)
} else {
setPrecisionBits(p.precisionBits)
setPrecisionBits(p.precision_bits ?: 0)
}
setOnLongClickListener {
navigateToNodeDetails(node.num)
@ -456,10 +455,10 @@ fun MapView(
Logger.d { "User deleted waypoint ${waypoint.id} for me" }
mapViewModel.deleteWaypoint(waypoint.id)
}
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
if ((waypoint.locked_to ?: 0) in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
builder.setPositiveButton(com.meshtastic.core.strings.getString(Res.string.delete_for_everyone)) { _, _ ->
Logger.d { "User deleted waypoint ${waypoint.id} for everyone" }
mapViewModel.sendWaypoint(waypoint.copy { expire = 1 })
mapViewModel.sendWaypoint(waypoint.copy(expire = 1))
mapViewModel.deleteWaypoint(waypoint.id)
}
}
@ -484,7 +483,7 @@ fun MapView(
Logger.d { "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, mapViewModel.myNodeNum ?: 0) && isConnected) {
if ((waypoint.locked_to ?: 0) in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
showEditWaypointDialog = waypoint
} else {
showDeleteMarkerDialog(waypoint)
@ -494,7 +493,7 @@ fun MapView(
fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL || (myId != null && id == myId)) {
com.meshtastic.core.strings.getString(Res.string.you)
} else {
mapViewModel.getUser(id).longName
mapViewModel.getUser(id).long_name
}
@Suppress("MagicNumber")
@ -502,20 +501,20 @@ fun MapView(
return waypoints.mapNotNull { waypoint ->
val pt = waypoint.data.waypoint ?: return@mapNotNull null
if (!mapFilterState.showWaypoints) return@mapNotNull null // Use collected mapFilterState
val lock = if (pt.lockedTo != 0) "\uD83D\uDD12" else ""
val lock = if ((pt.locked_to ?: 0) != 0) "\uD83D\uDD12" else ""
val time =
DateUtils.formatDateTime(
context,
waypoint.received_time,
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_ALL,
)
val label = pt.name + " " + formatAgo((waypoint.received_time / 1000).toInt())
val emoji = String(Character.toChars(if (pt.icon == 0) 128205 else pt.icon))
val label = (pt.name ?: "") + " " + formatAgo((waypoint.received_time / 1000).toInt())
val emoji = String(Character.toChars(if ((pt.icon ?: 0) == 0) 128205 else pt.icon!!))
val now = System.currentTimeMillis()
val expireTimeMillis = pt.expire * 1000L
val expireTimeMillis = (pt.expire ?: 0) * 1000L
val expireTimeStr =
when {
pt.expire == 0 || pt.expire == Int.MAX_VALUE -> "Never"
(pt.expire ?: 0) == 0 || pt.expire == Int.MAX_VALUE -> "Never"
expireTimeMillis <= now -> "Expired"
else ->
DateUtils.getRelativeTimeSpanString(
@ -533,7 +532,7 @@ fun MapView(
"[$time] ${pt.description} " +
com.meshtastic.core.strings.getString(Res.string.expires) +
": $expireTimeStr"
position = GeoPoint(pt.latitudeI * 1e-7, pt.longitudeI * 1e-7)
position = GeoPoint((pt.latitude_i ?: 0) * 1e-7, (pt.longitude_i ?: 0) * 1e-7)
if (selectedWaypointId == pt.id) {
showInfoWindow()
}
@ -557,10 +556,8 @@ fun MapView(
val enabled = isConnected && downloadRegionBoundingBox == null
if (enabled) {
showEditWaypointDialog = waypoint {
latitudeI = (p.latitude * 1e7).toInt()
longitudeI = (p.longitude * 1e7).toInt()
}
showEditWaypointDialog =
Waypoint(latitude_i = (p.latitude * 1e7).toInt(), longitude_i = (p.longitude * 1e7).toInt())
}
return true
}
@ -895,14 +892,22 @@ fun MapView(
onSendClicked = { waypoint ->
Logger.d { "User clicked send waypoint ${waypoint.id}" }
showEditWaypointDialog = null
val newId =
if (waypoint.id == 0) mapViewModel.generatePacketId() ?: return@EditWaypointDialog else waypoint.id
val newName = if (waypoint.name.isNullOrEmpty()) "Dropped Pin" else waypoint.name
val newExpire = if ((waypoint.expire ?: 0) == 0) Int.MAX_VALUE else (waypoint.expire ?: Int.MAX_VALUE)
val newLockedTo = if ((waypoint.locked_to ?: 0) != 0) mapViewModel.myNodeNum ?: 0 else 0
val newIcon = if ((waypoint.icon ?: 0) == 0) 128205 else waypoint.icon
mapViewModel.sendWaypoint(
waypoint.copy {
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) mapViewModel.myNodeNum ?: 0 else 0
if (waypoint.icon == 0) icon = 128205
},
waypoint.copy(
id = newId,
name = newName,
expire = newExpire,
locked_to = newLockedTo,
icon = newIcon,
),
)
},
onDeleteClicked = { waypoint ->

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.map
import android.graphics.Color
@ -26,7 +25,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import org.meshtastic.core.ui.R
import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.Position
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay
@ -125,15 +124,15 @@ fun MapView.addPolyline(density: Density, geoPoints: List<GeoPoint>, onClick: ()
return polyline
}
fun MapView.addPositionMarkers(positions: List<MeshProtos.Position>, onClick: () -> Unit): List<Marker> {
fun MapView.addPositionMarkers(positions: List<Position>, onClick: () -> Unit): List<Marker> {
val navIcon = ContextCompat.getDrawable(context, R.drawable.ic_map_navigation_24)
val markers =
positions.map {
Marker(this).apply {
icon = navIcon
rotation = (it.groundTrack * 1e-5).toFloat()
rotation = ((it.ground_track ?: 0) * 1e-5).toFloat()
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
position = GeoPoint(it.latitudeI * 1e-7, it.longitudeI * 1e-7)
position = GeoPoint((it.latitude_i ?: 0) * 1e-7, (it.longitude_i ?: 0) * 1e-7)
setOnMarkerClickListener { _, _ ->
onClick()
true

View file

@ -31,7 +31,7 @@ import org.meshtastic.core.navigation.MapRoutes
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
import org.meshtastic.proto.LocalConfig
import javax.inject.Inject
@Suppress("LongParameterList")
@ -57,8 +57,7 @@ constructor(
mapPrefs.mapStyle = value
}
val localConfig =
radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig.getDefaultInstance())
val localConfig = radioConfigRepository.localConfigFlow.stateInWhileSubscribed(initialValue = LocalConfig())
val config
get() = localConfig.value

View file

@ -76,9 +76,7 @@ import org.meshtastic.core.strings.waypoint_new
import org.meshtastic.core.ui.component.EditTextPreference
import org.meshtastic.core.ui.emoji.EmojiPickerDialog
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.MeshProtos.Waypoint
import org.meshtastic.proto.copy
import org.meshtastic.proto.waypoint
import org.meshtastic.proto.Waypoint
import java.util.Calendar
@Suppress("LongMethod", "CyclomaticComplexMethod")
@ -95,15 +93,16 @@ fun EditWaypointDialog(
val title = if (waypoint.id == 0) Res.string.waypoint_new else Res.string.waypoint_edit
@Suppress("MagicNumber")
val emoji = if (waypointInput.icon == 0) 128205 else waypointInput.icon
val emoji = if ((waypointInput.icon ?: 0) == 0) 128205 else waypointInput.icon!!
var showEmojiPickerView by remember { mutableStateOf(false) }
// Get current context for dialogs
val context = LocalContext.current
val calendar = remember {
Calendar.getInstance().apply {
if (waypoint.expire != 0 && waypoint.expire != Int.MAX_VALUE) {
timeInMillis = waypoint.expire * 1000L
val expire = waypoint.expire ?: 0
if (expire != 0 && expire != Int.MAX_VALUE) {
timeInMillis = expire * 1000L
} else {
timeInMillis = System.currentTimeMillis()
@Suppress("MagicNumber")
@ -121,7 +120,7 @@ fun EditWaypointDialog(
// State to hold selected date and time
var selectedDate by remember {
mutableStateOf(
if (waypointInput.expire != 0 && waypointInput.expire != Int.MAX_VALUE) {
if ((waypointInput.expire ?: 0) != 0 && waypointInput.expire != Int.MAX_VALUE) {
dateFormat.format(calendar.time)
} else {
""
@ -130,7 +129,7 @@ fun EditWaypointDialog(
}
var selectedTime by remember {
mutableStateOf(
if (waypointInput.expire != 0 && waypointInput.expire != Int.MAX_VALUE) {
if ((waypointInput.expire ?: 0) != 0 && waypointInput.expire != Int.MAX_VALUE) {
timeFormat.format(calendar.time)
} else {
""
@ -139,8 +138,8 @@ fun EditWaypointDialog(
}
var epochTime by remember {
mutableStateOf<Long?>(
if (waypointInput.expire != 0 && waypointInput.expire != Int.MAX_VALUE) {
waypointInput.expire * 1000L
if ((waypointInput.expire ?: 0) != 0 && waypointInput.expire != Int.MAX_VALUE) {
(waypointInput.expire ?: 0) * 1000L
} else {
null
},
@ -164,14 +163,14 @@ fun EditWaypointDialog(
)
EditTextPreference(
title = stringResource(Res.string.name),
value = waypointInput.name,
value = waypointInput.name ?: "",
maxSize = 29,
enabled = true,
isError = false,
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {}),
onValueChanged = { waypointInput = waypointInput.copy { name = it } },
onValueChanged = { waypointInput = waypointInput.copy(name = it) },
trailingIcon = {
IconButton(onClick = { showEmojiPickerView = true }) {
Text(
@ -187,14 +186,14 @@ fun EditWaypointDialog(
)
EditTextPreference(
title = stringResource(Res.string.description),
value = waypointInput.description,
value = waypointInput.description ?: "",
maxSize = 99,
enabled = true,
isError = false,
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {}),
onValueChanged = { waypointInput = waypointInput.copy { description = it } },
onValueChanged = { waypointInput = waypointInput.copy(description = it) },
)
Row(
modifier = Modifier.fillMaxWidth().size(48.dp),
@ -204,8 +203,8 @@ fun EditWaypointDialog(
Text(stringResource(Res.string.locked))
Switch(
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End),
checked = waypointInput.lockedTo != 0,
onCheckedChange = { waypointInput = waypointInput.copy { lockedTo = if (it) 1 else 0 } },
checked = (waypointInput.locked_to ?: 0) != 0,
onCheckedChange = { waypointInput = waypointInput.copy(locked_to = if (it) 1 else 0) },
)
}
val datePickerDialog =
@ -229,7 +228,7 @@ fun EditWaypointDialog(
calendar.set(Calendar.MINUTE, selectedMinute)
epochTime = calendar.timeInMillis
selectedTime = timeFormat.format(calendar.time)
waypointInput = waypointInput.copy { expire = (calendar.timeInMillis / 1000).toInt() }
waypointInput = waypointInput.copy(expire = (calendar.timeInMillis / 1000).toInt())
},
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
@ -247,7 +246,7 @@ fun EditWaypointDialog(
Text(stringResource(Res.string.expires))
Switch(
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End),
checked = waypointInput.expire != Int.MAX_VALUE && waypointInput.expire != 0,
checked = waypointInput.expire != Int.MAX_VALUE && (waypointInput.expire ?: 0) != 0,
onCheckedChange = { isChecked ->
if (isChecked) {
// Default to now if not already set
@ -256,18 +255,17 @@ fun EditWaypointDialog(
}
selectedDate = dateFormat.format(calendar.time)
selectedTime = timeFormat.format(calendar.time)
waypointInput =
waypointInput.copy { expire = (calendar.timeInMillis / 1000).toInt() }
waypointInput = waypointInput.copy(expire = (calendar.timeInMillis / 1000).toInt())
} else {
selectedDate = ""
selectedTime = ""
waypointInput = waypointInput.copy { expire = Int.MAX_VALUE }
waypointInput = waypointInput.copy(expire = Int.MAX_VALUE)
}
},
)
}
if (waypointInput.expire != Int.MAX_VALUE && waypointInput.expire != 0) {
if (waypointInput.expire != Int.MAX_VALUE && (waypointInput.expire ?: 0) != 0) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
@ -308,7 +306,7 @@ fun EditWaypointDialog(
Button(
modifier = modifier.weight(1f),
onClick = { onDeleteClicked(waypointInput) },
enabled = waypointInput.name.isNotEmpty(),
enabled = !(waypointInput.name.isNullOrEmpty()),
) {
Text(stringResource(Res.string.delete))
}
@ -322,7 +320,7 @@ fun EditWaypointDialog(
} else {
EmojiPickerDialog(onDismiss = { showEmojiPickerView = false }) {
showEmojiPickerView = false
waypointInput = waypointInput.copy { icon = it.codePointAt(0) }
waypointInput = waypointInput.copy(icon = it.codePointAt(0))
}
}
}
@ -334,13 +332,13 @@ private fun EditWaypointFormPreview() {
AppTheme {
EditWaypointDialog(
waypoint =
waypoint {
id = 123
name = "Test 123"
description = "This is only a test"
icon = 128169
expire = (System.currentTimeMillis() / 1000 + 8 * 3600).toInt()
},
Waypoint(
id = 123,
name = "Test 123",
description = "This is only a test",
icon = 128169,
expire = (System.currentTimeMillis() / 1000 + 8 * 3600).toInt(),
),
onSendClicked = {},
onDeleteClicked = {},
onDismissRequest = {},

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.map.node
import androidx.compose.foundation.layout.fillMaxSize
@ -39,7 +38,7 @@ private const val DEG_D = 1e-7
fun NodeMapScreen(nodeMapViewModel: NodeMapViewModel, onNavigateUp: () -> Unit) {
val density = LocalDensity.current
val positionLogs by nodeMapViewModel.positionLogs.collectAsStateWithLifecycle()
val geoPoints = positionLogs.map { GeoPoint(it.latitudeI * DEG_D, it.longitudeI * DEG_D) }
val geoPoints = positionLogs.map { GeoPoint((it.latitude_i ?: 0) * DEG_D, (it.longitude_i ?: 0) * DEG_D) }
val cameraView = remember { BoundingBox.fromGeoPoints(geoPoints) }
val mapView =
rememberMapViewWithLifecycle(