kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
fix: map regressions (#3004)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>pull/3006/head
rodzic
5e462c9fd7
commit
ce60d490b7
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) }
|
||||
}
|
Ładowanie…
Reference in New Issue