diff --git a/.skills/compose-ui/strings-index.txt b/.skills/compose-ui/strings-index.txt index f6884c99d5..5a92c0e055 100644 --- a/.skills/compose-ui/strings-index.txt +++ b/.skills/compose-ui/strings-index.txt @@ -698,7 +698,12 @@ map_reporting_interval_seconds map_reporting_summary map_select_download_region map_start_download +map_style_dark +map_style_light +map_style_osm +map_style_road_map map_style_selection +map_style_terrain map_subDescription map_tile_download_estimate map_tile_source @@ -887,6 +892,13 @@ notifications_on_message_receipt now ntp_server number_of_records +### OFFLINE ### +offline_download +offline_download_visible_region +offline_downloaded_regions +offline_maps +offline_saves_tiles +offline_unnamed_region ok_to_mqtt okay oled_type @@ -1419,3 +1431,4 @@ wind_speed you zh_CN zh_TW +zoom_to_fit_all diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 81896d0102..e68e5c6cbd 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -263,7 +263,6 @@ dependencies { debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.glance.preview) - googleImplementation(libs.dd.sdk.android.compose) googleImplementation(libs.dd.sdk.android.logs) googleImplementation(libs.dd.sdk.android.rum) googleImplementation(libs.dd.sdk.android.session.replay) diff --git a/core/resources/src/commonMain/composeResources/values/strings.xml b/core/resources/src/commonMain/composeResources/values/strings.xml index aa8c1fdac8..9ea2aaf178 100644 --- a/core/resources/src/commonMain/composeResources/values/strings.xml +++ b/core/resources/src/commonMain/composeResources/values/strings.xml @@ -728,7 +728,12 @@ Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, long and short name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name. Select download region Start Download + Dark + Light + OpenStreetMap + Road Map Map style selection + Terrain bearing: %1$d° distance: %2$s Tile download estimate: Tile Source @@ -917,6 +922,13 @@ Now NTP server Number of records + + Download + Download visible region + Downloaded Regions + Offline Maps + Saves tiles for offline use + Unnamed Region Ok to MQTT OK OLED type @@ -1248,53 +1260,6 @@ RX Boosted Gain System Settings - Generate QR Code - NFC is disabled. Please enable it in system settings. - All - - Bluetooth - Configure Bluetooth Permissions - Discovery - Find and identify Meshtastic devices near you. - Configuration - Wirelessly manage your device settings and channels. - Map style selection - OpenStreetMap - Light - Terrain - Road Map - Dark - Offline Maps - Download - Download visible region - Saves tiles for offline use - Downloaded Regions - Unnamed Region - - Battery: %1$d% - Nodes: %1$d online / %2$d total - Uptime: %1$s - ChUtil: %1$s% | AirTX: %2$s% - Traffic: TX %1$d / RX %2$d (D: %3$d) - Relays: %1$d (Canceled: %2$d) - Diagnostics: %1$s - Noise %1$d dBm - Bad %1$d - Dropped %1$d - Heap - %1$d / %2$d - %1$s - Powered - Refresh - Updated - - - Add Network Layer - https://example.com/map.kml or .geojson - - Local MBTiles File - Add Local MBTiles File - TAK (ATAK) TAK Configuration Member Role @@ -1457,6 +1422,7 @@ Warning Delete waypoint? Edit waypoint + Lock to my node New waypoint Received waypoint: %1$s Weight diff --git a/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/OfflineManagerFactory.kt b/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/OfflineManagerFactory.kt index 561b056817..26856b139e 100644 --- a/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/OfflineManagerFactory.kt +++ b/feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/OfflineManagerFactory.kt @@ -54,7 +54,7 @@ import org.meshtastic.core.resources.offline_unnamed_region import org.meshtastic.core.ui.icon.CloudDownload import org.meshtastic.core.ui.icon.MeshtasticIcons -@Suppress("LongMethod") +@Suppress("LongMethod", "ModifierMissing") @Composable actual fun OfflineMapContent(styleUri: String, cameraState: CameraState) { val offlineManager = rememberOfflineManager() diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapScreen.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapScreen.kt index 594272bf91..8027020a1b 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapScreen.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapScreen.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025-2026 Meshtastic LLC + * Copyright (c) 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 @@ -72,8 +72,8 @@ private val MAP_OVERLAY_PADDING = 16.dp fun MapScreen( onClickNodeChip: (Int) -> Unit, navigateToNodeDetails: (Int) -> Unit, - modifier: Modifier = Modifier, viewModel: MapViewModel, + modifier: Modifier = Modifier, waypointId: Int? = null, ) { val ourNodeInfo by viewModel.ourNodeInfo.collectAsStateWithLifecycle() @@ -163,7 +163,7 @@ fun MapScreen( modifier = Modifier.fillMaxSize(), gestureOptions = gestureOptions, styleState = styleState, - onCameraMoved = { position -> viewModel.saveCameraPosition(position) }, + onCameraMove = { position -> viewModel.saveCameraPosition(position) }, onWaypointClick = { wpId -> editingWaypointId = wpId longPressPosition = null @@ -235,10 +235,12 @@ fun MapScreen( // TrackBearing → TrackNorth bearingUpdate = BearingUpdate.ALWAYS_NORTH } + BearingUpdate.ALWAYS_NORTH -> { // TrackNorth → Off isLocationTrackingEnabled = false } + BearingUpdate.IGNORE -> { isLocationTrackingEnabled = false } diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt index 5a2b408e99..8578885c4e 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/EditWaypointDialog.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025-2026 Meshtastic LLC + * Copyright (c) 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 diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapFilterDropdown.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapFilterDropdown.kt index 5b8cb59935..015fd47564 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapFilterDropdown.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapFilterDropdown.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025-2026 Meshtastic LLC + * Copyright (c) 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 diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapStyleSelector.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapStyleSelector.kt index a50c9e2a7b..9d3993aab3 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapStyleSelector.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MapStyleSelector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025-2026 Meshtastic LLC + * Copyright (c) 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 diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MaplibreMapContent.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MaplibreMapContent.kt index 63ad11aa72..6a5b022ee3 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MaplibreMapContent.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/MaplibreMapContent.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @@ -121,10 +122,10 @@ fun MaplibreMapContent( modifier: Modifier = Modifier, gestureOptions: GestureOptions = GestureOptions.Standard, styleState: StyleState = rememberStyleState(), - onCameraMoved: (CameraPosition) -> Unit = {}, + onCameraMove: (CameraPosition) -> Unit = {}, onWaypointClick: (Int) -> Unit = {}, - onMapLoadFinished: () -> Unit = {}, - onMapLoadFailed: (String?) -> Unit = {}, + onMapLoad: () -> Unit = {}, + onMapLoadFail: (String?) -> Unit = {}, locationState: UserLocationState? = null, ) { MaplibreMap( @@ -137,8 +138,8 @@ fun MaplibreMapContent( onMapLongClick(position) ClickResult.Consume }, - onMapLoadFinished = onMapLoadFinished, - onMapLoadFailed = onMapLoadFailed, + onMapLoadFinished = onMapLoad, + onMapLoadFailed = onMapLoadFail, ) { // --- Terrain hillshade overlay --- if (showHillshade) { @@ -172,9 +173,10 @@ fun MaplibreMapContent( } // Persist camera position when it stops moving + val currentOnCameraMove = rememberUpdatedState(onCameraMove) LaunchedEffect(cameraState.isCameraMoving) { if (!cameraState.isCameraMoving) { - onCameraMoved(cameraState.position) + currentOnCameraMove.value(cameraState.position) } } } @@ -216,7 +218,7 @@ private fun NodeMarkerLayers( cameraState.animateTo( cameraState.position.copy( target = target, - zoom = cameraState.position.zoom + CLUSTER_ZOOM_INCREMENT, + zoom = minOf(cameraState.position.zoom + CLUSTER_ZOOM_INCREMENT, PRECISION_ZOOM_MAX.toDouble()), ), ) } diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackLayers.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackLayers.kt index fe38673c3c..b07d92a5b9 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackLayers.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackLayers.kt @@ -49,7 +49,7 @@ private const val SELECTED_OPACITY = 0.9f internal fun NodeTrackLayers( positions: List, selectedPositionTime: Int? = null, - onPositionSelected: ((Int) -> Unit)? = null, + onSelectPosition: ((Int) -> Unit)? = null, ) { if (positions.size < 2) return @@ -87,8 +87,8 @@ internal fun NodeTrackLayers( strokeColor = const(Color.White), onClick = { features -> val time = features.firstOrNull()?.properties?.get("time")?.jsonPrimitive?.content?.toIntOrNull() - if (time != null && onPositionSelected != null) { - onPositionSelected(time) + if (time != null && onSelectPosition != null) { + onSelectPosition(time) ClickResult.Consume } else { ClickResult.Pass diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackMap.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackMap.kt index 78091b1fca..619245a9f3 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackMap.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/NodeTrackMap.kt @@ -39,8 +39,8 @@ private const val BOUNDS_PADDING_DP = 48 /** * Embeddable position-track map showing a polyline with markers for the given positions. * - * Supports synchronized selection: [selectedPositionTime] highlights the corresponding marker and [onPositionSelected] - * is called when a marker is tapped, passing the `Position.time` for the host screen to synchronize its card list. + * Supports synchronized selection: [selectedPositionTime] highlights the corresponding marker and [onSelectPosition] is + * called when a marker is tapped, passing the `Position.time` for the host screen to synchronize its card list. * * Replaces both the Google Maps and OSMDroid flavor-specific NodeTrackMap implementations. */ @@ -49,7 +49,7 @@ fun NodeTrackMap( positions: List, modifier: Modifier = Modifier, selectedPositionTime: Int? = null, - onPositionSelected: ((Int) -> Unit)? = null, + onSelectPosition: ((Int) -> Unit)? = null, ) { val geoPositions = remember(positions) { positions.mapNotNull { pos -> toGeoPositionOrNull(pos.latitude_i, pos.longitude_i) } } @@ -82,7 +82,7 @@ fun NodeTrackMap( NodeTrackLayers( positions = positions, selectedPositionTime = selectedPositionTime, - onPositionSelected = onPositionSelected, + onSelectPosition = onSelectPosition, ) } } diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteLayers.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteLayers.kt index 9aa2826dc9..22e88f8b19 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteLayers.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteLayers.kt @@ -19,6 +19,7 @@ package org.meshtastic.feature.map.component import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em @@ -66,7 +67,7 @@ internal fun TracerouteLayers( overlay: TracerouteOverlay?, nodePositions: Map, nodes: Map, - onMappableCountChanged: (shown: Int, total: Int) -> Unit, + onMappableCountChange: (shown: Int, total: Int) -> Unit, ) { if (overlay == null) return @@ -81,7 +82,8 @@ internal fun TracerouteLayers( // Report mappable count via side effect (avoid state updates during composition) val mappableCount = routeData.hopFeatures.features.size val totalCount = overlay.relatedNodeNums.size - LaunchedEffect(mappableCount, totalCount) { onMappableCountChanged(mappableCount, totalCount) } + val currentOnMappableCountChange = rememberUpdatedState(onMappableCountChange) + LaunchedEffect(mappableCount, totalCount) { currentOnMappableCountChange.value(mappableCount, totalCount) } // Forward route line if (routeData.forwardLine.features.isNotEmpty()) { diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteMap.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteMap.kt index 68a93726d7..f1523e757b 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteMap.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/TracerouteMap.kt @@ -53,7 +53,7 @@ private const val BOUNDS_PADDING_DP = 64 fun TracerouteMap( tracerouteOverlay: TracerouteOverlay?, tracerouteNodePositions: Map, - onMappableCountChanged: (shown: Int, total: Int) -> Unit, + onMappableCountChange: (shown: Int, total: Int) -> Unit, modifier: Modifier = Modifier, nodes: Map = emptyMap(), ) { @@ -91,7 +91,7 @@ fun TracerouteMap( overlay = tracerouteOverlay, nodePositions = tracerouteNodePositions, nodes = nodes, - onMappableCountChanged = onMappableCountChanged, + onMappableCountChange = onMappableCountChange, ) } } diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/PositionLogScreens.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/PositionLogScreens.kt index 14061cc769..25ed836208 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/PositionLogScreens.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/PositionLogScreens.kt @@ -67,7 +67,7 @@ fun PositionLogScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) { positions = positions, modifier = modifier, selectedPositionTime = selectedTime, - onPositionSelected = { time -> onPointSelected(time.toDouble()) }, + onSelectPosition = { time -> onPointSelected(time.toDouble()) }, ) }, listPart = { modifier, selectedX, lazyListState, onCardClick -> diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt index 83079acb39..2b49b4bdc2 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt @@ -118,7 +118,7 @@ private fun TracerouteMapScaffold( TracerouteMap( tracerouteOverlay = overlay, tracerouteNodePositions = snapshotPositions, - onMappableCountChanged = { shown: Int, total: Int -> + onMappableCountChange = { shown: Int, total: Int -> tracerouteNodesShown = shown tracerouteNodesTotal = total },