Files
SnapAndSolve/docs/DamageListSystem.md
si2503 8342723e09 - Dokumentation
- Bereinigung von Code
2026-02-13 10:33:21 +01:00

388 lines
8.8 KiB
Markdown

# 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
```kotlin
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
```kotlin
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
```kotlin
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
```kotlin
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
```kotlin
@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<DamageWithDistance> | 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
```kotlin
@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
```kotlin
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<DamageWithDistance> - Sortiert nach Entfernung
**Ablauf:**
1. **Query:** Alle Features (`whereClause = "1=1"`)
2. **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
3. **Return:** Nach Distanz sortierte Liste
**Koordinaten-Transformation:**
```kotlin
GeometryEngine.projectOrNull(
featureGeometry,
userLocation.spatialReference
)
```
**Foto-Loading:**
```kotlin
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
```kotlin
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
```kotlin
fun getEmojiForType(typ: String): String
```
**Zweck:** Mapt Schadenstyp zu Emoji.
**Mapping:**
| Typ | Emoji |
|-----|-------|
| "Straße" | 🛣️ |
| "Gehweg" | 🚶 |
| "Fahrradweg" | 🚴 |
| "Beleuchtung" | 💡 |
| "Sonstiges" | 📍 |
---
## Verwendungsbeispiel
```kotlin
// 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)