8.8 KiB
DamageListDialog
Zweck: Dialog zur Anzeige von Straßenschäden in der Nähe mit Foto-Vorschau, Entfernungs- und Relevanz-Filter.
Features: GPS-basierte Entfernungsberechnung, Sortierung nach Distanz/Relevanz, dynamischer Radius-Filter.
Data Classes
DamageWithDistance
data class DamageWithDistance(
val feature: ArcGISFeature,
val distanceInMeters: Double?,
val typ: String,
val beschreibung: String,
val objectId: Long,
val rating: Int,
val photo: ImageBitmap? = null
)
Zweck: Container für Feature mit berechneter Distanz und geladenen Daten.
Properties:
| Name | Typ | Beschreibung |
|---|---|---|
feature |
ArcGISFeature | Originales ArcGIS Feature |
distanceInMeters |
Double? | Entfernung zum Nutzer in Metern |
typ |
String | Schadenstyp (z.B. "Straße") |
beschreibung |
String | Schadensbeschreibung |
objectId |
Long | Feature OBJECTID |
rating |
Int | Community-Bewertungen (communitycounter) |
photo |
ImageBitmap? | Erstes Attachment als Bitmap (optional) |
Enums
SortBy
enum class SortBy {
DISTANCE, // Nach Entfernung sortieren (nächste zuerst)
RELEVANCE // Nach Relevanz sortieren (höchste communitycounter zuerst)
}
Zweck: Sortierungs-Modi für Schadensliste.
Functions
calculateDistance
fun calculateDistance(
lat1: Double,
lon1: Double,
lat2: Double,
lon2: Double
): Double
Zweck: Berechnet Luftlinie zwischen zwei GPS-Koordinaten.
Parameter:
| Name | Typ | Beschreibung |
|---|---|---|
lat1 |
Double | Breitengrad Punkt 1 |
lon1 |
Double | Längengrad Punkt 1 |
lat2 |
Double | Breitengrad Punkt 2 |
lon2 |
Double | Längengrad Punkt 2 |
Return: Entfernung in Metern
Algorithmus: Haversine-Formel
R = 6371000.0 // Erdradius in Metern
dLat = toRadians(lat2 - lat1)
dLon = toRadians(lon2 - lon1)
a = sin²(dLat/2) + cos(lat1) * cos(lat2) * sin²(dLon/2)
c = 2 * atan2(√a, √(1-a))
distance = R * c
Composables
DamageListDialog
@Composable
fun DamageListDialog(
onDismiss: () -> Unit,
onDamageClick: (ArcGISFeature) -> Unit,
mapViewModel: MapViewModel
)
Zweck: Haupt-Dialog mit Filter- und Sortieroptionen.
Parameter:
| Name | Typ | Beschreibung |
|---|---|---|
onDismiss |
() -> Unit | Callback zum Schließen |
onDamageClick |
(ArcGISFeature) -> Unit | Callback bei Feature-Auswahl |
mapViewModel |
MapViewModel | ViewModel für Feature-Zugriff |
State Management
| State | Typ | Default | Beschreibung |
|---|---|---|---|
damages |
List | emptyList() | Geladene Schäden |
isLoading |
Boolean | true | Lade-Status |
maxDistance |
Float | 1000f | Radius-Filter in Metern |
userLocation |
Point? | null | GPS-Position |
errorMessage |
String? | null | Fehlermeldung |
sortBy |
SortBy | DISTANCE | Sortierungs-Modus |
minRelevance |
Int | 0 | Minimale Bewertungen |
Filter-Optionen
1. Entfernungs-Filter:
- Range: 100m - 5000m (5km)
- Steps: 48
- UI: Slider mit Icon 📍
- Anzeige: "{Distanz} m" oder "{Distanz} km"
2. Sortierung:
- DISTANCE: Nach Entfernung (nächste zuerst)
- RELEVANCE: Nach communitycounter (höchste zuerst)
- UI: FilterChips "📍 Entfernung" / "👥 Relevanz"
3. Relevanz-Filter (nur bei RELEVANCE):
- Range: 0 - 50+
- Steps: 49
- UI: Slider mit Icon 📈
- Anzeige: "Min. Bewertungen: {count}+"
UI-Zustände
Loading:
CircularProgressIndicator
"Lade Schäden..."
Error:
⚠️ Emoji
Fehlermeldung (rot)
Empty:
🔍 Emoji
"Keine Schäden im Umkreis"
oder "Keine Schäden mit dieser Bewertung"
Success:
LazyColumn mit DamageListItemWithPhoto
DamageListItemWithPhoto
@Composable
fun DamageListItemWithPhoto(
damage: DamageWithDistance,
onClick: () -> Unit
)
Zweck: Einzelne Zeile in der Schadensliste.
Parameter:
| Name | Typ | Beschreibung |
|---|---|---|
damage |
DamageWithDistance | Anzuzeigender Schaden |
onClick |
() -> Unit | Callback bei Klick |
Layout:
┌─────────────────────────────────────┐
│ ┌──────┐ {Emoji} {Typ} │
│ │ │ {Beschreibung} │
│ │ Foto │ 📍 {Distanz} 👥 {Rating} │
│ └──────┘ │
└─────────────────────────────────────┘
Foto-Box:
- Größe: 100x100 dp
- Shape: RoundedCornerShape (links abgerundet)
- Fallback: 📷 Emoji auf grauem Hintergrund
Info-Bereich:
- Typ: Emoji + Name (Bold)
- Beschreibung: Max. 40 Zeichen + "..."
- Distanz: 📍 + formatDistance()
- Rating: 👥 + communitycounter (farbcodiert)
Extension Functions
MapViewModel.loadDamagesNearby
suspend fun MapViewModel.loadDamagesNearby(
userLocation: Point,
maxDistanceMeters: Double,
context: Context
): List<DamageWithDistance>
Zweck: Lädt alle Features im Umkreis mit Distanz-Berechnung und Foto-Loading.
Parameter:
| Name | Typ | Beschreibung |
|---|---|---|
userLocation |
Point | GPS-Position des Nutzers |
maxDistanceMeters |
Double | Maximaler Radius |
context |
Context | Für Toast-Nachrichten |
Return: List - Sortiert nach Entfernung
Ablauf:
- Query: Alle Features (
whereClause = "1=1") - Für jedes Feature:
- Load Feature-Daten
- Geometrie extrahieren
- Koordinaten-Transformation (GeometryEngine.projectOrNull)
- Distanz-Berechnung (Haversine)
- Filter: nur wenn ≤ maxDistanceMeters
- Attribute extrahieren (Typ, Beschreibung, OBJECTID, communitycounter)
- Erstes Attachment laden und zu Bitmap konvertieren
- Return: Nach Distanz sortierte Liste
Koordinaten-Transformation:
GeometryEngine.projectOrNull(
featureGeometry,
userLocation.spatialReference
)
Foto-Loading:
val attachments = feature.fetchAttachments().getOrNull()
val firstAttachment = attachments?.firstOrNull()
val data = firstAttachment?.fetchData().getOrNull()
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
Error Handling:
- Try-Catch um gesamte Funktion
- Toast bei Fehler
- Return emptyList() bei Fehler
Debug-Logging:
- Query-Status
- Feature-Count
- Distanz-Berechnungen
- Transformation-Fehler
Helper Functions
formatDistance
fun formatDistance(meters: Double?): String
Zweck: Formatiert Distanz-Anzeige.
Logic:
< 1000m: "{meters} m"≥ 1000m: "{km} km" (1 Dezimalstelle)
Beispiele:
250.0→ "250 m"1500.0→ "1.5 km"
getEmojiForType
fun getEmojiForType(typ: String): String
Zweck: Mapt Schadenstyp zu Emoji.
Mapping:
| Typ | Emoji |
|---|---|
| "Straße" | 🛣️ |
| "Gehweg" | 🚶 |
| "Fahrradweg" | 🚴 |
| "Beleuchtung" | 💡 |
| "Sonstiges" | 📍 |
Verwendungsbeispiel
// In MainScreen
var showDamageList by remember { mutableStateOf(false) }
if (showDamageList) {
DamageListDialog(
onDismiss = { showDamageList = false },
onDamageClick = { feature ->
mapViewModel.selectedFeature = feature
mapViewModel.showFeatureInfo = true
showDamageList = false
},
mapViewModel = mapViewModel
)
}
// Aus SideSlider
SliderMenuItem(
text = "Schadensliste",
icon = Icons.Default.FormatListNumbered,
onClick = { showDamageList = true }
)
Feature-Attribute
Erforderliche Felder:
| Feldname | Typ | Beschreibung |
|---|---|---|
Typ |
String | Schadenstyp |
Beschreibung |
String | Schadensbeschreibung |
OBJECTID |
Long | Feature-ID |
communitycounter |
Int | Community-Bewertungen |
| Attachments | Blob | Fotos (optional) |
Performance-Hinweise
Optimierungen:
- Fotos werden parallel geladen (async per Feature)
- Distanz-Filter reduziert verarbeitete Features
- LazyColumn für effizientes Rendering
Potenzielle Bottlenecks:
- Foto-Loading bei vielen Features (async pro Feature)
- Koordinaten-Transformation (GeometryEngine)
- Haversine-Berechnung (für jedes Feature)
Technische Details
- UI Framework: Jetpack Compose
- Threading: Dispatchers.IO für Feature-Loading
- Geometrie: ArcGIS GeometryEngine für Transformation
- Distanz: Haversine-Formel
- Fotos: BitmapFactory + ImageBitmap
- State: remember/mutableStateOf (lokaler State)