- Dokumentation

- Bereinigung von Code
This commit is contained in:
2026-02-13 10:33:21 +01:00
parent 48802606e8
commit 8342723e09
16 changed files with 1070 additions and 169 deletions

View File

@@ -1,40 +1,77 @@
# Album/Kamera-System Dokumentation
# AlbumViewModel
## AlbumViewModel
```kotlin
class AlbumViewModel(private val coroutineContext: CoroutineContext) : ViewModel()
```
**Zweck:** Verwaltung von Bildauswahl und Kamera-Aufnahmen für Schadensmeldungen.
### Properties
| Name | Typ | Beschreibung |
|------|-----|--------------|
| `viewStateFlow` | `StateFlow<AlbumViewState>` | Read-only State für UI-Komponenten |
### Methoden
#### `onReceive(intent: Intent)`
Verarbeitet Benutzeraktionen für Bild-Verwaltung.
**Parameter:**
- `intent: Intent` - Benutzeraktion (siehe Intent-Klasse)
**Verwendete Intents:**
- `OnPermissionGrantedWith(Context)` - Erstellt temp. Datei für Kamera
- `OnFinishPickingImagesWith(Context, List<Uri>)` - Lädt Bilder aus Galerie
- `OnImageSavedWith(Context)` - Speichert Kamera-Aufnahme
- `OnImageSavingCanceled` - Verwirft temp. Datei
- `OnPermissionDenied` - Loggt Permission-Verweigerung
**Deprecated Intents:** `OnPermissionGranted`, `OnFinishPickingImages`, `OnImageSaved` (ohne Context)
#### `clearSelection()`
Löscht alle ausgewählten Bilder aus dem State.
## Übersicht
Die Klasse `AlbumViewModel` ist für die zentrale Verwaltung von Bilddaten innerhalb der App zuständig. Sie fungiert als Schnittstelle zwischen der Kamera-Hardware, der System-Galerie und der Benutzeroberfläche. Das ViewModel verarbeitet asynchrone Bildoperationen und stellt den aktuellen Status über einen reaktiven StateFlow bereit.
---
## 1. Klasse: AlbumViewModel
`class AlbumViewModel(private val coroutineContext: CoroutineContext) : ViewModel()`
### Konstruktor-Parameter
| Parameter | Typ | Beschreibung |
| :--- | :--- | :--- |
| `coroutineContext` | `CoroutineContext` | Der Kontext, in dem die Coroutines für Bildoperationen ausgeführt werden. |
---
## 2. Status-Management (State)
### `viewStateFlow: StateFlow<AlbumViewState>`
Ein observierbarer Datenstrom, der den aktuellen Zustand der Bildverwaltung liefert. Er basiert auf der Datenklasse `AlbumViewState`.
**Wichtige Felder im State:**
* **`tempFileUrl`**: Enthält die URL der temporären Datei, in der das mit der Kamera aufgenommene Bild zwischengespeichert wird.
* **`selectedPictures`**: Enthält die Liste der mit der Kamera aufgenommenen oder aus der Galerie ausgewählten Bilder (als `ImageBitmap`).
---
## 3. Zentrale Methoden
### `onReceive(intent: Intent)`
Diese Methode ist der zentrale Einstiegspunkt für alle Aktionen. Sie verarbeitet verschiedene `Intent`-Typen:
| Intent | Beschreibung |
| :--- | :--- |
| `OnPermissionGrantedWith` | Wird aufgerufen, wenn die Kamera-Berechtigung erteilt wurde. Erstellt eine temporäre Datei (`.jpg`) im Cache-Verzeichnis und generiert eine Inhalts-URI via `FileProvider`. |
| `OnPermissionDenied` | Loggt die Verweigerung der Kamera-Berechtigung durch den Nutzer. |
| `OnFinishPickingImagesWith` | Verarbeitet Bilder, die aus der Galerie ausgewählt wurden. Die URIs werden in `ImageBitmap` konvertiert und der Liste hinzugefügt. |
| `OnImageSavedWith` | Wird nach einer erfolgreichen Kameraaufnahme aufgerufen. Dekodiert das Bild aus der `tempFileUrl` und fügt es der Auswahl hinzu. |
| `OnImageSavingCanceled` | Setzt die `tempFileUrl` zurück, falls der Aufnahmevorgang abgebrochen wurde. |
### `clearSelection()`
Setzt die Liste der ausgewählten Bilder (`selectedPictures`) auf eine leere Liste zurück.
---
## 4. Funktionsweise & Datenfluss
### Bildverarbeitung (Galerie)
Beim Auswählen von Bildern aus der Galerie werden die `InputStreams` der bereitgestellten URIs ausgelesen. Um Speicher effizient zu nutzen, werden die Byte-Arrays mithilfe von `BitmapFactory` dekodiert und anschließend als Compose-kompatible `ImageBitmap` gespeichert.
### Bildverarbeitung (Kamera)
1. **Vorbereitung**: Ein `File.createTempFile` erstellt einen Platzhalter im Cache.
2. **Sicherheit**: Der `FileProvider` wandelt den Dateipfad in eine sichere URI um, damit die Kamera-App darauf zugreifen kann.
3. **Abschluss**: Nach der Aufnahme wird `ImageDecoder` genutzt, um die Datei in eine Bitmap umzuwandeln.
---
## 5. Technische Voraussetzungen
### FileProvider-Konfiguration
Damit die Kamera-App Bilder speichern kann, muss in der `AndroidManifest.xml` ein Provider definiert sein, der auf `${BuildConfig.APPLICATION_ID}.provider` hört.
### Abhängigkeiten
* **androidx.lifecycle:lifecycle-viewmodel-ktx**: Für den `viewModelScope`.
* **kotlinx-coroutines**: Für die asynchrone Verarbeitung der Bilddaten.
* **androidx.compose.ui:ui-graphics**: Für die Konvertierung in `ImageBitmap`.
---
## Fehlerbehandlung
* **NULL-Werte**: Falls ein InputStream nicht gelesen werden kann, wird eine Fehlermeldung geloggt, ohne die App zum Absturz zu bringen.
* **Speichermanagement**: Bilder werden als Bitmaps im Speicher gehalten. Bei sehr großen Mengen sollte eine Skalierung (Sampling) in der `BitmapFactory` implementiert werden.

