- Filterfunktion erweitert mit Filtern nach Datum
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.example.snapandsolve
|
||||
|
||||
import DamageFilterDialog
|
||||
import MapViewModel
|
||||
import android.Manifest
|
||||
import android.R.attr.enabled
|
||||
@@ -37,6 +38,7 @@ import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import applyDamageFilter
|
||||
import com.arcgismaps.data.ArcGISFeature
|
||||
// Hier holen wir die ArcGIS Klassen
|
||||
import com.arcgismaps.mapping.ArcGISMap
|
||||
@@ -48,6 +50,7 @@ import com.example.snapandsolve.camera.AlbumViewModel
|
||||
import com.example.snapandsolve.camera.AlbumViewState
|
||||
import com.example.snapandsolve.camera.Intent
|
||||
import com.example.snapandsolve.ui.theme.*
|
||||
import getActiveFilters
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -183,9 +186,9 @@ fun ContentScreen(
|
||||
damageTypes = MapViewModel.DAMAGE_TYPES, // <-- Nutzt zentrale Liste
|
||||
currentFilters = mapViewModel.getActiveFilters(),
|
||||
onDismiss = { showFilterDialog = false },
|
||||
onApplyFilter = { selectedTypes ->
|
||||
onApplyFilter = { selectedTypes, startDate, endDate ->
|
||||
coroutineScope.launch {
|
||||
mapViewModel.applyDamageFilter(selectedTypes)
|
||||
mapViewModel.applyDamageFilter(selectedTypes, startDate, endDate)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -82,11 +82,32 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||
damageTypeList += it.name
|
||||
}
|
||||
println("DEBUG: ServiceFeatureTable erfolgreich geladen")
|
||||
|
||||
// ===== DEBUG: Alle verfügbaren Felder ausgeben =====
|
||||
println("DEBUG: Verfügbare Felder in ServiceFeatureTable:")
|
||||
serviceFeatureTable.fields.forEach { field ->
|
||||
println(" - ${field.name}")
|
||||
}
|
||||
println("DEBUG: Ende Feldliste")
|
||||
|
||||
}.onFailure {
|
||||
println("DEBUG: Fehler beim Laden der Tabelle: ${it.message}")
|
||||
}
|
||||
featureLayer = FeatureLayer.createWithFeatureTable(serviceFeatureTable)
|
||||
map.operationalLayers.add(featureLayer)
|
||||
|
||||
// ===== DEBUG: Felder nach dem Hinzufügen zur Map =====
|
||||
featureLayer.load().onSuccess {
|
||||
println("DEBUG: FeatureLayer erfolgreich geladen")
|
||||
val table = featureLayer.featureTable
|
||||
if (table != null) {
|
||||
println("DEBUG: Verfügbare Felder im FeatureLayer:")
|
||||
table.fields.forEach { field ->
|
||||
println(" - ${field.name}")
|
||||
}
|
||||
println("DEBUG: Ende Feldliste FeatureLayer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +386,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||
viewModelScope.launch {
|
||||
// Determine if a user tapped on a feature.
|
||||
mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess {
|
||||
identifyResult ->
|
||||
identifyResult ->
|
||||
// Get the identified feature.
|
||||
val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
|
||||
identifiedFeature?.let {
|
||||
@@ -403,7 +424,7 @@ data class ReportDraft(
|
||||
val isValid: Boolean
|
||||
get() =
|
||||
beschreibung.isNotBlank() &&
|
||||
typ != "Schadenstyp wählen..." &&
|
||||
point != null &&
|
||||
photos.isNotEmpty()
|
||||
typ != "Schadenstyp wählen..." &&
|
||||
point != null &&
|
||||
photos.isNotEmpty()
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.example.snapandsolve.ui.theme
|
||||
|
||||
|
||||
|
||||
import MapViewModel
|
||||
import MapViewModel.Companion.DAMAGE_TYPES
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -17,25 +14,38 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* Dialog für Schaden-Filter
|
||||
* Ermöglicht das Filtern von Features nach Typ
|
||||
* Ermöglicht das Filtern von Features nach Typ UND Datum (unabhängig voneinander)
|
||||
*/
|
||||
@Composable
|
||||
fun DamageFilterDialog(
|
||||
damageTypes: List<String>,
|
||||
currentFilters: Set<String>,
|
||||
onDismiss: () -> Unit,
|
||||
onApplyFilter: (Set<String>) -> Unit
|
||||
onApplyFilter: (Set<String>, LocalDate?, LocalDate?) -> Unit
|
||||
) {
|
||||
var selectedFilters by remember { mutableStateOf(currentFilters) }
|
||||
// WICHTIG: Wenn keine Filter aktiv sind, alle Typen standardmäßig auswählen!
|
||||
// Das ermöglicht Datumfilterung ohne Typ-Auswahl
|
||||
var selectedFilters by remember {
|
||||
mutableStateOf(
|
||||
if (currentFilters.isEmpty()) damageTypes.toSet() else currentFilters
|
||||
)
|
||||
}
|
||||
var startDateString by remember { mutableStateOf("") }
|
||||
var endDateString by remember { mutableStateOf("") }
|
||||
var startDate by remember { mutableStateOf<LocalDate?>(null) }
|
||||
var endDate by remember { mutableStateOf<LocalDate?>(null) }
|
||||
var useDateFilter by remember { mutableStateOf(false) }
|
||||
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.7f),
|
||||
.fillMaxWidth(0.9f)
|
||||
.fillMaxHeight(0.85f),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
||||
) {
|
||||
Column(
|
||||
@@ -62,19 +72,28 @@ fun DamageFilterDialog(
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
// Info-Text
|
||||
Text(
|
||||
text = "Wähle die Schadenstypen aus, die angezeigt werden sollen:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
// Filter-Liste
|
||||
// Scroll-Content
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
// ===== TYP-FILTER =====
|
||||
Text(
|
||||
text = "Schadenstypen:",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
)
|
||||
|
||||
// Info-Text
|
||||
Text(
|
||||
text = "Wähle die Schadenstypen aus, die angezeigt werden sollen:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
)
|
||||
|
||||
// Filter-Liste
|
||||
damageTypes.forEach { type ->
|
||||
FilterCheckboxItem(
|
||||
label = type,
|
||||
@@ -88,17 +107,118 @@ fun DamageFilterDialog(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
// ===== DATUMS-FILTER =====
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = useDateFilter,
|
||||
onCheckedChange = { useDateFilter = it }
|
||||
)
|
||||
Text(
|
||||
text = "Nach Datum filtern",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Datums-Eingabe (nur wenn aktiviert)
|
||||
if (useDateFilter) {
|
||||
// Von-Datum
|
||||
Text(
|
||||
text = "Von:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = startDateString,
|
||||
onValueChange = { newValue ->
|
||||
startDateString = newValue
|
||||
startDate = try {
|
||||
if (newValue.length == 10) { // dd.MM.yyyy format
|
||||
LocalDate.parse(
|
||||
newValue,
|
||||
DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
placeholder = { Text("dd.MM.yyyy") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
// Bis-Datum
|
||||
Text(
|
||||
text = "Bis:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = endDateString,
|
||||
onValueChange = { newValue ->
|
||||
endDateString = newValue
|
||||
endDate = try {
|
||||
if (newValue.length == 10) { // dd.MM.yyyy format
|
||||
LocalDate.parse(
|
||||
newValue,
|
||||
DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
placeholder = { Text("dd.MM.yyyy") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Format: dd.MM.yyyy (z.B. 15.01.2024)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
// Info über aktive Filter
|
||||
if (selectedFilters.isNotEmpty()) {
|
||||
if (selectedFilters.isNotEmpty() || useDateFilter) {
|
||||
Text(
|
||||
text = "${selectedFilters.size} von ${damageTypes.size} Typen ausgewählt",
|
||||
text = buildString {
|
||||
if (selectedFilters.size < damageTypes.size) {
|
||||
append("${selectedFilters.size} von ${damageTypes.size} Typ(en)")
|
||||
} else {
|
||||
append("Alle Typen")
|
||||
}
|
||||
if (useDateFilter) {
|
||||
if (selectedFilters.isNotEmpty()) append(" | ")
|
||||
append("Datum: ${startDate?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) ?: "?"} - ${endDate?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) ?: "?"}")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,7 +232,7 @@ fun DamageFilterDialog(
|
||||
onClick = { selectedFilters = damageTypes.toSet() },
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text("Alle")
|
||||
Text("Alle Typen")
|
||||
}
|
||||
|
||||
// Alle abwählen
|
||||
@@ -129,14 +249,18 @@ fun DamageFilterDialog(
|
||||
// Anwenden Button
|
||||
Button(
|
||||
onClick = {
|
||||
onApplyFilter(selectedFilters)
|
||||
val startDateToApply = if (useDateFilter) startDate else null
|
||||
val endDateToApply = if (useDateFilter) endDate else null
|
||||
onApplyFilter(selectedFilters, startDateToApply, endDateToApply)
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
if (selectedFilters.isEmpty()) "Alle anzeigen"
|
||||
else "Filter anwenden"
|
||||
when {
|
||||
selectedFilters.isEmpty() && !useDateFilter -> "Alle anzeigen"
|
||||
else -> "Filter anwenden"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -172,7 +296,6 @@ fun FilterCheckboxItem(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
|
||||
Text(
|
||||
text = when (label) {
|
||||
"Straße" -> "🛣️"
|
||||
@@ -189,30 +312,69 @@ fun FilterCheckboxItem(
|
||||
|
||||
/**
|
||||
* Extension-Funktion für MapViewModel
|
||||
* Wendet Filter auf den FeatureLayer an
|
||||
* Wendet Filter auf den FeatureLayer an (Typ + Datum - UNABHÄNGIG)
|
||||
*/
|
||||
suspend fun MapViewModel.applyDamageFilter(selectedTypes: Set<String>): Boolean {
|
||||
suspend fun MapViewModel.applyDamageFilter(
|
||||
selectedTypes: Set<String>,
|
||||
startDate: LocalDate? = null,
|
||||
endDate: LocalDate? = null
|
||||
): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (selectedTypes.isEmpty()) {
|
||||
// Kein Filter - zeige alle Features
|
||||
featureLayer.definitionExpression = ""
|
||||
println("DEBUG: Filter entfernt - zeige alle Features")
|
||||
} else {
|
||||
// Erstelle WHERE-Klausel für SQL
|
||||
// Beispiel: "Typ IN ('Straße', 'Gehweg')"
|
||||
val typeList = selectedTypes.joinToString("', '", "'", "'")
|
||||
val whereClause = "Typ IN ($typeList)"
|
||||
val whereClauses = mutableListOf<String>()
|
||||
|
||||
featureLayer.definitionExpression = whereClause
|
||||
println("DEBUG: Filter angewendet: $whereClause")
|
||||
// ===== TYP-FILTER =====
|
||||
// WICHTIG: Nur hinzufügen wenn nicht ALLE Typen ausgewählt sind
|
||||
// Wenn alle ausgewählt sind, filtere nicht nach Typ (ermöglicht reinen Datumsfilter)
|
||||
if (selectedTypes.isNotEmpty() && selectedTypes.size < DAMAGE_TYPES.size) {
|
||||
val typeList = selectedTypes.joinToString("', '", "'", "'")
|
||||
whereClauses.add("Typ IN ($typeList)")
|
||||
}
|
||||
|
||||
// ===== DATUMS-FILTER =====
|
||||
// Feldname: EditDate
|
||||
if (startDate != null || endDate != null) {
|
||||
val dateField = "EditDate"
|
||||
|
||||
when {
|
||||
startDate != null && endDate != null -> {
|
||||
whereClauses.add("$dateField >= timestamp '$startDate 00:00:00' AND $dateField <= timestamp '$endDate 23:59:59'")
|
||||
}
|
||||
startDate != null -> {
|
||||
whereClauses.add("$dateField >= timestamp '$startDate 00:00:00'")
|
||||
}
|
||||
endDate != null -> {
|
||||
whereClauses.add("$dateField <= timestamp '$endDate 23:59:59'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== ALLE KLAUSELN KOMBINIEREN =====
|
||||
val whereClause = whereClauses.joinToString(" AND ")
|
||||
|
||||
featureLayer.definitionExpression = whereClause
|
||||
println("DEBUG: Filter angewendet: '$whereClause'")
|
||||
println("DEBUG: Typen: ${selectedTypes.size}/${DAMAGE_TYPES.size}, Datum aktiv: ${startDate != null || endDate != null}")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
snackBarMessage = if (selectedTypes.isEmpty()) {
|
||||
"Alle Schäden werden angezeigt"
|
||||
} else {
|
||||
"${selectedTypes.size} Typ(en) gefiltert"
|
||||
snackBarMessage = when {
|
||||
selectedTypes.isEmpty() && (startDate == null && endDate == null) -> {
|
||||
"Alle Schäden werden angezeigt"
|
||||
}
|
||||
selectedTypes.size == DAMAGE_TYPES.size && (startDate != null || endDate != null) -> {
|
||||
// NUR Datumsfilter aktiv (alle Typen ausgewählt)
|
||||
"Datumsfilter: ${startDate?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) ?: "?"} - ${endDate?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) ?: "?"}"
|
||||
}
|
||||
selectedTypes.size < DAMAGE_TYPES.size && (startDate == null && endDate == null) -> {
|
||||
// NUR Typ-Filter aktiv
|
||||
"${selectedTypes.size} Typ(en) gefiltert"
|
||||
}
|
||||
else -> {
|
||||
// BEIDE Filter aktiv
|
||||
val typeInfo = "${selectedTypes.size} Typ(en)"
|
||||
val dateInfo = "Datum: ${startDate?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) ?: "?"} - ${endDate?.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) ?: "?"}"
|
||||
"$typeInfo | $dateInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,14 +397,11 @@ suspend fun MapViewModel.applyDamageFilter(selectedTypes: Set<String>): Boolean
|
||||
* Gibt die aktuell aktiven Filter zurück
|
||||
*/
|
||||
fun MapViewModel.getActiveFilters(): Set<String> {
|
||||
|
||||
val expression = featureLayer.definitionExpression
|
||||
if (expression.isEmpty()) return emptySet()
|
||||
|
||||
|
||||
val regex = "'([^']+)'".toRegex()
|
||||
return regex.findAll(expression)
|
||||
.map { it.groupValues[1] }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user