Release GPS2Audio v2.5.44 POI category fix
This commit is contained in:
@@ -29,8 +29,12 @@ import androidx.compose.material.icons.filled.AddLocation
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.FiberManualRecord
|
||||
import androidx.compose.material.icons.filled.Layers
|
||||
import androidx.compose.material.icons.filled.MyLocation
|
||||
import androidx.compose.material.icons.filled.PlayCircle
|
||||
import androidx.compose.material.icons.filled.RadioButtonChecked
|
||||
import androidx.compose.material.icons.filled.RadioButtonUnchecked
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material.icons.filled.Timeline
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@@ -72,13 +76,14 @@ import com.google.android.gms.location.Priority
|
||||
import com.google.android.gms.tasks.CancellationTokenSource
|
||||
import de.waypointaudio.R
|
||||
import de.waypointaudio.data.GpsTrackPoint
|
||||
import de.waypointaudio.data.MapProvider
|
||||
import de.waypointaudio.data.MapProviderStore
|
||||
import de.waypointaudio.data.Waypoint
|
||||
import de.waypointaudio.viewmodel.WaypointViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import org.osmdroid.config.Configuration
|
||||
import org.osmdroid.events.MapEventsReceiver
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.MapView
|
||||
import org.osmdroid.views.overlay.MapEventsOverlay
|
||||
@@ -116,7 +121,12 @@ import kotlin.math.sqrt
|
||||
@Composable
|
||||
fun MapScreen(
|
||||
viewModel: WaypointViewModel,
|
||||
onNavigateBack: () -> Unit
|
||||
onNavigateBack: () -> Unit,
|
||||
// v2.2.1: Wenn gesetzt, zeigt die TopAppBar einen Explorer/Editor-Modus-
|
||||
// Umschalter im Titel-Slot. Dadurch wirken Explorer und Editor als ein
|
||||
// gemeinsames Kartenmodul.
|
||||
currentMode: MapMode? = null,
|
||||
onModeChange: ((MapMode) -> Unit)? = null
|
||||
) {
|
||||
val allWaypoints by viewModel.waypoints.collectAsState()
|
||||
val selectedTour by viewModel.selectedTour.collectAsState()
|
||||
@@ -149,6 +159,14 @@ fun MapScreen(
|
||||
var currentLocation by remember { mutableStateOf<Pair<Double, Double>?>(null) }
|
||||
var showAddWaypointAction by remember { mutableStateOf(false) }
|
||||
|
||||
// Karten-Anbieter (Phase 1): persistent via DataStore, app-weit gespeichert.
|
||||
// Hinweis: Eine Pro-Tour-Auswahl ist mit der bisherigen Tour-Settings-Architektur
|
||||
// nicht direkt vereinbar (Touren halten Audio-Einstellungen, keine Karten-Settings),
|
||||
// daher bewusst app-weit. Die Auswahl ist über das Karten-Menü jederzeit umstellbar.
|
||||
val providerStore = remember { MapProviderStore(context.applicationContext) }
|
||||
val selectedProvider by providerStore.provider.collectAsState(initial = MapProvider.DEFAULT)
|
||||
var showProviderDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// Editor-Dialog-State
|
||||
var editDialogWaypoint by remember { mutableStateOf<Waypoint?>(null) }
|
||||
var editDialogPrefill by remember { mutableStateOf<Pair<Double, Double>?>(null) }
|
||||
@@ -167,6 +185,15 @@ fun MapScreen(
|
||||
// Bestätigungsdialog für das Löschen eines Wegpunkts
|
||||
var deleteConfirmWaypoint by remember { mutableStateOf<Waypoint?>(null) }
|
||||
|
||||
// Ortssuche (Nominatim/OpenStreetMap) — temporärer roter Pin auf der Karte.
|
||||
// Verändert weder Wegpunkte, Tracks, Drafts noch den Aufnahmezustand.
|
||||
var showSearchDialog by remember { mutableStateOf(false) }
|
||||
var searchPin by remember { mutableStateOf<GeoPoint?>(null) }
|
||||
|
||||
// v2.2.1: Layer-Panel-Status. Die Switches steuern aktuell vorwiegend
|
||||
// Sichtbarkeit; markers/radius/track werden in drawMapOverlays gefiltert.
|
||||
var layerState by remember { mutableStateOf(MapLayerState()) }
|
||||
|
||||
// Bitmap für Standort-Puck (einmalig erstellt)
|
||||
val puckBitmap = remember { createLocationPuckBitmap(context) }
|
||||
|
||||
@@ -174,17 +201,27 @@ fun MapScreen(
|
||||
LocationServices.getFusedLocationProviderClient(context)
|
||||
}
|
||||
|
||||
// ─── Karte neu zeichnen wenn Wegpunkte / Track / Farben sich ändern ────────
|
||||
LaunchedEffect(waypoints, primaryColor, currentTrack) {
|
||||
// ─── Karten-Anbieter Wechsel: Tile-Source aktualisieren und Cache invalidieren ─
|
||||
LaunchedEffect(selectedProvider) {
|
||||
val mv = mapView ?: return@LaunchedEffect
|
||||
mv.setTileSource(selectedProvider.toTileSource())
|
||||
mv.invalidate()
|
||||
}
|
||||
|
||||
// ─── Karte neu zeichnen wenn Wegpunkte / Track / Farben / Auswahl / Such-Pin / Layer sich ändern ─
|
||||
LaunchedEffect(waypoints, primaryColor, currentTrack, actionMenuWaypoint, searchPin, layerState) {
|
||||
val mv = mapView ?: return@LaunchedEffect
|
||||
drawMapOverlays(
|
||||
mv = mv,
|
||||
waypoints = waypoints,
|
||||
trackPoints = currentTrack,
|
||||
waypoints = if (layerState.showMarkers) waypoints else emptyList(),
|
||||
trackPoints = if (layerState.showTrack) currentTrack else emptyList(),
|
||||
primaryArgb = primaryColor.toArgb(),
|
||||
errorArgb = errorColor.toArgb(),
|
||||
trackArgb = trackColor.toArgb(),
|
||||
onWaypointTapped = { wp -> actionMenuWaypoint = wp }
|
||||
selectedWaypointId = actionMenuWaypoint?.id,
|
||||
onWaypointTapped = { wp -> actionMenuWaypoint = wp },
|
||||
searchPin = searchPin,
|
||||
showRadius = layerState.showRadius
|
||||
)
|
||||
}
|
||||
|
||||
@@ -223,6 +260,10 @@ fun MapScreen(
|
||||
showEditDialog = false
|
||||
editDialogWaypoint = null
|
||||
editDialogPrefill = null
|
||||
},
|
||||
// v2.5.16 — Kopieren auch aus dem Karten-Bearbeiten-Flow möglich.
|
||||
onCopyToOtherTour = editDialogWaypoint?.let { wp ->
|
||||
{ targetTour -> viewModel.copyWaypointToTour(wp.id, targetTour) }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -350,18 +391,54 @@ fun MapScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Karten-Anbieter Auswahl-Dialog ─────────────────────────────────────
|
||||
if (showProviderDialog) {
|
||||
MapProviderDialog(
|
||||
selected = selectedProvider,
|
||||
onSelect = { provider ->
|
||||
scope.launch {
|
||||
providerStore.setProvider(provider)
|
||||
}
|
||||
showProviderDialog = false
|
||||
},
|
||||
onDismiss = { showProviderDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Ortssuche Dialog (Nominatim/OSM) ────────────────────────────────────
|
||||
if (showSearchDialog) {
|
||||
PlaceSearchDialog(
|
||||
onDismiss = { showSearchDialog = false },
|
||||
onResultSelected = { result ->
|
||||
val gp = GeoPoint(result.latitude, result.longitude)
|
||||
searchPin = gp
|
||||
mapView?.controller?.animateTo(gp)
|
||||
mapView?.controller?.setZoom(15.0)
|
||||
showSearchDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(stringResource(R.string.map_view))
|
||||
if (tourList.size > 1) {
|
||||
Text(
|
||||
text = selectedTour,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f)
|
||||
)
|
||||
if (currentMode != null && onModeChange != null) {
|
||||
MapModeSegmentedHeader(
|
||||
current = currentMode,
|
||||
onChange = onModeChange,
|
||||
subtitle = if (tourList.size > 1) selectedTour else null
|
||||
)
|
||||
} else {
|
||||
Column {
|
||||
Text(stringResource(R.string.map_view))
|
||||
if (tourList.size > 1) {
|
||||
Text(
|
||||
text = selectedTour,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -373,10 +450,33 @@ fun MapScreen(
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { showProviderDialog = true }) {
|
||||
Icon(
|
||||
Icons.Filled.Layers,
|
||||
contentDescription = stringResource(R.string.map_provider_menu)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { showSearchDialog = true }) {
|
||||
Icon(
|
||||
Icons.Filled.Search,
|
||||
contentDescription = stringResource(R.string.search_place_action)
|
||||
)
|
||||
}
|
||||
if (searchPin != null) {
|
||||
IconButton(onClick = { searchPin = null }) {
|
||||
Icon(
|
||||
Icons.Filled.Delete,
|
||||
contentDescription = stringResource(R.string.search_place_close)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -518,7 +618,7 @@ fun MapScreen(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { ctx ->
|
||||
MapView(ctx).apply {
|
||||
setTileSource(TileSourceFactory.MAPNIK)
|
||||
setTileSource(selectedProvider.toTileSource())
|
||||
setMultiTouchControls(true)
|
||||
controller.setZoom(12.0)
|
||||
|
||||
@@ -557,12 +657,15 @@ fun MapScreen(
|
||||
mapView = this
|
||||
drawMapOverlays(
|
||||
mv = this,
|
||||
waypoints = waypoints,
|
||||
trackPoints = currentTrack,
|
||||
waypoints = if (layerState.showMarkers) waypoints else emptyList(),
|
||||
trackPoints = if (layerState.showTrack) currentTrack else emptyList(),
|
||||
primaryArgb = primaryColor.toArgb(),
|
||||
errorArgb = errorColor.toArgb(),
|
||||
trackArgb = trackColor.toArgb(),
|
||||
onWaypointTapped = { wp -> actionMenuWaypoint = wp }
|
||||
selectedWaypointId = actionMenuWaypoint?.id,
|
||||
onWaypointTapped = { wp -> actionMenuWaypoint = wp },
|
||||
searchPin = searchPin,
|
||||
showRadius = layerState.showRadius
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -570,12 +673,15 @@ fun MapScreen(
|
||||
mapView = mv
|
||||
drawMapOverlays(
|
||||
mv = mv,
|
||||
waypoints = waypoints,
|
||||
trackPoints = currentTrack,
|
||||
waypoints = if (layerState.showMarkers) waypoints else emptyList(),
|
||||
trackPoints = if (layerState.showTrack) currentTrack else emptyList(),
|
||||
primaryArgb = primaryColor.toArgb(),
|
||||
errorArgb = errorColor.toArgb(),
|
||||
trackArgb = trackColor.toArgb(),
|
||||
onWaypointTapped = { wp -> actionMenuWaypoint = wp }
|
||||
selectedWaypointId = actionMenuWaypoint?.id,
|
||||
onWaypointTapped = { wp -> actionMenuWaypoint = wp },
|
||||
searchPin = searchPin,
|
||||
showRadius = layerState.showRadius
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -618,11 +724,12 @@ fun MapScreen(
|
||||
}
|
||||
|
||||
// ─── Track-Statistik-Panel ───────────────────────────────────────
|
||||
// Bottom padding leaves a clear strip for the OSM attribution badge.
|
||||
AnimatedVisibility(
|
||||
visible = isRecording || currentTrack.isNotEmpty(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(start = 12.dp, bottom = 12.dp),
|
||||
.padding(start = 12.dp, bottom = 40.dp),
|
||||
enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
|
||||
exit = fadeOut() + slideOutVertically(targetOffsetY = { it })
|
||||
) {
|
||||
@@ -710,11 +817,12 @@ fun MapScreen(
|
||||
}
|
||||
|
||||
// ─── Editor-Hinweis (unten mittig) ──────────────────────────────
|
||||
// Raised above the OSM attribution strip so the mandatory badge is never covered.
|
||||
AnimatedVisibility(
|
||||
visible = !isRecording && currentTrack.isEmpty(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 16.dp),
|
||||
.padding(bottom = 40.dp),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
@@ -731,6 +839,26 @@ fun MapScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Layer-/3D-Overlay-FAB (v2.2.1) ──────────────────────────────
|
||||
// Schwebt rechts oben über der Karte, öffnet das Material-3-Panel
|
||||
// mit Layer-Switches und 3D-Vorbereitungs-Hinweis.
|
||||
MapLayerFab(
|
||||
state = layerState,
|
||||
onStateChange = { layerState = it },
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = 8.dp, end = 8.dp)
|
||||
)
|
||||
|
||||
// ─── OSM-Attribution (Pflicht) ───────────────────────────────────
|
||||
// Moved to bottom-left so the FAB stack at bottom-right cannot overlap it.
|
||||
// The track-stats card and editor hint above leave a clear strip for this badge.
|
||||
OsmAttributionBadge(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(start = 6.dp, bottom = 6.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,42 +880,73 @@ private fun drawMapOverlays(
|
||||
primaryArgb: Int,
|
||||
errorArgb: Int,
|
||||
trackArgb: Int,
|
||||
onWaypointTapped: (Waypoint) -> Unit
|
||||
selectedWaypointId: String?,
|
||||
onWaypointTapped: (Waypoint) -> Unit,
|
||||
searchPin: GeoPoint? = null,
|
||||
showRadius: Boolean = true
|
||||
) {
|
||||
val overlays = mv.overlays
|
||||
// Radius-Polygone, Wegpunkt-Marker und Track-Polylines entfernen
|
||||
// Radius-Polygone, Wegpunkt-Marker und Track-Polylines entfernen.
|
||||
// Mein-Standort-Puck überlebt (eigener Marker mit title="Mein Standort").
|
||||
overlays.removeAll(overlays.filterIsInstance<Polygon>().toSet())
|
||||
val waypointMarkers = overlays.filterIsInstance<Marker>()
|
||||
.filter { it.title != "Mein Standort" }
|
||||
overlays.removeAll(waypointMarkers.toSet())
|
||||
overlays.removeAll(overlays.filterIsInstance<Polyline>().toSet())
|
||||
|
||||
// GPS-Track zeichnen
|
||||
val density = mv.resources.displayMetrics.density
|
||||
|
||||
// GPS-Track zeichnen — kräftiger, mit weißem Halo für Kontrast auf jedem Untergrund.
|
||||
if (trackPoints.size >= 2) {
|
||||
val pts = trackPoints.map { GeoPoint(it.latitude, it.longitude) }
|
||||
// Halo (heller Rand)
|
||||
val halo = Polyline(mv).apply {
|
||||
setPoints(pts)
|
||||
outlinePaint.color = 0xFFFFFFFF.toInt()
|
||||
outlinePaint.strokeWidth = 18f * density / 2f
|
||||
outlinePaint.strokeCap = Paint.Cap.ROUND
|
||||
outlinePaint.strokeJoin = Paint.Join.ROUND
|
||||
outlinePaint.alpha = 230
|
||||
}
|
||||
overlays.add(halo)
|
||||
// Hauptlinie
|
||||
val polyline = Polyline(mv).apply {
|
||||
setPoints(trackPoints.map { GeoPoint(it.latitude, it.longitude) })
|
||||
setPoints(pts)
|
||||
outlinePaint.color = trackArgb
|
||||
outlinePaint.strokeWidth = 6f
|
||||
outlinePaint.alpha = 200
|
||||
outlinePaint.strokeWidth = 12f * density / 2f
|
||||
outlinePaint.strokeCap = Paint.Cap.ROUND
|
||||
outlinePaint.strokeJoin = Paint.Join.ROUND
|
||||
outlinePaint.alpha = 235
|
||||
}
|
||||
overlays.add(polyline)
|
||||
}
|
||||
|
||||
// Wegpunkte zeichnen
|
||||
// Wegpunkte zeichnen — größere, gut sichtbare Streckenpunkt-Handles
|
||||
for (wp in waypoints) {
|
||||
val geoPoint = GeoPoint(wp.latitude, wp.longitude)
|
||||
|
||||
// Radius-Kreis
|
||||
val circle = buildRadiusCircle(geoPoint, wp.radiusMeters.toDouble(), primaryArgb)
|
||||
overlays.add(circle)
|
||||
// Radius-Kreis (nur wenn Layer aktiv)
|
||||
if (showRadius) {
|
||||
val circle = buildRadiusCircle(geoPoint, wp.radiusMeters.toDouble(), primaryArgb)
|
||||
overlays.add(circle)
|
||||
}
|
||||
|
||||
// Marker mit Tap-Handler
|
||||
val isSelected = selectedWaypointId != null && wp.id == selectedWaypointId
|
||||
// Größerer Marker mit Kontrast-Halo. Ausgewählter Punkt: andere Farbe + größer.
|
||||
val handleBitmap = createWaypointHandleBitmap(
|
||||
context = mv.context,
|
||||
fillArgb = if (isSelected) errorArgb else primaryArgb,
|
||||
isSelected = isSelected
|
||||
)
|
||||
val marker = Marker(mv).apply {
|
||||
position = geoPoint
|
||||
title = wp.name
|
||||
snippet = "%.6f, %.6f · %dm".format(Locale.US, wp.latitude, wp.longitude, wp.radiusMeters.toInt())
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
alpha = if (wp.isActive) 1.0f else 0.4f
|
||||
// ANCHOR_CENTER, ANCHOR_CENTER → Touch-Trefferfläche entspricht der Bitmap-Größe und
|
||||
// sitzt zentriert über dem Punkt (deutlich größer als der frühere Default-Pin).
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||
alpha = if (wp.isActive) 1.0f else 0.55f
|
||||
icon = android.graphics.drawable.BitmapDrawable(mv.context.resources, handleBitmap)
|
||||
// infoWindow = null → Tap löst onMarkerClick aus
|
||||
setOnMarkerClickListener { _, _ ->
|
||||
onWaypointTapped(wp)
|
||||
@@ -797,9 +956,79 @@ private fun drawMapOverlays(
|
||||
overlays.add(marker)
|
||||
}
|
||||
|
||||
// Temporärer roter Such-Treffer-Pin (Nominatim/OSM). Verändert keine Daten.
|
||||
if (searchPin != null) {
|
||||
val pinBm = makeSearchPinBitmapShared(96)
|
||||
val pinMarker = Marker(mv).apply {
|
||||
position = searchPin
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
icon = android.graphics.drawable.BitmapDrawable(mv.context.resources, pinBm)
|
||||
title = null
|
||||
snippet = null
|
||||
subDescription = null
|
||||
infoWindow = null
|
||||
setOnMarkerClickListener { _, _ -> true }
|
||||
}
|
||||
overlays.add(pinMarker)
|
||||
}
|
||||
|
||||
mv.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet einen großen, kontraststarken Kreis-Handle für einen Streckenpunkt.
|
||||
* Die Bitmap-Größe bestimmt zugleich die Touch-Trefferfläche des osmdroid-Markers.
|
||||
* - Normaler Punkt: ca. 44dp Trefferfläche, sichtbarer Kreis ~26dp mit weißem Halo
|
||||
* - Ausgewählter Punkt: ca. 56dp Trefferfläche, sichtbarer Kreis ~36dp mit dunklem Außenrand
|
||||
*/
|
||||
private fun createWaypointHandleBitmap(
|
||||
context: android.content.Context,
|
||||
fillArgb: Int,
|
||||
isSelected: Boolean
|
||||
): Bitmap {
|
||||
val density = context.resources.displayMetrics.density
|
||||
// Touch-Trefferfläche (Bitmap-Kantenlänge) – mind. 44dp/56dp lt. Material Touch-Targets
|
||||
val touchDp = if (isSelected) 56f else 44f
|
||||
val sizePx = (touchDp * density).toInt()
|
||||
val bmp = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bmp)
|
||||
val cx = sizePx / 2f
|
||||
val cy = sizePx / 2f
|
||||
|
||||
// Sichtbarer Kreis-Radius
|
||||
val visibleRadiusDp = if (isSelected) 18f else 13f
|
||||
val visibleRadius = visibleRadiusDp * density
|
||||
val haloThicknessDp = if (isSelected) 4.5f else 3.5f
|
||||
val haloThickness = haloThicknessDp * density
|
||||
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
// Weicher Schatten-/Halo-Ring (weiß), damit der Punkt auf jedem Kartenuntergrund sichtbar bleibt
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = 0xFFFFFFFF.toInt()
|
||||
canvas.drawCircle(cx, cy, visibleRadius + haloThickness, paint)
|
||||
|
||||
// Füllung
|
||||
paint.color = fillArgb
|
||||
canvas.drawCircle(cx, cy, visibleRadius, paint)
|
||||
|
||||
// Kontrast-Außenrand (dunkel) – beim ausgewählten Punkt deutlich kräftiger
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeWidth = (if (isSelected) 3f else 2f) * density
|
||||
paint.color = 0xFF202020.toInt()
|
||||
canvas.drawCircle(cx, cy, visibleRadius, paint)
|
||||
|
||||
if (isSelected) {
|
||||
// Innerer heller Ring als zusätzliche „Selected"-Markierung
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeWidth = 2f * density
|
||||
paint.color = 0xFFFFFFFF.toInt()
|
||||
canvas.drawCircle(cx, cy, visibleRadius - 4f * density, paint)
|
||||
}
|
||||
|
||||
return bmp
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hilfsfunktionen: Standort-Puck, Marker, Radius-Kreis, Distanz
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user