View File

@@ -0,0 +1,285 @@
# DamageFilterSystem
**Zweck:** Filter-System für Feature Layer. Ermöglicht Filterung nach Schadenstyp, Bearbeitungsstatus und Datum (unabhängig kombinierbar).
**Komponenten:** UI-Dialog + Extension-Funktionen für MapViewModel.
---
## FilterCheckboxItem
```kotlin
@Composable
fun FilterCheckboxItem(
label: String,
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit,
emoji: String = ""
)
```
**Zweck:** Wiederverwendbare Checkbox-Komponente mit Label und Emoji.
**Parameter:**
| Name | Typ | Beschreibung |
|------|-----|--------------|
| `label` | String | Angezeigter Text |
| `isChecked` | Boolean | Checkbox-Status |
| `onCheckedChange` | (Boolean) -> Unit | Callback bei Status-Änderung |
| `emoji` | String | Icon/Emoji rechts (default: "•") |
---
## DamageFilterDialog
```kotlin
@Composable
fun DamageFilterDialog(
damageTypes: List<String>,
currentFilters: Set<String>,
onDismiss: () -> Unit,
onApplyFilter: (Set<String>, Set<String>, LocalDate?, LocalDate?) -> Unit
)
```
**Zweck:** Vollbildschirm-Dialog zur Auswahl von Filter-Kriterien.
**Parameter:**
| Name | Typ | Beschreibung |
|------|-----|--------------|
| `damageTypes` | List<String> | Verfügbare Schadenstypen (z.B. "Straße", "Gehweg") |
| `currentFilters` | Set<String> | Aktuell aktive Typ-Filter |
| `onDismiss` | () -> Unit | Callback zum Schließen des Dialogs |
| `onApplyFilter` | (Set<String>, Set<String>, LocalDate?, LocalDate?) -> Unit | Callback mit (Typen, Status, StartDatum, EndDatum) |
### Filter-Typen
**1. Schadenstypen:**
- Straße 🛣️
- Gehweg 🚶
- Fahrradweg 🚴
- Beleuchtung 💡
- Sonstiges 📍
**2. Bearbeitungsstatus:**
- neu 🔴
- in Bearbeitung 🟠
- Schaden behoben 🟢
**3. Datums-Filter:**
- Von-Datum (dd.MM.yyyy)
- Bis-Datum (dd.MM.yyyy)
- Optional aktivierbar via Checkbox
### State Management
| State | Typ | Default | Beschreibung |
|-------|-----|---------|--------------|
| `selectedFilters` | Set<String> | alle Typen | Ausgewählte Schadenstypen |
| `selectedStatus` | Set<String> | alle Status | Ausgewählte Status |
| `startDateString` | String | "" | Eingabe Von-Datum |
| `endDateString` | String | "" | Eingabe Bis-Datum |
| `startDate` | LocalDate? | null | Geparste Von-Datum |
| `endDate` | LocalDate? | null | Geparste Bis-Datum |
| `useDateFilter` | Boolean | false | Datums-Filter aktiv |
### UI-Buttons
| Button | Funktion |
|--------|----------|
| "Alle" | Wählt alle Typen + alle Status |
| "Keine" | Deselektiert alle Typen + alle Status |
| "Filter anwenden" | Ruft `onApplyFilter()` auf und schließt Dialog |
### Datums-Parsing
**Format:** dd.MM.yyyy (z.B. 15.01.2024)
**Validierung:**
```kotlin
LocalDate.parse(input, DateTimeFormatter.ofPattern("dd.MM.yyyy"))
```
**Parsing:** Automatisch bei Eingabe (length == 10), fehlerhafte Eingaben werden ignoriert.
---
## applyDamageFilter (Extension Function)
```kotlin
suspend fun MapViewModel.applyDamageFilter(
selectedTypes: Set<String>,
selectedStatus: Set<String>,
startDate: LocalDate? = null,
endDate: LocalDate? = null
): Boolean
```
**Zweck:** Wendet Filter auf FeatureLayer via SQL WHERE-Clause an.
**Parameter:**
| Name | Typ | Beschreibung |
|------|-----|--------------|
| `selectedTypes` | Set<String> | Ausgewählte Schadenstypen |
| `selectedStatus` | Set<String> | Ausgewählte Status |
| `startDate` | LocalDate? | Start-Datum (optional) |
| `endDate` | LocalDate? | End-Datum (optional) |
**Return:** `Boolean` - true bei Erfolg, false bei Fehler
### Filter-Logik
**1. Typ-Filter:**
```sql
Typ IN ('Straße', 'Gehweg')
```
- Nur hinzugefügt wenn nicht alle Typen ausgewählt
- Verwendet SQL IN-Operator
**2. Status-Filter:**
```sql
status IN ('neu', 'in Bearbeitung')
```
- Nur hinzugefügt wenn nicht alle Status ausgewählt
- Verwendet SQL IN-Operator
**3. Datums-Filter:**
```sql
EditDate >= timestamp '2024-01-01 00:00:00'
AND EditDate <= timestamp '2024-12-31 23:59:59'
```
- Feldname: `EditDate`
- Format: SQL timestamp
- Unterstützt: Von, Bis, oder beides
**Kombination:**
```sql
Typ IN ('Straße') AND status IN ('neu') AND EditDate >= timestamp '...'
```
- Alle aktiven Filter werden mit AND verknüpft
### Snackbar-Feedback
**Format:** "Filter: {Typ-Info} | {Status-Info} | {Datum-Info}"
**Beispiele:**
- "Filter: 3 Typ(en) | 2 Status"
- "Filter: Datum: 01.01.2024 - 31.12.2024"
- "Alle Schäden werden angezeigt" (keine Filter)
### Threading
**Ausführung:** `withContext(Dispatchers.IO)` für Feature Query
**UI-Updates:** `withContext(Dispatchers.Main)` für snackBarMessage
---
## getActiveFilters (Extension Function)
```kotlin
fun MapViewModel.getActiveFilters(): Set<String>
```
**Zweck:** Extrahiert aktuell aktive Filter aus FeatureLayer.definitionExpression.
**Return:** Set<String> - Menge der gefilterten Werte (z.B. {"Straße", "Gehweg"})
**Extraktion:**
```kotlin
val regex = "'([^']+)'".toRegex()
regex.findAll(expression).map { it.groupValues[1] }.toSet()
```
**Beispiel:**
```
Expression: "Typ IN ('Straße', 'Gehweg')"
Return: {"Straße", "Gehweg"}
```
---
## Verwendungsbeispiel
```kotlin
// Dialog anzeigen
var showFilterDialog by remember { mutableStateOf(false) }
if (showFilterDialog) {
DamageFilterDialog(
damageTypes = MapViewModel.DAMAGE_TYPES,
currentFilters = mapViewModel.getActiveFilters(),
onDismiss = { showFilterDialog = false },
onApplyFilter = { types, status, start, end ->
coroutineScope.launch {
mapViewModel.applyDamageFilter(types, status, start, end)
}
}
)
}
// Aus MainScreen
SliderMenuItem(
text = "Schäden filtern",
icon = Icons.Default.FilterAlt,
onClick = { showFilterDialog = true }
)
```
---
## SQL-Beispiele
**Nur Typ:**
```sql
Typ IN ('Straße', 'Gehweg')
```
**Nur Status:**
```sql
status IN ('neu', 'in Bearbeitung')
```
**Nur Datum:**
```sql
EditDate >= timestamp '2024-01-01 00:00:00'
AND EditDate <= timestamp '2024-12-31 23:59:59'
```
**Alle kombiniert:**
```sql
Typ IN ('Straße')
AND status IN ('neu')
AND EditDate >= timestamp '2024-01-01 00:00:00'
```
**Keine Filter (alle anzeigen):**
```sql
(empty string)
```
---
## Feature-Attribute
**Erforderliche Felder im Feature Layer:**
| Feldname | Typ | Werte | Beschreibung |
|----------|-----|-------|--------------|
| `Typ` | String | "Straße", "Gehweg", etc. | Schadenstyp |
| `status` | String | "neu", "in Bearbeitung", "Schaden behoben" | Bearbeitungsstatus |
| `EditDate` | Timestamp | SQL timestamp | Letzte Änderung |
---
## Technische Details
- **UI Framework:** Jetpack Compose
- **Dialog:** Material3 Card im Dialog
- **State:** remember/mutableStateOf
- **Threading:** Coroutines (Dispatchers.IO/Main)
- **SQL:** ArcGIS SQL-Syntax (definitionExpression)
- **Datum:** Java Time API (LocalDate, DateTimeFormatter)

