Refactor map layer management and navigation infrastructure (#4921)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-25 19:29:24 -05:00 committed by GitHub
parent b608a04ca4
commit a005231d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 5408 additions and 3090 deletions

View file

@ -89,6 +89,7 @@ import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.database.entity.FirmwareReleaseType
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.UiText
import org.meshtastic.core.resources.back
import org.meshtastic.core.resources.cancel
import org.meshtastic.core.resources.chirpy
@ -713,7 +714,11 @@ private fun ProgressContent(
Spacer(Modifier.height(24.dp))
Text(progressState.message, style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center)
Text(
progressState.message.asString(),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
)
val details = progressState.details
if (details != null) {
@ -829,7 +834,7 @@ private fun VerificationFailedState(onRetry: () -> Unit, onIgnore: () -> Unit) {
}
@Composable
private fun ErrorState(error: String, onRetry: () -> Unit) {
private fun ErrorState(error: UiText, onRetry: () -> Unit) {
Icon(
MeshtasticIcons.Dangerous,
contentDescription = null,
@ -838,7 +843,7 @@ private fun ErrorState(error: String, onRetry: () -> Unit) {
)
Spacer(Modifier.height(24.dp))
Text(
stringResource(Res.string.firmware_update_error, error),
stringResource(Res.string.firmware_update_error, error.asString()),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,

View file

@ -36,6 +36,7 @@ import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.UiText
import org.meshtastic.core.resources.firmware_update_downloading_percent
import org.meshtastic.core.resources.firmware_update_nordic_failed
import org.meshtastic.core.resources.firmware_update_not_found_in_release
@ -68,7 +69,11 @@ class NordicDfuHandler(
.replace(Regex(":?\\s*%1\\\$d%?"), "")
.trim()
updateState(FirmwareUpdateState.Downloading(ProgressState(message = downloadingMsg, progress = 0f)))
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = UiText.DynamicString(downloadingMsg), progress = 0f),
),
)
if (firmwareUri != null) {
initiateDfu(target, hardware, firmwareUri, updateState)
@ -79,14 +84,18 @@ class NordicDfuHandler(
val percent = (progress * PERCENT_MAX).toInt()
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = downloadingMsg, progress = progress, details = "$percent%"),
ProgressState(
message = UiText.DynamicString(downloadingMsg),
progress = progress,
details = "$percent%",
),
),
)
}
if (firmwareFile == null) {
val errorMsg = getString(Res.string.firmware_update_not_found_in_release, hardware.displayName)
updateState(FirmwareUpdateState.Error(errorMsg))
updateState(FirmwareUpdateState.Error(UiText.DynamicString(errorMsg)))
null
} else {
initiateDfu(target, hardware, CommonUri.parse("file://$firmwareFile"), updateState)
@ -98,7 +107,7 @@ class NordicDfuHandler(
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
Logger.e(e) { "Nordic DFU Update failed" }
val errorMsg = getString(Res.string.firmware_update_nordic_failed)
updateState(FirmwareUpdateState.Error(e.message ?: errorMsg))
updateState(FirmwareUpdateState.Error(UiText.DynamicString(e.message ?: errorMsg)))
null
}
@ -108,8 +117,9 @@ class NordicDfuHandler(
firmwareUri: CommonUri,
updateState: (FirmwareUpdateState) -> Unit,
) {
val startingMsg = getString(Res.string.firmware_update_starting_service)
updateState(FirmwareUpdateState.Processing(ProgressState(startingMsg)))
updateState(
FirmwareUpdateState.Processing(ProgressState(UiText.Resource(Res.string.firmware_update_starting_service))),
)
// n = Nordic (Legacy prefix handling in mesh service)
radioController.setDeviceAddress("n")

View file

@ -27,6 +27,7 @@ import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.UiText
import org.meshtastic.core.resources.firmware_update_downloading_percent
import org.meshtastic.core.resources.firmware_update_rebooting
import org.meshtastic.core.resources.firmware_update_retrieval_failed
@ -56,11 +57,14 @@ class UsbUpdateHandler(
.replace(Regex(":?\\s*%1\\\$d%?"), "")
.trim()
updateState(FirmwareUpdateState.Downloading(ProgressState(message = downloadingMsg, progress = 0f)))
val rebootingMsg = getString(Res.string.firmware_update_rebooting)
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = UiText.DynamicString(downloadingMsg), progress = 0f),
),
)
if (firmwareUri != null) {
val rebootingMsg = UiText.Resource(Res.string.firmware_update_rebooting)
updateState(FirmwareUpdateState.Processing(ProgressState(rebootingMsg)))
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum ?: 0
radioController.rebootToDfu(myNodeNum)
@ -74,22 +78,28 @@ class UsbUpdateHandler(
val percent = (progress * PERCENT_MAX).toInt()
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = downloadingMsg, progress = progress, details = "$percent%"),
ProgressState(
message = UiText.DynamicString(downloadingMsg),
progress = progress,
details = "$percent%",
),
),
)
}
if (firmwareFile == null) {
val retrievalFailedMsg = getString(Res.string.firmware_update_retrieval_failed)
updateState(FirmwareUpdateState.Error(retrievalFailedMsg))
updateState(FirmwareUpdateState.Error(UiText.DynamicString(retrievalFailedMsg)))
null
} else {
val rebootingMsg = UiText.Resource(Res.string.firmware_update_rebooting)
updateState(FirmwareUpdateState.Processing(ProgressState(rebootingMsg)))
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum ?: 0
radioController.rebootToDfu(myNodeNum)
delay(REBOOT_DELAY)
updateState(FirmwareUpdateState.AwaitingFileSave(firmwareFile, java.io.File(firmwareFile).name))
val fileName = java.io.File(firmwareFile).name
updateState(FirmwareUpdateState.AwaitingFileSave(firmwareFile, fileName))
firmwareFile
}
}
@ -98,7 +108,7 @@ class UsbUpdateHandler(
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
Logger.e(e) { "USB Update failed" }
val usbFailedMsg = getString(Res.string.firmware_update_usb_failed)
updateState(FirmwareUpdateState.Error(e.message ?: usbFailedMsg))
updateState(FirmwareUpdateState.Error(UiText.DynamicString(e.message ?: usbFailedMsg)))
null
}
}

View file

@ -36,6 +36,7 @@ import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.UiText
import org.meshtastic.core.resources.firmware_update_connecting_attempt
import org.meshtastic.core.resources.firmware_update_downloading_percent
import org.meshtastic.core.resources.firmware_update_erasing
@ -163,18 +164,19 @@ class Esp32OtaUpdateHandler(
throw e
} catch (e: OtaProtocolException.HashRejected) {
Logger.e(e) { "ESP32 OTA: Hash rejected by device" }
val msg = getString(Res.string.firmware_update_hash_rejected)
updateState(FirmwareUpdateState.Error(msg))
updateState(FirmwareUpdateState.Error(UiText.Resource(Res.string.firmware_update_hash_rejected)))
null
} catch (e: OtaProtocolException) {
Logger.e(e) { "ESP32 OTA: Protocol error" }
val msg = getString(Res.string.firmware_update_ota_failed, e.message ?: "")
updateState(FirmwareUpdateState.Error(msg))
updateState(
FirmwareUpdateState.Error(UiText.Resource(Res.string.firmware_update_ota_failed, e.message ?: "")),
)
null
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
Logger.e(e) { "ESP32 OTA: Unexpected error" }
val msg = getString(Res.string.firmware_update_ota_failed, e.message ?: "")
updateState(FirmwareUpdateState.Error(msg))
updateState(
FirmwareUpdateState.Error(UiText.Resource(Res.string.firmware_update_ota_failed, e.message ?: "")),
)
null
}
@ -186,12 +188,20 @@ class Esp32OtaUpdateHandler(
): String? {
val downloadingMsg =
getString(Res.string.firmware_update_downloading_percent, 0).replace(Regex(":?\\s*%1\\\$d%?"), "").trim()
updateState(FirmwareUpdateState.Downloading(ProgressState(message = downloadingMsg, progress = 0f)))
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = UiText.DynamicString(downloadingMsg), progress = 0f),
),
)
return firmwareRetriever.retrieveEsp32Firmware(release, hardware) { progress ->
val percent = (progress * PERCENT_MAX).toInt()
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = downloadingMsg, progress = progress, details = "$percent%"),
ProgressState(
message = UiText.DynamicString(downloadingMsg),
progress = progress,
details = "$percent%",
),
),
)
}
@ -234,11 +244,18 @@ class Esp32OtaUpdateHandler(
val downloadingMsg =
getString(Res.string.firmware_update_downloading_percent, 0).replace(Regex(":?\\s*%1\\\$d%?"), "").trim()
updateState(FirmwareUpdateState.Downloading(ProgressState(message = downloadingMsg, progress = 0f)))
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = UiText.DynamicString(downloadingMsg), progress = 0f),
),
)
return if (firmwareUri != null) {
val extractingMsg = getString(Res.string.firmware_update_extracting)
updateState(FirmwareUpdateState.Processing(ProgressState(message = extractingMsg)))
updateState(
FirmwareUpdateState.Processing(
ProgressState(message = UiText.Resource(Res.string.firmware_update_extracting)),
),
)
getFirmwareFromUri(firmwareUri)
} else {
val firmwareFile =
@ -246,14 +263,21 @@ class Esp32OtaUpdateHandler(
val percent = (progress * PERCENT_MAX).toInt()
updateState(
FirmwareUpdateState.Downloading(
ProgressState(message = downloadingMsg, progress = progress, details = "$percent%"),
ProgressState(
message = UiText.DynamicString(downloadingMsg),
progress = progress,
details = "$percent%",
),
),
)
}
if (firmwareFile == null) {
val errorMsg = getString(Res.string.firmware_update_not_found_in_release, hardware.displayName)
updateState(FirmwareUpdateState.Error(errorMsg))
updateState(
FirmwareUpdateState.Error(
UiText.Resource(Res.string.firmware_update_not_found_in_release, hardware.displayName),
),
)
null
} else {
firmwareFile
@ -267,13 +291,17 @@ class Esp32OtaUpdateHandler(
updateState: (FirmwareUpdateState) -> Unit,
): Boolean {
// Show "waiting for reboot" state before first connection attempt
val waitingMsg = getString(Res.string.firmware_update_waiting_reboot)
updateState(FirmwareUpdateState.Processing(ProgressState(waitingMsg)))
updateState(
FirmwareUpdateState.Processing(ProgressState(UiText.Resource(Res.string.firmware_update_waiting_reboot))),
)
for (i in 1..attempts) {
try {
val connectingMsg = getString(Res.string.firmware_update_connecting_attempt, i, attempts)
updateState(FirmwareUpdateState.Processing(ProgressState(connectingMsg)))
updateState(
FirmwareUpdateState.Processing(
ProgressState(UiText.Resource(Res.string.firmware_update_connecting_attempt, i, attempts)),
),
)
transport.connect().getOrThrow()
return true
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
@ -294,21 +322,25 @@ class Esp32OtaUpdateHandler(
) {
val file = java.io.File(firmwareFile)
// Step 5: Start OTA
val startingOtaMsg = getString(Res.string.firmware_update_starting_ota)
updateState(FirmwareUpdateState.Processing(ProgressState(startingOtaMsg)))
updateState(
FirmwareUpdateState.Processing(ProgressState(UiText.Resource(Res.string.firmware_update_starting_ota))),
)
transport
.startOta(sizeBytes = file.length(), sha256Hash = sha256Hash) { status ->
when (status) {
OtaHandshakeStatus.Erasing -> {
val erasingMsg = getString(Res.string.firmware_update_erasing)
updateState(FirmwareUpdateState.Processing(ProgressState(erasingMsg)))
updateState(
FirmwareUpdateState.Processing(
ProgressState(UiText.Resource(Res.string.firmware_update_erasing)),
),
)
}
}
}
.getOrThrow()
// Step 6: Stream
val uploadingMsg = getString(Res.string.firmware_update_uploading)
val uploadingMsg = UiText.Resource(Res.string.firmware_update_uploading)
updateState(FirmwareUpdateState.Updating(ProgressState(uploadingMsg, 0f)))
val firmwareData = file.readBytes()
val chunkSize =