mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-07-03 14:06:34 +02:00
chore(map): bump MapLibre Compose to 0.13.0, light up desktop base map, drop Google Maps leftovers
Rebased onto main and dusted off the MapLibre Compose Multiplatform branch. - Bump maplibre-compose 0.12.1 -> 0.13.0 and migrate the redesigned location API (BearingUpdate.TRACK_LOCATION -> TRACK_AUTOMATIC; LocationPuck locationState -> location). - desktopApp: add a host-detecting maplibre-native-bindings-jni runtime backend so the desktop base map renders (Metal on macOS arm64, OpenGL on Linux/Windows amd64). Without it Gradle links no native renderer and the map canvas is black. - Gate Compose map overlays off on the JVM target via a mapOverlaysSupported expect/actual flag (true Android/iOS, false desktop). maplibre-compose 0.13.0 stubs the desktop layers/sources API with TODO(), so composing markers/waypoints/tracks/traceroute threw NotImplementedError and tore down the window. Desktop now renders base-map-only; overlays auto-enable when upstream implements desktop layers. - Remove orphaned Google Maps leftovers the rebase carried over from main's #5702/#5709 marker work (MarkerBitmapRenderer.kt + play-services-maps); this branch replaces Google Maps entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
-144
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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.app.map.component
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextPaint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.google.android.gms.maps.MapsInitializer
|
||||
import com.google.android.gms.maps.model.BitmapDescriptor
|
||||
import com.google.android.gms.maps.model.BitmapDescriptorFactory
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
private const val CHIP_CORNER_RADIUS_DP = 4f
|
||||
private const val CHIP_PADDING_HORIZONTAL_DP = 8f
|
||||
private const val CHIP_MIN_WIDTH_DP = 64f
|
||||
private const val CHIP_MIN_HEIGHT_DP = 28f
|
||||
private const val CHIP_TEXT_SIZE_SP = 14f
|
||||
private const val EMOJI_TEXT_SIZE_SP = 32f
|
||||
private const val EMOJI_PADDING_DP = 2f
|
||||
|
||||
/**
|
||||
* Renders a node chip marker as a [BitmapDescriptor] using Canvas — avoids the off-screen ComposeView pipeline in
|
||||
* maps-compose's `MarkerComposable`/`rememberComposeBitmapDescriptor` which can crash with "The ComposeView was
|
||||
* measured to have a width or height of zero" during subcomposition races (googlemaps/android-maps-compose#875).
|
||||
*/
|
||||
@Composable
|
||||
fun rememberNodeChipDescriptor(node: Node): BitmapDescriptor {
|
||||
val context = LocalContext.current
|
||||
val density = LocalDensity.current.density
|
||||
val fontScale = LocalDensity.current.fontScale
|
||||
return remember(node.num, node.user.short_name, node.colors, node.isIgnored) {
|
||||
ensureMapsInitialized(context)
|
||||
renderNodeChipBitmap(node, density, fontScale)
|
||||
}
|
||||
}
|
||||
|
||||
/** Renders an emoji waypoint marker as a [BitmapDescriptor] using Canvas. */
|
||||
@Composable
|
||||
fun rememberEmojiMarkerDescriptor(codePoint: Int): BitmapDescriptor {
|
||||
val context = LocalContext.current
|
||||
val density = LocalDensity.current.density
|
||||
val fontScale = LocalDensity.current.fontScale
|
||||
return remember(codePoint) {
|
||||
ensureMapsInitialized(context)
|
||||
renderEmojiBitmap(codePoint, density, fontScale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [BitmapDescriptorFactory] only works after the Maps SDK has been initialized, which normally happens when a
|
||||
* GoogleMap/MapView is created. These descriptors are built during composition, and on the node-detail inline map the
|
||||
* icon is computed before that screen's GoogleMap has loaded the SDK — so [BitmapDescriptorFactory.fromBitmap] crashes
|
||||
* with "IBitmapDescriptorFactory is not initialized". Initialize explicitly first; [MapsInitializer.initialize] is
|
||||
* synchronous and idempotent, so it is a no-op once the SDK is already up.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
private fun ensureMapsInitialized(context: Context) {
|
||||
MapsInitializer.initialize(context)
|
||||
}
|
||||
|
||||
private fun renderNodeChipBitmap(node: Node, density: Float, fontScale: Float): BitmapDescriptor {
|
||||
val (textColorInt, nodeColorInt) = node.colors
|
||||
val scaledDensity = density * fontScale
|
||||
|
||||
val textPaint =
|
||||
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
textSize = CHIP_TEXT_SIZE_SP * scaledDensity
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
color = textColorInt
|
||||
textAlign = Paint.Align.CENTER
|
||||
isStrikeThruText = node.isIgnored
|
||||
}
|
||||
val label = node.user.short_name.ifEmpty { "???" }
|
||||
|
||||
val textWidth = textPaint.measureText(label)
|
||||
val paddingH = CHIP_PADDING_HORIZONTAL_DP * density
|
||||
val minWidth = CHIP_MIN_WIDTH_DP * density
|
||||
val minHeight = CHIP_MIN_HEIGHT_DP * density
|
||||
|
||||
val width = maxOf(minWidth, textWidth + paddingH * 2).toInt()
|
||||
val height = minHeight.toInt()
|
||||
|
||||
val bitmap = createBitmap(width, height)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = nodeColorInt }
|
||||
val cornerRadius = CHIP_CORNER_RADIUS_DP * density
|
||||
canvas.drawRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), cornerRadius, cornerRadius, bgPaint)
|
||||
|
||||
val textX = width / 2f
|
||||
val textY = (height / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f)
|
||||
canvas.drawText(label, textX, textY, textPaint)
|
||||
|
||||
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
}
|
||||
|
||||
private fun renderEmojiBitmap(codePoint: Int, density: Float, fontScale: Float): BitmapDescriptor {
|
||||
val scaledDensity = density * fontScale
|
||||
val textPaint =
|
||||
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
textSize = EMOJI_TEXT_SIZE_SP * scaledDensity
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
val emoji = String(Character.toChars(codePoint))
|
||||
val padding = EMOJI_PADDING_DP * density
|
||||
|
||||
val textWidth = textPaint.measureText(emoji)
|
||||
val metrics = textPaint.fontMetrics
|
||||
val textHeight = metrics.descent - metrics.ascent
|
||||
|
||||
val width = (textWidth + padding * 2).toInt().coerceAtLeast(1)
|
||||
val height = (textHeight + padding * 2).toInt().coerceAtLeast(1)
|
||||
|
||||
val bitmap = createBitmap(width, height)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
val textX = width / 2f
|
||||
val textY = padding - metrics.ascent
|
||||
canvas.drawText(emoji, textX, textY, textPaint)
|
||||
|
||||
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
}
|
||||
@@ -233,7 +233,42 @@ compose.desktop {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the MapLibre Native desktop backend capability matching the build host.
|
||||
*
|
||||
* MapLibre Compose ships the native renderer as mutually-exclusive per-OS/arch capability variants of
|
||||
* `maplibre-native-bindings-jni`. Gradle can't auto-pick among them, so without an explicit selection no native library
|
||||
* is linked and the map canvas renders black. See https://maplibre.org/maplibre-compose/getting-started/.
|
||||
*
|
||||
* macOS is Apple-Silicon only (Metal); Linux/Windows use OpenGL here for broadest driver/Flatpak/CI compatibility
|
||||
* (Vulkan variants — `*-amd64-vulkan` — are also published if preferred).
|
||||
*/
|
||||
fun maplibreNativeTarget(): String {
|
||||
val os = System.getProperty("os.name").lowercase()
|
||||
val arch = System.getProperty("os.arch").lowercase()
|
||||
return when {
|
||||
os.contains("mac") || os.contains("darwin") -> {
|
||||
require(arch == "aarch64" || arch == "arm64") {
|
||||
"MapLibre Native desktop ships only a macOS arm64 (Apple Silicon) backend; host arch '$arch' is unsupported."
|
||||
}
|
||||
"macos-aarch64-metal"
|
||||
}
|
||||
|
||||
os.contains("win") -> "windows-amd64-opengl"
|
||||
|
||||
else -> "linux-amd64-opengl"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// MapLibre Native renderer for the current desktop host (runtime-only). Selects exactly one OS/arch
|
||||
// capability — required, or the map renders black. See maplibreNativeTarget() above.
|
||||
runtimeOnly("org.maplibre.compose:maplibre-native-bindings-jni:${libs.versions.maplibre.compose.get()}") {
|
||||
capabilities {
|
||||
requireCapability("org.maplibre.compose:maplibre-native-bindings-jni-${maplibreNativeTarget()}")
|
||||
}
|
||||
}
|
||||
|
||||
implementation(libs.aboutlibraries.core)
|
||||
implementation(libs.aboutlibraries.compose.m3)
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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
|
||||
|
||||
/** Android implements the full MapLibre Compose sources/layers API. */
|
||||
actual val mapOverlaysSupported: Boolean = true
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Whether the current platform's MapLibre Compose target implements programmatic map sources and layers
|
||||
* (`rememberGeoJsonSource`, `CircleLayer`, `SymbolLayer`, `LineLayer`, `HillshadeLayer`, …).
|
||||
*
|
||||
* As of maplibre-compose 0.13.0 the desktop (JVM) target stubs the **entire** sources/layers API with `TODO()`, so
|
||||
* composing any overlay throws `NotImplementedError` ("An operation is not implemented") and tears down the window. The
|
||||
* base map style still renders natively from its style URI. Every source/layer composition must therefore be guarded
|
||||
* behind this flag; when `false`, only the base map is shown.
|
||||
*
|
||||
* Re-evaluate on each maplibre-compose upgrade — once the desktop target implements layers/sources, set this `true` for
|
||||
* JVM and the guards become no-ops.
|
||||
*/
|
||||
expect val mapOverlaysSupported: Boolean
|
||||
@@ -133,7 +133,7 @@ fun MapScreen(
|
||||
|
||||
// Location tracking state: 3-mode cycling (Off → Track → TrackBearing → Off)
|
||||
var isLocationTrackingEnabled by remember { mutableStateOf(false) }
|
||||
var bearingUpdate by remember { mutableStateOf(BearingUpdate.TRACK_LOCATION) }
|
||||
var bearingUpdate by remember { mutableStateOf(BearingUpdate.TRACK_AUTOMATIC) }
|
||||
val locationProvider = rememberLocationProviderOrNull()
|
||||
val locationState = rememberUserLocationState(locationProvider ?: rememberNullLocationProvider())
|
||||
val locationAvailable = locationProvider != null
|
||||
@@ -144,8 +144,13 @@ fun MapScreen(
|
||||
if (isLocationTrackingEnabled) {
|
||||
when (bearingUpdate) {
|
||||
BearingUpdate.IGNORE -> GestureOptions.PositionLocked
|
||||
|
||||
BearingUpdate.ALWAYS_NORTH -> GestureOptions.ZoomOnly
|
||||
BearingUpdate.TRACK_LOCATION -> GestureOptions.ZoomOnly
|
||||
|
||||
BearingUpdate.TRACK_AUTOMATIC,
|
||||
BearingUpdate.TRACK_COURSE,
|
||||
BearingUpdate.TRACK_ORIENTATION,
|
||||
-> GestureOptions.ZoomOnly
|
||||
}
|
||||
} else {
|
||||
GestureOptions.Standard
|
||||
@@ -220,7 +225,7 @@ fun MapScreen(
|
||||
LocationTrackingEffect(
|
||||
locationState = locationState,
|
||||
enabled = isLocationTrackingEnabled,
|
||||
trackBearing = bearingUpdate == BearingUpdate.TRACK_LOCATION,
|
||||
trackBearing = bearingUpdate == BearingUpdate.TRACK_AUTOMATIC,
|
||||
) {
|
||||
cameraState.updateFromLocation(updateBearing = bearingUpdate)
|
||||
}
|
||||
@@ -239,7 +244,7 @@ fun MapScreen(
|
||||
modifier = Modifier.align(Alignment.TopEnd).padding(paddingValues),
|
||||
bearing = cameraState.position.bearing.toFloat(),
|
||||
onCompassClick = { scope.launch { cameraState.animateTo(cameraState.position.copy(bearing = 0.0)) } },
|
||||
followPhoneBearing = isLocationTrackingEnabled && bearingUpdate == BearingUpdate.TRACK_LOCATION,
|
||||
followPhoneBearing = isLocationTrackingEnabled && bearingUpdate == BearingUpdate.TRACK_AUTOMATIC,
|
||||
activeFilterCount = activeFilterCount,
|
||||
filterDropdownContent = {
|
||||
MapFilterDropdown(
|
||||
@@ -267,27 +272,23 @@ fun MapScreen(
|
||||
},
|
||||
layersContent = { OfflineMapContent(styleUri = selectedMapStyle.styleUri, cameraState = cameraState) },
|
||||
isLocationTrackingEnabled = isLocationTrackingEnabled,
|
||||
isTrackingBearing = bearingUpdate == BearingUpdate.TRACK_LOCATION,
|
||||
isTrackingBearing = bearingUpdate == BearingUpdate.TRACK_AUTOMATIC,
|
||||
onToggleLocationTracking = {
|
||||
if (!locationAvailable) {
|
||||
scope.launch { snackbarHostState.showSnackbar(locationUnavailableMsg) }
|
||||
} else if (!isLocationTrackingEnabled) {
|
||||
// Off → Track with bearing
|
||||
bearingUpdate = BearingUpdate.TRACK_LOCATION
|
||||
bearingUpdate = BearingUpdate.TRACK_AUTOMATIC
|
||||
isLocationTrackingEnabled = true
|
||||
} else {
|
||||
when (bearingUpdate) {
|
||||
BearingUpdate.TRACK_LOCATION -> {
|
||||
BearingUpdate.TRACK_AUTOMATIC -> {
|
||||
// TrackBearing → TrackNorth
|
||||
bearingUpdate = BearingUpdate.ALWAYS_NORTH
|
||||
}
|
||||
|
||||
BearingUpdate.ALWAYS_NORTH -> {
|
||||
// TrackNorth → Off
|
||||
isLocationTrackingEnabled = false
|
||||
}
|
||||
|
||||
BearingUpdate.IGNORE -> {
|
||||
else -> {
|
||||
// TrackNorth (or any other) → Off
|
||||
isLocationTrackingEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.maplibre.spatialk.geojson.Feature
|
||||
import org.maplibre.spatialk.geojson.FeatureCollection
|
||||
import org.maplibre.spatialk.geojson.Point
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.feature.map.mapOverlaysSupported
|
||||
import org.meshtastic.feature.map.model.MapStyle
|
||||
import org.meshtastic.feature.map.util.MARKER_STROKE_WIDTH
|
||||
import org.meshtastic.feature.map.util.NODE_MARKER_RADIUS
|
||||
@@ -84,6 +85,10 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
options =
|
||||
MapOptions(gestureOptions = GestureOptions.AllDisabled, ornamentOptions = OrnamentOptions.AllDisabled),
|
||||
) {
|
||||
// Desktop (maplibre-compose 0.13.0) stubs all layers/sources; render base map only. See
|
||||
// [mapOverlaysSupported].
|
||||
if (!mapOverlaysSupported) return@MaplibreMap
|
||||
|
||||
val source = rememberGeoJsonSource(data = GeoJsonData.Features(nodeFeature))
|
||||
|
||||
// Node marker dot
|
||||
|
||||
+6
-1
@@ -79,6 +79,7 @@ import org.maplibre.compose.util.ClickResult
|
||||
import org.maplibre.spatialk.geojson.Point
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.feature.map.mapOverlaysSupported
|
||||
import org.meshtastic.feature.map.util.MARKER_STROKE_WIDTH
|
||||
import org.meshtastic.feature.map.util.NODE_MARKER_RADIUS
|
||||
import org.meshtastic.feature.map.util.PRECISION_CIRCLE_STROKE_ALPHA
|
||||
@@ -157,6 +158,10 @@ fun MaplibreMapContent(
|
||||
onMapLoadFinished = onMapLoad,
|
||||
onMapLoadFailed = onMapLoadFail,
|
||||
) {
|
||||
// MapLibre Compose layers/sources are stubbed on desktop (maplibre-compose 0.13.0); gate overlays off
|
||||
// there so the base map still renders without throwing NotImplementedError. See [mapOverlaysSupported].
|
||||
if (!mapOverlaysSupported) return@MaplibreMap
|
||||
|
||||
// --- Terrain hillshade overlay ---
|
||||
if (showHillshade) {
|
||||
val demSource = rememberRasterDemSource(tiles = TERRAIN_TILES, encoding = RasterDemEncoding.Terrarium)
|
||||
@@ -181,7 +186,7 @@ fun MaplibreMapContent(
|
||||
if (locationState != null) {
|
||||
LocationPuck(
|
||||
idPrefix = "user-location",
|
||||
locationState = locationState,
|
||||
location = locationState.location,
|
||||
cameraState = cameraState,
|
||||
colors = LocationPuckDefaults.colors(),
|
||||
)
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.maplibre.compose.map.GestureOptions
|
||||
import org.maplibre.compose.map.MapOptions
|
||||
import org.maplibre.compose.map.MaplibreMap
|
||||
import org.maplibre.compose.map.OrnamentOptions
|
||||
import org.meshtastic.feature.map.mapOverlaysSupported
|
||||
import org.meshtastic.feature.map.model.MapStyle
|
||||
import org.meshtastic.feature.map.util.computeBoundingBox
|
||||
import org.meshtastic.feature.map.util.toGeoPositionOrNull
|
||||
@@ -79,6 +80,9 @@ fun NodeTrackMap(
|
||||
options =
|
||||
MapOptions(gestureOptions = GestureOptions.RotationLocked, ornamentOptions = OrnamentOptions.AllEnabled),
|
||||
) {
|
||||
// Desktop (maplibre-compose 0.13.0) stubs all layers/sources; render base map only. See [mapOverlaysSupported].
|
||||
if (!mapOverlaysSupported) return@MaplibreMap
|
||||
|
||||
NodeTrackLayers(
|
||||
positions = positions,
|
||||
selectedPositionTime = selectedPositionTime,
|
||||
|
||||
+4
@@ -30,6 +30,7 @@ import org.maplibre.compose.map.MaplibreMap
|
||||
import org.maplibre.compose.map.OrnamentOptions
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.TracerouteOverlay
|
||||
import org.meshtastic.feature.map.mapOverlaysSupported
|
||||
import org.meshtastic.feature.map.model.MapStyle
|
||||
import org.meshtastic.feature.map.util.computeBoundingBox
|
||||
import org.meshtastic.feature.map.util.toGeoPositionOrNull
|
||||
@@ -87,6 +88,9 @@ fun TracerouteMap(
|
||||
options =
|
||||
MapOptions(gestureOptions = GestureOptions.RotationLocked, ornamentOptions = OrnamentOptions.AllEnabled),
|
||||
) {
|
||||
// Desktop (maplibre-compose 0.13.0) stubs all layers/sources; render base map only. See [mapOverlaysSupported].
|
||||
if (!mapOverlaysSupported) return@MaplibreMap
|
||||
|
||||
TracerouteLayers(
|
||||
overlay = tracerouteOverlay,
|
||||
nodePositions = tracerouteNodePositions,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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
|
||||
|
||||
/** iOS implements the full MapLibre Compose sources/layers API. */
|
||||
actual val mapOverlaysSupported: Boolean = true
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Desktop (JVM) does NOT implement the MapLibre Compose sources/layers API in maplibre-compose 0.13.0 — every
|
||||
* `Layer`/`Source` is stubbed with `TODO()`. Overlays are gated off so the base map renders without crashing.
|
||||
*/
|
||||
actual val mapOverlaysSupported: Boolean = false
|
||||
@@ -47,14 +47,15 @@ compose-multiplatform-material3 = "1.11.0-alpha07"
|
||||
# AndroidCompose.kt's resolutionStrategy force-aligns these groups to *this* version
|
||||
# at resolution time, so it is the source of truth for the Android target.
|
||||
androidx-compose-bom-aligned = "1.11.2"
|
||||
# `androidx-compose-material` (M2) is independent of CMP. Pinned because
|
||||
# maps-compose-widgets requests `androidx.compose.material:material` without
|
||||
# a version (relying on a BOM that we exclude). M2 is frozen at 1.7.8.
|
||||
# `androidx-compose-material` (M2) is independent of CMP. Pinned because some
|
||||
# transitive consumers request `androidx.compose.material:material` without a
|
||||
# version (relying on a BOM that we exclude). M2 is frozen at 1.7.8.
|
||||
# Consumed by build-logic AndroidCompose.kt to force-align the M2 material group.
|
||||
androidx-compose-material = "1.7.8"
|
||||
jetbrains-adaptive = "1.3.0-beta01"
|
||||
|
||||
# MapLibre
|
||||
maplibre-compose = "0.12.1"
|
||||
maplibre-compose = "0.13.0"
|
||||
|
||||
# ML Kit
|
||||
mlkit-barcode-scanning = "17.3.0"
|
||||
|
||||
Reference in New Issue
Block a user