View File

@@ -0,0 +1,387 @@
# 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)

View File

@@ -0,0 +1,65 @@
# FeatureRatingSystem
## Übersicht
Das FeatureRatingSystem enthält Komponenten zur detaillierten Anzeige von gemeldeten Straßenschäden. Es ermöglicht Nutzern, Informationen und Fotos zu einem Schaden einzusehen und diesen über ein Community-Rating-System zu validieren.
---
## Methoden
## 1. FeatureInfoDialog
`Composable Function`
Ein modaler UI-Dialog, der zur Anzeige von Objektdaten eines `ArcGISFeature` dient.
### Zweck
Visualisierung von Sachdaten (Attribute), das asynchrone Laden von Bildanhängen und die Bereitstellung einer Schnittstelle für Nutzerinteraktionen (Bewertungen).
### Parameter
| Parameter | Typ | Beschreibung |
| :--- | :--- | :--- |
| `feature` | `ArcGISFeature?` | Das ArcGIS-Objekt, dessen Daten angezeigt werden. |
| `onDismiss` | `() -> Unit` | Callback zum Schließen des Dialogs. |
| `onRate` | `(ArcGISFeature, Boolean) -> Unit` | Callback, der ausgelöst wird, wenn ein Nutzer eine Bewertung abgibt. |
### Interne Zustandsvariablen (State)
* **`photoBitmaps`** (`List<ImageBitmap>`): Speichert die dekodierten Bilder, die aus den Feature-Attachments geladen wurden.
* **`isLoadingPhotos`** (`Boolean`): Statusindikator, der den Ladevorgang der Anhänge steuert.
### Funktionsweise
1. **Initialisierung**: Beim Start (`LaunchedEffect`) wird geprüft, ob das Feature vollständig geladen ist.
2. **Attachment-Download**: Die Funktion ruft `fetchAttachments()` auf. Für jeden Anhang werden die Rohdaten (`fetchData`) geladen.
3. **Bildverarbeitung**: Die Byte-Arrays werden mittels `BitmapFactory` dekodiert und in `ImageBitmap` konvertiert, um sie in Compose anzuzeigen.
4. **UI-Rendering**: Die Attribute `Typ` und `Beschreibung` werden zusammen mit den Bildern in einer scrollbaren `Card` dargestellt.
---
## 2. updateFeatureRating
`Extension Function (suspend)`
Eine Erweiterungsfunktion für das `MapViewModel`, die die Geschäftslogik für das Bewertungssystem kapselt.
### Zweck
Persistente Aktualisierung des Community-Zählers eines Schadens in der ArcGIS Online Feature Layer Table.
### Parameter
| Parameter | Typ | Beschreibung |
| :--- | :--- | :--- |
| `feature` | `ArcGISFeature` | Das zu bewertende Feature-Objekt. |
| `isPositive` | `Boolean` | `true` für eine Bestätigung (+1), `false` für eine Abmilderung (-1). |
| `context` | `Context` | Erforderlich für die Anzeige von UI-Feedback (Toasts). |
### Logik-Ablauf
1. **Wertberechnung**: Extrahiert das Attribut `communitycounter`. Erhöht oder verringert den Wert, wobei ein Minimum von `0` sichergestellt wird.
2. **Lokale Aktualisierung**: Setzt den neuen Wert im Attribut-Dictionary des Features und ruft `updateFeature()` auf der `ServiceFeatureTable` auf.
3. **Remote-Synchronisation**: Mittels `applyEdits()` werden die Änderungen an den ArcGIS-Server gesendet.
4. **Feedback**: Informiert den Nutzer via `Toast` und `SnackBar` über den Erfolg oder Fehler der Operation.
### Datenbank-Attribute (ArcGIS Schema)
* **`Typ`**: Identifikator für die Schadensart.
* **`Beschreibung`**: Optionaler Freitext des Erstellers.
* **`communitycounter`**: Ganzzahliger Wert zur Speicherung der Community-Validierungen.
---
## Fehlerbehandlung
* **Bild-Dekodierung**: Schlägt das Laden eines Bildes fehl, wird der Fehler geloggt, aber der Dialog bleibt funktionsfähig.
* **Netzwerk-Synchronisation**: Bei Fehlern während `applyEdits` wird eine Fehlermeldung ausgegeben, um den Nutzer über mangelnde Konnektivität zu informieren.

