- 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

@@ -8,12 +8,12 @@ import androidx.compose.ui.graphics.ImageBitmap
*/
data class AlbumViewState(
/**
* holds the URL of the temporary file which stores the image taken by the camera.
* Speichert die URL der temporären Datei des Kamerabildes.
*/
val tempFileUrl: Uri? = null,
/**
* holds the list of images taken by camera or selected pictures from the gallery.
* Speichert eine Liste der Bilder, die entweder über die Kamera aufgenommen oder aus der Galerie ausgewählt wurden.
*/
val selectedPictures: List<ImageBitmap> = emptyList(),
)

View File

@@ -167,7 +167,7 @@ class ProximityNotificationService : Service() {
Log.d("ProximityService", "Feature point SR: ${featureGeometry.spatialReference?.wkid}")
// KRITISCHER FIX: Beide Punkte ins gleiche Koordinatensystem bringen
// Beide Punkte ins gleiche Koordinatensystem bringen von UTM ins WGS
val projectedCurrentPoint = if (currentPoint.spatialReference?.wkid != featureGeometry.spatialReference?.wkid) {
GeometryEngine.projectOrNull(currentPoint, featureGeometry.spatialReference!!) as? Point
} else {
@@ -179,7 +179,7 @@ class ProximityNotificationService : Service() {
return@forEach
}
// KORRIGIERTE Distanzberechnung mit GeometryEngine
// korrigierte Distanzberechnung mit GeometryEngine
val distanceResult = GeometryEngine.distanceGeodeticOrNull(
point1 = projectedCurrentPoint,
point2 = featureGeometry,
@@ -214,14 +214,14 @@ class ProximityNotificationService : Service() {
}
private fun sendDamageNotification(feature: ArcGISFeature, distance: Double) {
// Versuche verschiedene Attribut-Namen für die Kategorie
val kategorie = feature.attributes["Kategorie"]?.toString()
?: feature.attributes["kategorie"]?.toString()
?: feature.attributes["Category"]?.toString()
?: feature.attributes["category"]?.toString()
?: "Straßenschaden" // Fallback
// Versuche verschiedene Attribut-Namen für die Beschreibung
val beschreibung = feature.attributes["Beschreibung"]?.toString()
?: feature.attributes["beschreibung"]?.toString()
?: feature.attributes["Description"]?.toString()
@@ -229,7 +229,7 @@ class ProximityNotificationService : Service() {
val distanceText = String.format("%.0f", distance)
// Baue den Notification-Text
// Notification-Text
val notificationText = if (beschreibung != null && beschreibung.isNotEmpty()) {
"$kategorie: $beschreibung - ca. ${distanceText}m entfernt"
} else {

View File

@@ -68,7 +68,7 @@ fun DamageFilterDialog(
// Status-Liste
val statusTypes = listOf("neu", "in Bearbeitung", "Schaden behoben")
// WICHTIG: Wenn keine Filter aktiv sind, alle Typen standardmäßig auswählen!
var selectedFilters by remember {
mutableStateOf(
if (currentFilters.isEmpty()) damageTypes.toSet() else currentFilters
@@ -121,7 +121,7 @@ fun DamageFilterDialog(
.weight(1f)
.verticalScroll(rememberScrollState())
) {
// ===== TYP-FILTER =====
// Filtertyp
Text(
text = "Schadenstypen:",
style = MaterialTheme.typography.titleMedium,
@@ -161,7 +161,7 @@ fun DamageFilterDialog(
Divider(modifier = Modifier.padding(vertical = 16.dp))
// ===== STATUS-FILTER =====
// Filter nach Status
Text(
text = "Bearbeitungsstatus:",
style = MaterialTheme.typography.titleMedium,
@@ -198,7 +198,7 @@ fun DamageFilterDialog(
Divider(modifier = Modifier.padding(vertical = 16.dp))
// ===== DATUMS-FILTER =====
// Filter nach Datum
Row(
modifier = Modifier
.fillMaxWidth()
@@ -372,8 +372,8 @@ fun DamageFilterDialog(
}
/**
* Extension-Funktion für MapViewModel
* Wendet Filter auf den FeatureLayer an (Typ + Status + Datum - UNABHÄNGIG)
* Erweiterungs-Funktion für MapViewModel
* Wendet Filter auf den FeatureLayer an
*/
suspend fun MapViewModel.applyDamageFilter(
selectedTypes: Set<String>,
@@ -387,22 +387,22 @@ suspend fun MapViewModel.applyDamageFilter(
try {
val whereClauses = mutableListOf<String>()
// ===== TYP-FILTER =====
// WICHTIG: Nur hinzufügen wenn nicht ALLE Typen ausgewählt sind
// Filter Typ
if (selectedTypes.isNotEmpty() && selectedTypes.size < DAMAGE_TYPES.size) {
val typeList = selectedTypes.joinToString("', '", "'", "'")
whereClauses.add("Typ IN ($typeList)")
}
// ===== STATUS-FILTER =====
// Nur hinzufügen wenn nicht ALLE Status ausgewählt sind
// Filter Status
if (selectedStatus.isNotEmpty() && selectedStatus.size < statusTypes.size) {
val statusList = selectedStatus.joinToString("', '", "'", "'")
whereClauses.add("status IN ($statusList)")
}
// ===== DATUMS-FILTER =====
// Feldname: EditDate
// Filter nach Datum
if (startDate != null || endDate != null) {
val dateField = "EditDate"
@@ -419,7 +419,7 @@ suspend fun MapViewModel.applyDamageFilter(
}
}
// ===== ALLE KLAUSELN KOMBINIEREN =====
// Alle Klauseln kombinieren
val whereClause = whereClauses.joinToString(" AND ")
featureLayer.definitionExpression = whereClause
@@ -469,7 +469,7 @@ suspend fun MapViewModel.applyDamageFilter(
}
/**
* Extension-Funktion für MapViewModel
* Erweiterte-Funktion für MapViewModel
* Gibt die aktuell aktiven Filter zurück
*/
fun MapViewModel.getActiveFilters(): Set<String> {

View File

@@ -152,7 +152,7 @@ fun DamageListDialog(
Divider(modifier = Modifier.padding(vertical = 12.dp))
// ===== ENTFERNUNGS-FILTER =====
// Filtern nach Entfernung
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -192,7 +192,7 @@ fun DamageListDialog(
Spacer(modifier = Modifier.height(16.dp))
// ===== SORTIERUNG =====
// Sortieren
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Sortieren nach:",
@@ -205,7 +205,7 @@ fun DamageListDialog(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Distance Button
// Entfernung Button
FilterChip(
selected = sortBy == SortBy.DISTANCE,
onClick = { sortBy = SortBy.DISTANCE },
@@ -220,7 +220,7 @@ fun DamageListDialog(
modifier = Modifier.weight(1f)
)
// Relevance Button
// Relevanz Button
FilterChip(
selected = sortBy == SortBy.RELEVANCE,
onClick = { sortBy = SortBy.RELEVANCE },
@@ -239,7 +239,7 @@ fun DamageListDialog(
Spacer(modifier = Modifier.height(16.dp))
// ===== RELEVANZ-FILTER (nur wenn nach Relevanz sortiert) =====
// Nach Relevanz filtern
if (sortBy == SortBy.RELEVANCE) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
@@ -380,7 +380,7 @@ fun DamageListDialog(
}
/**
* Einzelnes Schadens-Item in der Liste MIT FOTO und BEWERTUNG
* Einzelnes Schadens-Item in der Liste mit Foto und Bewertung
*/
@Composable
fun DamageListItemWithPhoto(
@@ -401,7 +401,7 @@ fun DamageListItemWithPhoto(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
// LINKS: Foto
//LINKS: Foto
Box(
modifier = Modifier
.width(100.dp)
@@ -500,7 +500,7 @@ fun DamageListItemWithPhoto(
}
/**
* Extension-Funktion für MapViewModel
* Erweiterte-Funktion für MapViewModel
* Lädt alle Schäden im Umkreis
*/
suspend fun MapViewModel.loadDamagesNearby(

View File

@@ -19,7 +19,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.arcgismaps.data.ArcGISFeature
import com.arcgismaps.data.Attachment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -35,12 +34,13 @@ fun FeatureInfoDialog(
) {
if (feature == null) return
var attachments by remember { mutableStateOf<List<Attachment>>(emptyList()) }
var photoBitmaps by remember { mutableStateOf<List<androidx.compose.ui.graphics.ImageBitmap>>(emptyList()) }
var isLoadingPhotos by remember { mutableStateOf(true) }
// Lade Fotos beim Öffnen
LaunchedEffect(feature) {
isLoadingPhotos = true
try {
// Lade Feature falls nötig
if (feature.loadStatus.value != com.arcgismaps.LoadStatus.Loaded) {
@@ -49,23 +49,22 @@ fun FeatureInfoDialog(
// Hole Attachments
feature.fetchAttachments().onSuccess { fetchedAttachments ->
attachments = fetchedAttachments
val loadedBitmaps = mutableListOf<androidx.compose.ui.graphics.ImageBitmap>()
// Lade Foto-Daten
val bitmaps = mutableListOf<androidx.compose.ui.graphics.ImageBitmap>()
fetchedAttachments.forEach { attachment ->
attachment.fetchData().onSuccess { data ->
try {
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
if (bitmap != null) {
bitmaps.add(bitmap.asImageBitmap())
loadedBitmaps.add(bitmap.asImageBitmap())
// Update die UI-Liste direkt, wenn ein Bild fertig ist
photoBitmaps = loadedBitmaps.toList()
}
} catch (e: Exception) {
println("DEBUG: Fehler beim Laden des Bildes: ${e.message}")
println("DEBUG: Fehler beim Decodieren des Bildes: ${e.message}")
}
}
}
photoBitmaps = bitmaps
}
} catch (e: Exception) {
println("DEBUG: Fehler beim Laden der Attachments: ${e.message}")
@@ -95,7 +94,7 @@ fun FeatureInfoDialog(
modifier = Modifier.padding(bottom = 16.dp)
)
Divider(modifier = Modifier.padding(bottom = 16.dp))
HorizontalDivider(modifier = Modifier.padding(bottom = 16.dp))
// Beschreibung
val description = feature.attributes["Beschreibung"]?.toString()
@@ -113,7 +112,7 @@ fun FeatureInfoDialog(
}
// Fotos
if (isLoadingPhotos) {
if (isLoadingPhotos && photoBitmaps.isEmpty()) {
Row(
modifier = Modifier
.fillMaxWidth()
@@ -122,7 +121,7 @@ fun FeatureInfoDialog(
) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Lade Fotos...")
Text("Suche Fotos...")
}
} else if (photoBitmaps.isNotEmpty()) {
Text(
@@ -149,7 +148,6 @@ fun FeatureInfoDialog(
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
@@ -200,7 +198,6 @@ fun FeatureInfoDialog(
}
}
// Info-Text
Text(
text = "Hast du diesen Schaden auch gesehen?",
style = MaterialTheme.typography.bodyMedium,
@@ -211,24 +208,19 @@ fun FeatureInfoDialog(
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
// Daumen runter
OutlinedButton(
) {//Daumen runter
Button(
onClick = {
onRate(feature, false)
onDismiss()
},
modifier = Modifier.weight(1f)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(vertical = 8.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("👎", style = MaterialTheme.typography.headlineMedium)
Text("Nein", style = MaterialTheme.typography.bodyMedium)
}
}
// Daumen hoch
Button(
onClick = {
@@ -237,17 +229,13 @@ fun FeatureInfoDialog(
},
modifier = Modifier.weight(1f)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(vertical = 8.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("👍", style = MaterialTheme.typography.headlineMedium)
Text("Ja", style = MaterialTheme.typography.bodyMedium)
}
}
}
// Schließen Button
TextButton(
onClick = onDismiss,
modifier = Modifier
@@ -261,10 +249,8 @@ fun FeatureInfoDialog(
}
}
/**
* Extension-Funktion für MapViewModel
* Erweiterungs-Funktion für MapViewModel
* Aktualisiert die Community-Bewertung eines Features
*/
suspend fun MapViewModel.updateFeatureRating(
@@ -274,62 +260,31 @@ suspend fun MapViewModel.updateFeatureRating(
): Boolean {
return withContext(Dispatchers.IO) {
try {
// Hole aktuelle Bewertung
val currentRating = (feature.attributes["communitycounter"] as? Number)?.toInt() ?: 0
val newRating = if (isPositive) currentRating + 1 else maxOf(0, currentRating - 1)
// Berechne neue Bewertung
val newRating = if (isPositive) {
currentRating + 1
} else {
maxOf(0, currentRating - 1) // Verhindert negative Werte
}
println("DEBUG: Rating-Update: $currentRating -> $newRating (${if(isPositive) "+" else "-"})")
// Aktualisiere Feature-Attribut
feature.attributes["communitycounter"] = newRating
// Speichere in Feature Table
serviceFeatureTable.updateFeature(feature).onSuccess {
println("DEBUG: updateFeature erfolgreich")
// Synchronisiere mit ArcGIS Online
serviceFeatureTable.applyEdits().onSuccess {
println("DEBUG: applyEdits erfolgreich - Rating gespeichert")
withContext(Dispatchers.Main) {
val message = if (isPositive) {
"✓ Schaden bestätigt! (${currentRating}${newRating})"
} else {
"✓ Bewertung verringert (${currentRating}${newRating})"
}
val message = if (isPositive) "✓ Schaden bestätigt!" else "✓ Bewertung verringert"
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
snackBarMessage = message
}
}.onFailure { error ->
println("DEBUG: applyEdits fehlgeschlagen: ${error.message}")
withContext(Dispatchers.Main) {
Toast.makeText(context, "Sync-Fehler: ${error.message}", Toast.LENGTH_LONG).show()
}
}
}.onFailure { error ->
println("DEBUG: updateFeature fehlgeschlagen: ${error.message}")
withContext(Dispatchers.Main) {
Toast.makeText(context, "Update-Fehler: ${error.message}", Toast.LENGTH_LONG).show()
}
}
true
} catch (e: Exception) {
println("DEBUG: Rating-Update Exception: ${e.message}")
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"Fehler beim Bewerten: ${e.message}",
Toast.LENGTH_LONG
).show()
Toast.makeText(context, "Fehler: ${e.message}", Toast.LENGTH_LONG).show()
}
false
}

View File

@@ -28,7 +28,7 @@ fun SettingsScreen(
val context = LocalContext.current
val isProximityActive by ProximityNotificationService.isRunning.collectAsState()
// NEU: Notification Permission Launcher
// Notification Permission Launcher
val notificationPermissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
@@ -65,7 +65,7 @@ fun SettingsScreen(
.padding(paddingValues)
.padding(16.dp)
) {
// Benachrichtigungen Section
// Benachrichtigungen
Text(
text = "Benachrichtigungen",
style = MaterialTheme.typography.titleMedium,
@@ -180,7 +180,7 @@ fun SettingsScreen(
Spacer(modifier = Modifier.height(24.dp))
// Weitere Einstellungen können hier hinzugefügt werden
Text(
text = "Informationen",
style = MaterialTheme.typography.titleMedium,

View File

@@ -43,8 +43,6 @@ class LocationHelper(private val context: Context) {
/**
* Composable zum Einrichten des Location Display
*
* @param autoPanMode Wie die Karte dem Standort folgen soll (default: Recenter)
* @return LocationDisplay-Objekt, das an MapView übergeben werden kann
*/
@Composable
fun setupLocationDisplay(