fix: map regressions (#3004)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
pull/3006/head
James Rich 2025-09-06 14:33:06 -05:00 zatwierdzone przez GitHub
rodzic 5e462c9fd7
commit ce60d490b7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 129 dodań i 36 usunięć

Wyświetl plik

@ -24,10 +24,12 @@ import com.google.maps.android.compose.MapType
interface GoogleMapsPrefs {
var selectedGoogleMapType: String?
var selectedCustomTileUrl: String?
var hiddenLayerUrls: Set<String>
}
class GoogleMapsPrefsImpl(prefs: SharedPreferences) : GoogleMapsPrefs {
override var selectedGoogleMapType: String? by
NullableStringPrefDelegate(prefs, "selected_google_map_type", MapType.NORMAL.name)
override var selectedCustomTileUrl: String? by NullableStringPrefDelegate(prefs, "selected_custom_tile_url", null)
override var hiddenLayerUrls: Set<String> by StringSetPrefDelegate(prefs, "hidden_layer_urls", emptySet())
}

Wyświetl plik

@ -217,7 +217,16 @@ fun MapView(
var mapTypeMenuExpanded by remember { mutableStateOf(false) }
var showCustomTileManagerSheet by remember { mutableStateOf(false) }
val cameraPositionState = rememberCameraPositionState {}
val cameraPositionState = rememberCameraPositionState {
position =
CameraPosition.fromLatLngZoom(
LatLng(
ourNodeInfo?.position?.latitudeI?.times(DEG_D) ?: 0.0,
ourNodeInfo?.position?.longitudeI?.times(DEG_D) ?: 0.0,
),
7f,
)
}
// Location tracking functionality
val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) }
@ -279,7 +288,12 @@ fun MapView(
}
}
DisposableEffect(Unit) { onDispose { fusedLocationClient.removeLocationUpdates(locationCallback) } }
DisposableEffect(Unit) {
onDispose {
fusedLocationClient.removeLocationUpdates(locationCallback)
mapViewModel.clearLoadedLayerData()
}
}
val allNodes by
mapViewModel.nodes

Wyświetl plik

@ -44,6 +44,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
@ -231,8 +232,11 @@ constructor(
val mapLayers: StateFlow<List<MapLayerItem>> = _mapLayers.asStateFlow()
init {
viewModelScope.launch {
customTileProviderRepository.getCustomTileProviders().first()
loadPersistedMapType()
}
loadPersistedLayers()
loadPersistedMapType()
}
private fun loadPersistedMapType() {
@ -271,6 +275,7 @@ constructor(
val persistedLayerFiles = layersDir.listFiles()
if (persistedLayerFiles != null) {
val hiddenLayerUrls = googleMapsPrefs.hiddenLayerUrls
val loadedItems =
persistedLayerFiles.mapNotNull { file ->
if (file.isFile) {
@ -286,10 +291,11 @@ constructor(
}
layerType?.let {
val uri = Uri.fromFile(file)
MapLayerItem(
name = file.nameWithoutExtension,
uri = Uri.fromFile(file),
isVisible = true,
uri = uri,
isVisible = !hiddenLayerUrls.contains(uri.toString()),
layerType = it,
)
}
@ -372,7 +378,25 @@ constructor(
}
fun toggleLayerVisibility(layerId: String) {
_mapLayers.value = _mapLayers.value.map { if (it.id == layerId) it.copy(isVisible = !it.isVisible) else it }
var toggledLayer: MapLayerItem? = null
val updatedLayers =
_mapLayers.value.map {
if (it.id == layerId) {
toggledLayer = it.copy(isVisible = !it.isVisible)
toggledLayer
} else {
it
}
}
_mapLayers.value = updatedLayers
toggledLayer?.let {
if (it.isVisible) {
googleMapsPrefs.hiddenLayerUrls -= it.uri.toString()
} else {
googleMapsPrefs.hiddenLayerUrls += it.uri.toString()
}
}
}
fun removeMapLayer(layerId: String) {
@ -383,7 +407,10 @@ constructor(
LayerType.GEOJSON -> layerToRemove.geoJsonLayerData?.removeLayerFromMap()
null -> {}
}
layerToRemove?.uri?.let { uri -> deleteFileToInternalStorage(uri) }
layerToRemove?.uri?.let { uri ->
deleteFileToInternalStorage(uri)
googleMapsPrefs.hiddenLayerUrls -= uri.toString()
}
_mapLayers.value = _mapLayers.value.filterNot { it.id == layerId }
}
}
@ -418,40 +445,55 @@ constructor(
if (layerItem.kmlLayerData != null || layerItem.geoJsonLayerData != null) return
try {
when (layerItem.layerType) {
LayerType.KML -> {
val kmlLayer =
getInputStreamFromUri(layerItem)?.use { KmlLayer(map, it, application.applicationContext) }
_mapLayers.update { currentLayers ->
currentLayers.map {
if (it.id == layerItem.id) {
it.copy(kmlLayerData = kmlLayer)
} else {
it
}
}
}
}
LayerType.GEOJSON -> {
val geoJsonLayer =
getInputStreamFromUri(layerItem)?.use { inputStream ->
val jsonObject = JSONObject(inputStream.bufferedReader().use { it.readText() })
GeoJsonLayer(map, jsonObject)
}
_mapLayers.update { currentLayers ->
currentLayers.map {
if (it.id == layerItem.id) {
it.copy(geoJsonLayerData = geoJsonLayer)
} else {
it
}
}
}
}
LayerType.KML -> loadKmlLayerIfNeeded(layerItem, map)
LayerType.GEOJSON -> loadGeoJsonLayerIfNeeded(layerItem, map)
}
} catch (e: Exception) {
Timber.tag("MapViewModel").e(e, "Error loading map layer for ${layerItem.uri}")
}
}
private suspend fun loadKmlLayerIfNeeded(layerItem: MapLayerItem, map: GoogleMap) {
val kmlLayer =
getInputStreamFromUri(layerItem)?.use {
KmlLayer(map, it, application.applicationContext).apply {
if (!layerItem.isVisible) removeLayerFromMap()
}
}
_mapLayers.update { currentLayers ->
currentLayers.map {
if (it.id == layerItem.id) {
it.copy(kmlLayerData = kmlLayer)
} else {
it
}
}
}
}
private suspend fun loadGeoJsonLayerIfNeeded(layerItem: MapLayerItem, map: GoogleMap) {
val geoJsonLayer =
getInputStreamFromUri(layerItem)?.use { inputStream ->
val jsonObject = JSONObject(inputStream.bufferedReader().use { it.readText() })
GeoJsonLayer(map, jsonObject).apply { if (!layerItem.isVisible) removeLayerFromMap() }
}
_mapLayers.update { currentLayers ->
currentLayers.map {
if (it.id == layerItem.id) {
it.copy(geoJsonLayerData = geoJsonLayer)
} else {
it
}
}
}
}
fun clearLoadedLayerData() {
_mapLayers.update { currentLayers ->
currentLayers.map { it.copy(kmlLayerData = null, geoJsonLayerData = null) }
}
}
}
enum class LayerType {

Wyświetl plik

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025 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 com.geeksville.mesh.android.prefs
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class StringSetPrefDelegate(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: Set<String>,
) : ReadWriteProperty<Any?, Set<String>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Set<String> =
prefs.getStringSet(key, defaultValue) ?: emptySet()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Set<String>) =
prefs.edit { putStringSet(key, value) }
}