View File

@@ -0,0 +1,72 @@
# ProximityNotificationService
## Übersicht
Der `ProximityNotificationService` im Paket `com.example.snapandsolve.service` ist ein **Android Foreground Service**. Er ermöglicht die Hintergrund-Überwachung des Nutzerstandorts, um proaktiv Benachrichtigungen auszulösen, wenn sich der Nutzer in der Nähe (Standard: 100m) eines in ArcGIS registrierten Straßenschadens befindet.
---
## 1. Architektur & Lebenszyklus
Der Dienst ist darauf ausgelegt, unabhängig von der Sichtbarkeit der App zu laufen. Als **Foreground Service** ist er mit einer permanenten System-Benachrichtigung verknüpft, um eine vorzeitige Beendigung durch das Android-Betriebssystem zu verhindern.
### Steuerung über das Companion Object
* **`start(context, featureTable)`**: Initialisiert den Dienst mit der notwendigen Datenquelle und startet ihn.
* **`stop(context)`**: Beendet das Tracking und den Dienst.
---
## 2. Funktionsweise des Standorts-Trackings
### Initialisierung
Nach dem Start (`onCreate`) wird die `SystemLocationDataSource` von ArcGIS initialisiert. Diese liefert kontinuierlich Standort-Updates innerhalb eines dedizierten `serviceScope` (CoroutineScope).
### Geofencing-Logik (`checkProximityToDamages`)
Bei jedem Standort-Update führt der Dienst eine räumliche Analyse durch:
1. **Abfrage**: Alle verfügbaren Features werden aus der `ServiceFeatureTable` abgerufen.
2. **Projektion**: Da GPS-Koordinaten (`SpatialReference.wgs84()`) oft nicht mit dem Koordinatensystem der Karte übereinstimmen, wird der Standort des Nutzers mittels `GeometryEngine.projectOrNull` transformiert.
3. **Distanzberechnung**: Die exakte Entfernung wird über `GeometryEngine.distanceGeodeticOrNull` berechnet. Hierbei wird der Kurventyp `Geodesic` verwendet, um die Erdkrümmung korrekt zu berücksichtigen.
---
## 3. Benachrichtigungs-Management
### Spam-Prävention
Um mehrfache Warnungen für denselben Schaden zu vermeiden, nutzt der Dienst ein internes Cache-System:
* **`notifiedFeatures`**: Ein Set von `OBJECTID`s, für die bereits eine Benachrichtigung gesendet wurde.
* **Hysterese-Bereinigung**: Eine ID wird erst dann aus dem Cache entfernt, wenn sich der Nutzer mehr als 150 Meter vom entsprechenden Schaden entfernt hat (`cleanupNotifiedFeatures`). Dies verhindert "flackernde" Benachrichtigungen an der Radiengrenze.
### Benachrichtigungs-Inhalt
Der Dienst sucht dynamisch nach verfügbaren Attributen im ArcGIS-Feature, um den Text zu generieren:
* **Kategorie**: Prüft Felder wie `Kategorie`, `kategorie`, `Category` oder `category`.
* **Beschreibung**: Sucht nach zusätzlichen Details in `Beschreibung` oder `Description`.
---
## 4. Konfiguration & Konstanten
| Konstante | Wert | Beschreibung |
| :--- | :--- | :--- |
| `PROXIMITY_RADIUS_METERS` | 100.0 | Radius, ab dem eine Warnung ausgelöst wird. |
| `CHANNEL_ID` | `proximity_notifications` | ID des Android-Benachrichtigungskanals. |
| `START_STICKY` | Konstante | Sorgt für einen automatischen Neustart des Dienstes nach einem System-Kill. |
---
## 5. Voraussetzungen & Sicherheit
### Erforderliche Berechtigungen
In der `AndroidManifest.xml` müssen folgende Berechtigungen konfiguriert sein:
* `ACCESS_FINE_LOCATION` (Präziser Standort)
* `ACCESS_BACKGROUND_LOCATION` (Standort im Hintergrund)
* `FOREGROUND_SERVICE` (Erlaubnis für Hintergrunddienste)
### Datenintegrität
Der Dienst setzt voraus, dass die `ServiceFeatureTable` beim Start übergeben wird. Ist die Tabelle `null`, stellt der Dienst die Überprüfung ein und loggt einen Fehler, bleibt aber als Foreground Service aktiv, um Systeminstabilitäten zu vermeiden.
---
## Fehlerbehandlung
* **Projektionsfehler**: Falls die Koordinatentransformation fehlschlägt, wird das betroffene Feature übersprungen.
* **Geometrie-Validierung**: Nur Features mit gültigen Punkt-Geometrien werden verarbeitet.

