kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: pass users preferred tile source to MapViews
rodzic
aeedd4de43
commit
ee75ba3392
|
@ -1,6 +1,7 @@
|
||||||
package com.geeksville.mesh.model
|
package com.geeksville.mesh.model
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
@ -17,8 +18,10 @@ import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.database.MeshLogRepository
|
import com.geeksville.mesh.database.MeshLogRepository
|
||||||
import com.geeksville.mesh.database.entity.MeshLog
|
import com.geeksville.mesh.database.entity.MeshLog
|
||||||
|
import com.geeksville.mesh.model.map.CustomTileSource
|
||||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||||
import com.geeksville.mesh.ui.Route
|
import com.geeksville.mesh.ui.Route
|
||||||
|
import com.geeksville.mesh.ui.map.MAP_STYLE_ID
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
@ -111,6 +114,7 @@ class MetricsViewModel @Inject constructor(
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val meshLogRepository: MeshLogRepository,
|
private val meshLogRepository: MeshLogRepository,
|
||||||
private val radioConfigRepository: RadioConfigRepository,
|
private val radioConfigRepository: RadioConfigRepository,
|
||||||
|
private val preferences: SharedPreferences,
|
||||||
) : ViewModel(), Logging {
|
) : ViewModel(), Logging {
|
||||||
private val destNum = savedStateHandle.toRoute<Route.NodeDetail>().destNum
|
private val destNum = savedStateHandle.toRoute<Route.NodeDetail>().destNum
|
||||||
|
|
||||||
|
@ -119,6 +123,7 @@ class MetricsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUser(nodeNum: Int) = radioConfigRepository.getUser(nodeNum)
|
fun getUser(nodeNum: Int) = radioConfigRepository.getUser(nodeNum)
|
||||||
|
val tileSource get() = CustomTileSource.getTileSource(preferences.getInt(MAP_STYLE_ID, 0))
|
||||||
|
|
||||||
fun deleteLog(uuid: String) = viewModelScope.launch(dispatchers.io) {
|
fun deleteLog(uuid: String) = viewModelScope.launch(dispatchers.io) {
|
||||||
meshLogRepository.deleteLog(uuid)
|
meshLogRepository.deleteLog(uuid)
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.geeksville.mesh.database.entity.QuickChatAction
|
||||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||||
import com.geeksville.mesh.service.MeshService
|
import com.geeksville.mesh.service.MeshService
|
||||||
|
import com.geeksville.mesh.ui.map.MAP_STYLE_ID
|
||||||
import com.geeksville.mesh.util.positionToMeter
|
import com.geeksville.mesh.util.positionToMeter
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -43,10 +44,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.osmdroid.util.GeoPoint
|
|
||||||
import java.io.BufferedWriter
|
import java.io.BufferedWriter
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
|
@ -122,15 +121,6 @@ data class NodesUiState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MapState(
|
|
||||||
val center: GeoPoint? = null,
|
|
||||||
val zoom: Double = 0.0,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
val Empty = MapState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Contact(
|
data class Contact(
|
||||||
val contactKey: String,
|
val contactKey: String,
|
||||||
val shortName: String,
|
val shortName: String,
|
||||||
|
@ -259,11 +249,9 @@ class UIViewModel @Inject constructor(
|
||||||
|
|
||||||
val nodesWithPosition get() = nodeDB.nodeDBbyNum.value.values.filter { it.validPosition != null }
|
val nodesWithPosition get() = nodeDB.nodeDBbyNum.value.values.filter { it.validPosition != null }
|
||||||
|
|
||||||
private val _mapState = MutableStateFlow(MapState.Empty)
|
var mapStyleId: Int
|
||||||
val mapState: StateFlow<MapState> get() = _mapState
|
get() = preferences.getInt(MAP_STYLE_ID, 0)
|
||||||
|
set(value) = preferences.edit { putInt(MAP_STYLE_ID, value) }
|
||||||
fun updateMapCenterAndZoom(center: GeoPoint, zoom: Double) =
|
|
||||||
_mapState.update { it.copy(center = center, zoom = zoom) }
|
|
||||||
|
|
||||||
fun getUser(userId: String?) = nodeDB.getUser(userId ?: DataPacket.ID_BROADCAST)
|
fun getUser(userId: String?) = nodeDB.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ fun NodeMapScreen(
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
val geoPoints = state.positionLogs.map { GeoPoint(it.latitudeI * DegD, it.longitudeI * DegD) }
|
val geoPoints = state.positionLogs.map { GeoPoint(it.latitudeI * DegD, it.longitudeI * DegD) }
|
||||||
val cameraView = remember { BoundingBox.fromGeoPoints(geoPoints) }
|
val cameraView = remember { BoundingBox.fromGeoPoints(geoPoints) }
|
||||||
val mapView = rememberMapViewWithLifecycle(cameraView)
|
val mapView = rememberMapViewWithLifecycle(cameraView, viewModel.tileSource)
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
|
@ -214,10 +214,6 @@ fun MapView(
|
||||||
// UI Elements
|
// UI Elements
|
||||||
var cacheEstimate by remember { mutableStateOf("") }
|
var cacheEstimate by remember { mutableStateOf("") }
|
||||||
|
|
||||||
// constants
|
|
||||||
val prefsName = "org.geeksville.osm.prefs"
|
|
||||||
val mapStyleId = "map_style_id"
|
|
||||||
|
|
||||||
var zoomLevelMin by remember { mutableDoubleStateOf(0.0) }
|
var zoomLevelMin by remember { mutableDoubleStateOf(0.0) }
|
||||||
var zoomLevelMax by remember { mutableDoubleStateOf(0.0) }
|
var zoomLevelMax by remember { mutableDoubleStateOf(0.0) }
|
||||||
|
|
||||||
|
@ -225,20 +221,33 @@ fun MapView(
|
||||||
var downloadRegionBoundingBox: BoundingBox? by remember { mutableStateOf(null) }
|
var downloadRegionBoundingBox: BoundingBox? by remember { mutableStateOf(null) }
|
||||||
var myLocationOverlay: MyLocationNewOverlay? by remember { mutableStateOf(null) }
|
var myLocationOverlay: MyLocationNewOverlay? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
var showDownloadButton: Boolean by remember { mutableStateOf(false) }
|
||||||
|
var showEditWaypointDialog by remember { mutableStateOf<Waypoint?>(null) }
|
||||||
|
var showCurrentCacheInfo by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val mPrefs = remember { context.getSharedPreferences(prefsName, Context.MODE_PRIVATE) }
|
|
||||||
|
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
fun performHapticFeedback() = haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
fun performHapticFeedback() = haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
|
||||||
val hasGps = remember { context.hasGps() }
|
val hasGps = remember { context.hasGps() }
|
||||||
|
|
||||||
|
fun loadOnlineTileSourceBase(): ITileSource {
|
||||||
|
val id = model.mapStyleId
|
||||||
|
debug("mapStyleId from prefs: $id")
|
||||||
|
return CustomTileSource.getTileSource(id).also {
|
||||||
|
zoomLevelMax = it.maximumZoomLevel.toDouble()
|
||||||
|
showDownloadButton =
|
||||||
|
if (it is OnlineTileSourceBase) it.tileSourcePolicy.acceptsBulkDownload() else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val cameraView = remember {
|
val cameraView = remember {
|
||||||
val geoPoints = model.nodesWithPosition.map { GeoPoint(it.latitude, it.longitude) }
|
val geoPoints = model.nodesWithPosition.map { GeoPoint(it.latitude, it.longitude) }
|
||||||
BoundingBox.fromGeoPoints(geoPoints)
|
BoundingBox.fromGeoPoints(geoPoints)
|
||||||
}
|
}
|
||||||
val map = rememberMapViewWithLifecycle(cameraView)
|
val map = rememberMapViewWithLifecycle(cameraView, loadOnlineTileSourceBase())
|
||||||
|
|
||||||
val nodeClusterer = remember { RadiusMarkerClusterer(context) }
|
val nodeClusterer = remember { RadiusMarkerClusterer(context) }
|
||||||
|
|
||||||
|
@ -281,10 +290,6 @@ fun MapView(
|
||||||
val nodes by model.nodeList.collectAsStateWithLifecycle()
|
val nodes by model.nodeList.collectAsStateWithLifecycle()
|
||||||
val waypoints by model.waypoints.collectAsStateWithLifecycle(emptyMap())
|
val waypoints by model.waypoints.collectAsStateWithLifecycle(emptyMap())
|
||||||
|
|
||||||
var showDownloadButton: Boolean by remember { mutableStateOf(false) }
|
|
||||||
var showEditWaypointDialog by remember { mutableStateOf<Waypoint?>(null) }
|
|
||||||
var showCurrentCacheInfo by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val markerIcon = remember {
|
val markerIcon = remember {
|
||||||
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_location_on_24)
|
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_location_on_24)
|
||||||
}
|
}
|
||||||
|
@ -451,16 +456,6 @@ fun MapView(
|
||||||
UpdateMarkers(onNodesChanged(nodes), onWaypointChanged(waypoints.values), nodeClusterer)
|
UpdateMarkers(onNodesChanged(nodes), onWaypointChanged(waypoints.values), nodeClusterer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadOnlineTileSourceBase(): ITileSource {
|
|
||||||
val id = mPrefs.getInt(mapStyleId, 0)
|
|
||||||
debug("mapStyleId from prefs: $id")
|
|
||||||
return CustomTileSource.getTileSource(id).also {
|
|
||||||
zoomLevelMax = it.maximumZoomLevel.toDouble()
|
|
||||||
showDownloadButton =
|
|
||||||
if (it is OnlineTileSourceBase) it.tileSourcePolicy.acceptsBulkDownload() else false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates Box overlay showing what area can be downloaded
|
* Creates Box overlay showing what area can be downloaded
|
||||||
*/
|
*/
|
||||||
|
@ -536,10 +531,10 @@ fun MapView(
|
||||||
val builder = MaterialAlertDialogBuilder(context)
|
val builder = MaterialAlertDialogBuilder(context)
|
||||||
val mapStyles: Array<CharSequence> = CustomTileSource.mTileSources.values.toTypedArray()
|
val mapStyles: Array<CharSequence> = CustomTileSource.mTileSources.values.toTypedArray()
|
||||||
|
|
||||||
val mapStyleInt = mPrefs.getInt(mapStyleId, 0)
|
val mapStyleInt = model.mapStyleId
|
||||||
builder.setSingleChoiceItems(mapStyles, mapStyleInt) { dialog, which ->
|
builder.setSingleChoiceItems(mapStyles, mapStyleInt) { dialog, which ->
|
||||||
debug("Set mapStyleId pref to $which")
|
debug("Set mapStyleId pref to $which")
|
||||||
mPrefs.edit().putInt(mapStyleId, which).apply()
|
model.mapStyleId = which
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
map.setTileSource(loadOnlineTileSourceBase())
|
map.setTileSource(loadOnlineTileSourceBase())
|
||||||
}
|
}
|
||||||
|
@ -586,7 +581,6 @@ fun MapView(
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
map.apply {
|
map.apply {
|
||||||
setTileSource(loadOnlineTileSourceBase())
|
|
||||||
setDestroyMode(false) // keeps map instance alive when in the background
|
setDestroyMode(false) // keeps map instance alive when in the background
|
||||||
addMapListener(boxOverlayListener)
|
addMapListener(boxOverlayListener)
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue