Release GPS2Audio v2.5.44 POI category fix
This commit is contained in:
@@ -0,0 +1,931 @@
|
||||
package de.waypointaudio.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AltRoute
|
||||
import androidx.compose.material.icons.filled.DarkMode
|
||||
import androidx.compose.material.icons.filled.GpsFixed
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Landscape
|
||||
import androidx.compose.material.icons.filled.Layers
|
||||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.material.icons.filled.LocationCity
|
||||
import androidx.compose.material.icons.filled.Map
|
||||
import androidx.compose.material.icons.filled.MusicNote
|
||||
import androidx.compose.material.icons.filled.Nightlife
|
||||
import androidx.compose.material.icons.filled.PinDrop
|
||||
import androidx.compose.material.icons.filled.Route
|
||||
import androidx.compose.material.icons.filled.RadioButtonChecked
|
||||
import androidx.compose.material.icons.filled.Timeline
|
||||
import androidx.compose.material.icons.filled.Label
|
||||
import androidx.compose.material.icons.filled.LabelOff
|
||||
import androidx.compose.material.icons.filled.Place
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SmallFloatingActionButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.waypointaudio.R
|
||||
import de.waypointaudio.data.MapBaseStyle
|
||||
import de.waypointaudio.data.PoiLayerSettings
|
||||
|
||||
/**
|
||||
* Daten-Container für die Layer-Schalter eines Kartenscreens.
|
||||
*
|
||||
* v2.4.1 — Erweitertes Basis-Stil-Menü:
|
||||
* - Modern hell, Dunkel, Topografisch, Terrain/3D, Satellit
|
||||
* v2.5.38 — POI-Layer-Kategorien ergänzt.
|
||||
*/
|
||||
data class MapLayerState(
|
||||
val followLocation: Boolean = true,
|
||||
val showMarkers: Boolean = true,
|
||||
val showRadius: Boolean = true,
|
||||
val showTrack: Boolean = true,
|
||||
val showAudioLength: Boolean = true,
|
||||
val baseStyle: MapBaseStyle = MapBaseStyle.MODERN_2D,
|
||||
val showAudioLines: Boolean = true,
|
||||
val poiLayers: PoiLayerSettings = PoiLayerSettings(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Floating-Button + BottomSheet für Karten-Ebenen.
|
||||
*
|
||||
* v2.4.1 — Neue auswählbare Basis-Stile: Modern hell, Dunkel, Topo, Terrain/3D,
|
||||
* Satellit. Auswahl wird über [MapStyleStore] persistiert.
|
||||
* v2.5.38 — POI-Layer-Abschnitt (Sehenswürdigkeiten, Infrastruktur, Gastronomie,
|
||||
* Mobilität) als Chips. Karteninfo-Dialog (Attribution). Kartentyp persistiert.
|
||||
* v2.5.39 — Sheet-Inhalt scrollbar (verticalScroll), damit POI-Kategorien und
|
||||
* Karteninfo auch auf kleinen Displays erreichbar sind.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MapLayerFab(
|
||||
state: MapLayerState,
|
||||
onStateChange: (MapLayerState) -> Unit,
|
||||
customSatelliteUrl: String = "",
|
||||
onCustomSatelliteUrlChange: (String) -> Unit = {},
|
||||
customTopoUrl: String = "",
|
||||
onCustomTopoUrlChange: (String) -> Unit = {},
|
||||
customTerrainDemUrl: String = "",
|
||||
onCustomTerrainDemUrlChange: (String) -> Unit = {},
|
||||
perspectiveTilt: Int = 35,
|
||||
onPerspectiveTiltChange: (Int) -> Unit = {},
|
||||
onShowRoutingInfo: () -> Unit = {},
|
||||
onShowRoutePlanner: () -> Unit = {},
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var showSheet by remember { mutableStateOf(false) }
|
||||
var showStyleHint by remember { mutableStateOf<MapBaseStyle?>(null) }
|
||||
var showAttributionDialog by remember { mutableStateOf(false) }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
Box(modifier = modifier) {
|
||||
SmallFloatingActionButton(
|
||||
onClick = { showSheet = true },
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Layers,
|
||||
contentDescription = stringResource(R.string.map_layers_open)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showSheet) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { showSheet = false },
|
||||
sheetState = sheetState
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 600.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Layers, contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
stringResource(R.string.map_layers_title),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(R.string.map_layers_base_section),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
// v2.5.37 — Erste Zeile: Modern, Dunkel, Outdoor
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
BaseStyleChip(
|
||||
label = stringResource(R.string.map_layers_base_2d),
|
||||
icon = Icons.Filled.LightMode,
|
||||
selected = state.baseStyle == MapBaseStyle.MODERN_2D,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(state.copy(baseStyle = MapBaseStyle.MODERN_2D))
|
||||
}
|
||||
)
|
||||
BaseStyleChip(
|
||||
label = stringResource(R.string.map_layers_base_dark),
|
||||
icon = Icons.Filled.DarkMode,
|
||||
selected = state.baseStyle == MapBaseStyle.DARK,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(state.copy(baseStyle = MapBaseStyle.DARK))
|
||||
}
|
||||
)
|
||||
BaseStyleChip(
|
||||
label = stringResource(R.string.map_layers_base_outdoor),
|
||||
icon = Icons.Filled.Landscape,
|
||||
selected = state.baseStyle == MapBaseStyle.OUTDOOR,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(state.copy(baseStyle = MapBaseStyle.OUTDOOR))
|
||||
}
|
||||
)
|
||||
}
|
||||
// v2.5.37 — Zweite Zeile: 3D Stadt, Klassisch
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
BaseStyleChip(
|
||||
label = stringResource(R.string.map_layers_base_city3d),
|
||||
icon = Icons.Filled.LocationCity,
|
||||
selected = state.baseStyle == MapBaseStyle.CITY_3D,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(state.copy(baseStyle = MapBaseStyle.CITY_3D))
|
||||
}
|
||||
)
|
||||
BaseStyleChip(
|
||||
label = stringResource(R.string.map_layers_base_classic),
|
||||
icon = Icons.Filled.Map,
|
||||
selected = state.baseStyle == MapBaseStyle.CLASSIC,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(state.copy(baseStyle = MapBaseStyle.CLASSIC))
|
||||
}
|
||||
)
|
||||
// Platzhalter rechts für symmetrisches Layout
|
||||
Spacer(Modifier.weight(1f))
|
||||
}
|
||||
|
||||
// v2.5.37 — Info-Block für Dunkel-Stil
|
||||
if (state.baseStyle == MapBaseStyle.DARK) {
|
||||
StyleConfigSurface(
|
||||
tone = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.55f)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Filled.DarkMode,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.map_layers_dark_note),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
TextButton(onClick = { showStyleHint = MapBaseStyle.DARK }) {
|
||||
Text(stringResource(R.string.map_layers_satellite_info_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// v2.5.37 — Info-Block für 3D-Stadt-Stil
|
||||
if (state.baseStyle == MapBaseStyle.CITY_3D) {
|
||||
StyleConfigSurface(
|
||||
tone = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.55f)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Filled.LocationCity,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.map_layers_city3d_note),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
TextButton(onClick = { showStyleHint = MapBaseStyle.CITY_3D }) {
|
||||
Text(stringResource(R.string.map_layers_satellite_info_button))
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
stringResource(R.string.map_layers_terrain_pitch_label) +
|
||||
": " + perspectiveTilt + "°",
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
Slider(
|
||||
value = perspectiveTilt.coerceIn(0, 60).toFloat(),
|
||||
valueRange = 0f..60f,
|
||||
steps = 11,
|
||||
onValueChange = { onPerspectiveTiltChange(it.toInt()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// v2.5.37 — Info-Block für Outdoor-Stil
|
||||
if (state.baseStyle == MapBaseStyle.OUTDOOR) {
|
||||
StyleConfigSurface(
|
||||
tone = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.4f)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Filled.Landscape,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.map_layers_outdoor_note),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
TextButton(onClick = { showStyleHint = MapBaseStyle.OUTDOOR }) {
|
||||
Text(stringResource(R.string.map_layers_satellite_info_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(R.string.map_layers_overlays_section),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
LayerSwitchRow(
|
||||
icon = Icons.Filled.GpsFixed,
|
||||
label = stringResource(R.string.map_layers_follow),
|
||||
checked = state.followLocation,
|
||||
onCheckedChange = { onStateChange(state.copy(followLocation = it)) }
|
||||
)
|
||||
LayerSwitchRow(
|
||||
icon = Icons.Filled.PinDrop,
|
||||
label = stringResource(R.string.map_layers_markers),
|
||||
checked = state.showMarkers,
|
||||
onCheckedChange = { onStateChange(state.copy(showMarkers = it)) }
|
||||
)
|
||||
LayerSwitchRow(
|
||||
icon = Icons.Filled.RadioButtonChecked,
|
||||
label = stringResource(R.string.map_layers_radius),
|
||||
checked = state.showRadius,
|
||||
onCheckedChange = { onStateChange(state.copy(showRadius = it)) }
|
||||
)
|
||||
LayerSwitchRow(
|
||||
icon = Icons.Filled.MusicNote,
|
||||
label = stringResource(R.string.map_layers_audio_length),
|
||||
checked = state.showAudioLength,
|
||||
onCheckedChange = { onStateChange(state.copy(showAudioLength = it)) }
|
||||
)
|
||||
LayerSwitchRow(
|
||||
icon = Icons.Filled.Timeline,
|
||||
label = stringResource(R.string.map_layers_audio_lines),
|
||||
checked = state.showAudioLines,
|
||||
onCheckedChange = { onStateChange(state.copy(showAudioLines = it)) }
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.map_layers_audio_lines_hint),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.75f),
|
||||
modifier = Modifier.padding(start = 28.dp)
|
||||
)
|
||||
if (state.showAudioLines) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 28.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
AudioLineLegendChip(
|
||||
color = 0xFF66BB6A.toInt(),
|
||||
label = stringResource(R.string.map_layers_audio_lines_legend_short)
|
||||
)
|
||||
AudioLineLegendChip(
|
||||
color = 0xFFFFA726.toInt(),
|
||||
label = stringResource(R.string.map_layers_audio_lines_legend_medium)
|
||||
)
|
||||
AudioLineLegendChip(
|
||||
color = 0xFFE53935.toInt(),
|
||||
label = stringResource(R.string.map_layers_audio_lines_legend_long)
|
||||
)
|
||||
}
|
||||
}
|
||||
LayerSwitchRow(
|
||||
icon = Icons.Filled.Timeline,
|
||||
label = stringResource(R.string.map_layers_track),
|
||||
checked = state.showTrack,
|
||||
onCheckedChange = { onStateChange(state.copy(showTrack = it)) }
|
||||
)
|
||||
|
||||
// ─── v2.5.38 — POI-Layer-Abschnitt ───────────────────────────
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
stringResource(R.string.poi_section_title),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
if (state.baseStyle == MapBaseStyle.CLASSIC) {
|
||||
Text(
|
||||
stringResource(R.string.poi_unavailable_classic),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
stringResource(R.string.poi_section_subtitle),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.75f),
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
// Zeile 1: Sehenswürdigkeiten + Infrastruktur
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
PoiCategoryChip(
|
||||
label = stringResource(R.string.poi_cat_sights),
|
||||
icon = Icons.Filled.Place,
|
||||
selected = state.poiLayers.showSights,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(
|
||||
state.copy(
|
||||
poiLayers = state.poiLayers.copy(
|
||||
showSights = !state.poiLayers.showSights
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
PoiCategoryChip(
|
||||
label = stringResource(R.string.poi_cat_infrastructure),
|
||||
icon = Icons.Filled.LocationOn,
|
||||
selected = state.poiLayers.showInfrastructure,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(
|
||||
state.copy(
|
||||
poiLayers = state.poiLayers.copy(
|
||||
showInfrastructure = !state.poiLayers.showInfrastructure
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
// Zeile 2: Gastronomie + Mobilität
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
PoiCategoryChip(
|
||||
label = stringResource(R.string.poi_cat_gastronomy),
|
||||
icon = Icons.Filled.Nightlife,
|
||||
selected = state.poiLayers.showGastronomy,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(
|
||||
state.copy(
|
||||
poiLayers = state.poiLayers.copy(
|
||||
showGastronomy = !state.poiLayers.showGastronomy
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
PoiCategoryChip(
|
||||
label = stringResource(R.string.poi_cat_mobility),
|
||||
icon = Icons.Filled.Route,
|
||||
selected = state.poiLayers.showMobility,
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
onStateChange(
|
||||
state.copy(
|
||||
poiLayers = state.poiLayers.copy(
|
||||
showMobility = !state.poiLayers.showMobility
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
// v2.5.42 — POI-Namen-Toggle: standardmäßig deaktiviert
|
||||
val anyPoiActive = state.poiLayers.showSights ||
|
||||
state.poiLayers.showGastronomy ||
|
||||
state.poiLayers.showMobility ||
|
||||
state.poiLayers.showInfrastructure
|
||||
if (anyPoiActive) {
|
||||
LayerSwitchRow(
|
||||
icon = if (state.poiLayers.showPoiNames)
|
||||
Icons.Filled.Label else Icons.Filled.LabelOff,
|
||||
label = "POI-Namen anzeigen",
|
||||
checked = state.poiLayers.showPoiNames,
|
||||
onCheckedChange = {
|
||||
onStateChange(
|
||||
state.copy(
|
||||
poiLayers = state.poiLayers.copy(
|
||||
showPoiNames = it
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── v2.5.38 — Karteninfo / Attribution ──────────────────────
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Info,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.map_info_dialog_title),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
"OpenFreeMap · OpenMapTiles · © OpenStreetMap-Mitwirkende",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.78f)
|
||||
)
|
||||
}
|
||||
TextButton(onClick = { showAttributionDialog = true }) {
|
||||
Text(stringResource(R.string.map_info_open))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// v2.4.2 — Routenplaner-Eintrag im Layer-Panel.
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.55f),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.AltRoute,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.route_planner_open_short),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.route_planner_subtitle),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.78f)
|
||||
)
|
||||
}
|
||||
TextButton(onClick = onShowRoutePlanner) {
|
||||
Text(stringResource(R.string.route_planner_open))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.AltRoute,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.routing_poi_open),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.routing_poi_search_attribution),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.75f)
|
||||
)
|
||||
}
|
||||
TextButton(onClick = onShowRoutingInfo) {
|
||||
Text(stringResource(R.string.routing_poi_open))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = { showSheet = false },
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
) {
|
||||
Text(stringResource(R.string.map_layers_close))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// v2.5.38 — Stil-Info-Dialog (unverändert aus v2.5.37)
|
||||
showStyleHint?.let { style ->
|
||||
val title: String
|
||||
val body: String
|
||||
when (style) {
|
||||
MapBaseStyle.DARK -> {
|
||||
title = stringResource(R.string.map_layers_base_dark)
|
||||
body = stringResource(R.string.map_layers_dark_info)
|
||||
}
|
||||
MapBaseStyle.OUTDOOR -> {
|
||||
title = stringResource(R.string.map_layers_base_outdoor)
|
||||
body = stringResource(R.string.map_layers_outdoor_info)
|
||||
}
|
||||
MapBaseStyle.CITY_3D -> {
|
||||
title = stringResource(R.string.map_layers_base_city3d)
|
||||
body = stringResource(R.string.map_layers_city3d_info)
|
||||
}
|
||||
MapBaseStyle.CLASSIC -> {
|
||||
title = stringResource(R.string.map_layers_base_classic)
|
||||
body = stringResource(R.string.map_layers_classic_info)
|
||||
}
|
||||
else -> {
|
||||
title = stringResource(R.string.map_layers_base_2d)
|
||||
body = stringResource(R.string.map_layers_modern_info)
|
||||
}
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { showStyleHint = null },
|
||||
title = { Text(title) },
|
||||
text = { Text(body) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showStyleHint = null }) {
|
||||
Text(stringResource(R.string.version_notice_ok))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// v2.5.38 — Karteninfo / Attribution-Dialog
|
||||
if (showAttributionDialog) {
|
||||
MapAttributionDialog(onDismiss = { showAttributionDialog = false })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* v2.5.38 — Vollständiger Karteninfo-/Attribution-Dialog.
|
||||
*
|
||||
* Zeigt rechtlich korrekte Namensnennung für:
|
||||
* - OpenFreeMap (openfreemap.org)
|
||||
* - OpenMapTiles (openmaptiles.org)
|
||||
* - OpenStreetMap-Mitwirkende (ODbL)
|
||||
*
|
||||
* Links werden als anklickbare Texte dargestellt.
|
||||
*/
|
||||
@Composable
|
||||
fun MapAttributionDialog(onDismiss: () -> Unit) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
Icons.Filled.Info,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.map_info_dialog_title))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
// Kartenstil
|
||||
Text(
|
||||
stringResource(R.string.map_info_style_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.map_info_style_source),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
// Kartendaten
|
||||
Text(
|
||||
stringResource(R.string.map_info_data_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.map_info_data_text),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
// Links
|
||||
Text(
|
||||
stringResource(R.string.map_info_links_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
// openfreemap.org
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.35f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
runCatching {
|
||||
uriHandler.openUri("https://openfreemap.org")
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"↗ " + stringResource(R.string.map_info_link_ofm),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// openmaptiles.org
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.35f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
runCatching {
|
||||
uriHandler.openUri("https://openmaptiles.org")
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"↗ " + stringResource(R.string.map_info_link_omt),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// openstreetmap.org/copyright
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.35f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
runCatching {
|
||||
uriHandler.openUri("https://www.openstreetmap.org/copyright")
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"↗ " + stringResource(R.string.map_info_link_osm),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
"Lizenz: ODbL (Open Database License). Namensnennung erforderlich.",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.75f)
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.map_info_close))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StyleConfigSurface(
|
||||
tone: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||
content: @Composable androidx.compose.foundation.layout.ColumnScope.() -> Unit
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = tone,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BaseStyleChip(
|
||||
label: String,
|
||||
icon: ImageVector,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val container = if (selected) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f)
|
||||
}
|
||||
val content = if (selected) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
Surface(
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
color = container,
|
||||
modifier = modifier.clickable(onClick = onClick)
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 10.dp, horizontal = 6.dp)
|
||||
) {
|
||||
Icon(icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = content)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
label,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* v2.5.38 — POI-Kategorie-Chip (kompaktes Toggle-Chip-Design).
|
||||
*/
|
||||
@Composable
|
||||
private fun PoiCategoryChip(
|
||||
label: String,
|
||||
icon: ImageVector,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val container = if (selected) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f)
|
||||
}
|
||||
val content = if (selected) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = container,
|
||||
modifier = modifier.clickable(onClick = onClick)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
) {
|
||||
Icon(icon, contentDescription = null, modifier = Modifier.size(16.dp), tint = content)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
label,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AudioLineLegendChip(color: Int, label: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(2.dp),
|
||||
color = androidx.compose.ui.graphics.Color(color),
|
||||
modifier = Modifier
|
||||
.width(14.dp)
|
||||
.height(4.dp)
|
||||
) {}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(label, style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LayerSwitchRow(
|
||||
icon: ImageVector,
|
||||
label: String,
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f)
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Text(
|
||||
label,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user