57
docs/SettingsScreen.md Normal file
View File

@@ -0,0 +1,57 @@
# Dokumentation: SettingsScreen (Einstellungen)
## Übersicht
Die Datei `SettingsScreen.kt` im Paket `com.example.snapandsolve.ui.theme` stellt die Benutzeroberfläche für die App-Konfiguration bereit. Sie dient primär der Steuerung des **ProximityNotificationService**, welcher Nutzer benachrichtigt, sobald sie sich in der Nähe eines gemeldeten Straßenschadens befinden.
---
## 1. Hauptkomponente: SettingsScreen
`@Composable fun SettingsScreen(onBack: () -> Unit, mapViewModel: MapViewModel)`
Ein Full-Screen Composable, das mittels Material Design 3 (M3) eine übersichtliche Struktur für Benutzereinstellungen bietet.
### Parameter
| Parameter | Typ | Beschreibung |
| :--- | :--- | :--- |
| `onBack` | `() -> Unit` | Callback zur Navigation zurück zum vorherigen Screen. |
| `mapViewModel` | `MapViewModel` | ViewModel zur Bereitstellung der ArcGIS Feature Table. |
### Zustandsverwaltung (State)
* **`isProximityActive`**: Ein via `collectAsState` beobachteter Boolean, der den aktuellen Status des Hintergrunddienstes direkt aus dem `ProximityNotificationService` widerspiegelt.
* **`notificationPermissionLauncher`**: Ein Activity-Result-Launcher, der die erforderliche Berechtigung `POST_NOTIFICATIONS` verwaltet.
---
## 2. Funktionslogik: Benachrichtigungs-Switch
Der zentrale Teil des Screens ist ein `Switch`, der den Proximity-Dienst steuert. Der Ablauf bei Aktivierung ist wie folgt:
1. **Versionsprüfung**: Prüft, ob die Berechtigung für Benachrichtigungen vorliegt.
2. **Berechtigungsanfrage**: Fehlt die Berechtigung, wird der System-Dialog zur Anfrage gestartet.
3. **Validierung der Datenquelle**: Es wird geprüft, ob die `FeatureTable` im `MapViewModel` verfügbar ist.
4. **Dienst-Start/Stop**:
- Bei Erfolg: `ProximityNotificationService.start(context, table)`
- Bei Deaktivierung: `ProximityNotificationService.stop(context)`
---
## 3. UI-Struktur & Design
### TopAppBar
Die Kopfzeile nutzt das Farbschema der App (`AppColor`) und bietet eine konsistente Navigation.
### Layout-Elemente
* **Settings-Karten (`Card`)**: Gruppieren inhaltlich zusammenhängende Einstellungen (z.B. Benachrichtigungen, Informationen).
* **Status-Feedback**: Wenn der Dienst aktiv ist, wird dynamisch eine zusätzliche Infokarte mit grünem Häkchen (`✓`) eingeblendet, um den aktiven Status zu visualisieren.
* **Informations-Sektion**: Zeigt feste Parameter wie den Proximity-Radius (aktuell 100 Meter) an.
---
## 4. Integration & Anforderungen
### Erforderliche Berechtigungen
In der `AndroidManifest.xml` müssen für die volle Funktionalität dieses Screens folgende Berechtigungen deklariert sein:
```xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

View File

View File

@@ -1,38 +0,0 @@
# Architektur
## Überblick
## Struktur
## Verantwortlichkeiten
## Prozessablauf
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
## Process Flow (Architecture)
```mermaid
flowchart LR
UI[UI: Screen / Fragment / Compose] -->|user action| VM[ViewModel]
VM -->|invoke| UC[Use Case]
UC -->|calls| R[Repository]
R -->|read/write| LDS[Local Data Source\nDB / DataStore]
R -->|fetch| RDS[Remote Data Source\nREST / GraphQL]
RDS -->|DTOs| MAP[Mapper]
LDS -->|Entities| MAP
MAP -->|Domain Model| UC
UC -->|Result| VM
VM -->|StateFlow / LiveData| UI
subgraph Data
R
LDS
RDS
MAP
end

View File

@@ -0,0 +1,83 @@
# locationHelper
## Übersicht
Dieses Modul stellt Hilfsfunktionen und Composables bereit, um die GPS-Standortbestimmung innerhalb der ArcGIS Maps SDK für Kotlin (Compose-Toolkit) zu verwalten. Es kümmert sich um die Prüfung und Abfrage von Android-Laufzeitberechtigungen sowie die Initialisierung des `LocationDisplay`.
---
## 1. LocationHelper (Klasse)
`class LocationHelper(private val context: Context)`
Eine Utility-Klasse zur Kapselung von Berechtigungsprüfungen.
### Zweck
Zentralisierung der Logik für die Prüfung von Standortberechtigungen (`ACCESS_COARSE_LOCATION` und `ACCESS_FINE_LOCATION`).
### Methoden
| Methode | Rückgabetyp | Beschreibung |
| :--- | :--- | :--- |
| `hasLocationPermissions()` | `Boolean` | Gibt `true` zurück, wenn sowohl die grobe als auch die feine Standortberechtigung vom Nutzer erteilt wurde. |
---
## 2. setupLocationDisplay (Composable)
`@Composable fun setupLocationDisplay(autoPanMode: LocationDisplayAutoPanMode): LocationDisplay`
Die Haupt-Einstiegsfunktion für die Standortvisualisierung in einer MapView.
### Zweck
Initialisiert das `LocationDisplay`-Objekt, setzt den Modus für die automatische Schwenkung der Karte (Auto-Pan) und startet die Datenquelle für Standortaktualisierungen.
### Parameter
| Parameter | Typ | Default | Beschreibung |
| :--- | :--- | :--- | :--- |
| `autoPanMode` | `LocationDisplayAutoPanMode` | `.Recenter` | Bestimmt das Verhalten der Kamera bei Standortänderung (z.B. Zentrieren oder Navigieren). |
### Funktionsweise
1. **Initialisierung**: Erzeugt ein `LocationDisplay` mittels `rememberLocationDisplay()`.
2. **Berechtigungsprüfung**: Nutzt den `LocationHelper`, um den aktuellen Status zu prüfen.
3. **Datenquelle starten**:
- Sind Berechtigungen vorhanden: Startet die `dataSource` sofort via `LaunchedEffect`.
- Fehlen Berechtigungen: Ruft das Composable `RequestLocationPermissions` auf.
4. **Rückgabe**: Liefert das konfigurierte Objekt an die übergeordnete `MapView` zurück.
---
## 3. RequestLocationPermissions (Privates Composable)
`@Composable private fun RequestLocationPermissions(...)`
Ein UI-Komponente zur Interaktion mit dem Android-Berechtigungssystem.
### Zweck
Anforderung der erforderlichen Berechtigungen während der Laufzeit (Runtime Permissions).
### Parameter
| Parameter | Typ | Beschreibung |
| :--- | :--- | :--- |
| `context` | `Context` | Android-Kontext für Toast-Meldungen. |
| `onPermissionsGranted` | `() -> Unit` | Callback, der ausgeführt wird, wenn der Nutzer alle angeforderten Rechte bestätigt hat. |
### Ablauf
1. Nutzt `rememberLauncherForActivityResult`, um auf die Antwort des Betriebssystems zu warten.
2. Fordert im `LaunchedEffect` gleichzeitig `ACCESS_COARSE_LOCATION` und `ACCESS_FINE_LOCATION` an.
3. **Erfolg**: Ruft `onPermissionsGranted()` auf, was in der Regel den Start des GPS-Tracking auslöst.
4. **Ablehnung**: Zeigt eine `Toast`-Meldung an, um den Nutzer über die fehlende Funktionalität aufzuklären.
---
## Verwendete Berechtigungen (Manifest)
Für die korrekte Funktion müssen folgende Tags in der `AndroidManifest.xml` vorhanden sein:
* `android.permission.ACCESS_FINE_LOCATION`
* `android.permission.ACCESS_COARSE_LOCATION`
---
## Architektur-Hinweis
Das Modul nutzt das **ArcGIS Maps Compose Toolkit**. Das zurückgegebene `LocationDisplay` wird normalerweise direkt in einer `MapView` Composable als Parameter übergeben:
```kotlin
MapView(
modifier = Modifier.fillMaxSize(),
arcGISMap = map,
locationDisplay = setupLocationDisplay()
)