From 30d5a17e6e1c6094c3dcaf7465c88e14bd5780f3 Mon Sep 17 00:00:00 2001 From: fr2651 Date: Sun, 18 Jan 2026 16:49:03 +0100 Subject: [PATCH] - Code bereinigt - MapView in MapSegment ausgelagert - Punkt kann jetzt manuell gesetzt werden - Eingaben werden erstmal im ReportDraft gespeichert --- .../com/example/snapandsolve/MainScreen.kt | 339 +++++++++++------- .../com/example/snapandsolve/MapSegment.kt | 7 +- .../com/example/snapandsolve/MapViewModel.kt | 239 +++++++++++- .../snapandsolve/camera/AlbumViewModel.kt | 3 + 4 files changed, 455 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/example/snapandsolve/MainScreen.kt b/app/src/main/java/com/example/snapandsolve/MainScreen.kt index d122b82..7ebec38 100644 --- a/app/src/main/java/com/example/snapandsolve/MainScreen.kt +++ b/app/src/main/java/com/example/snapandsolve/MainScreen.kt @@ -2,6 +2,7 @@ package com.example.snapandsolve import MapViewModel import android.Manifest +import android.R.attr.enabled import android.app.Application import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest @@ -17,6 +18,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.AddLocation +import androidx.compose.material.icons.filled.AddLocationAlt import androidx.compose.material.icons.filled.FilterAlt import androidx.compose.material.icons.filled.FormatListNumbered import androidx.compose.material.icons.filled.Menu @@ -25,6 +28,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -44,10 +48,30 @@ import kotlinx.coroutines.Dispatchers @Composable fun MainScreen(modifier: Modifier = Modifier, application: Application) { var showReport by rememberSaveable { mutableStateOf(false) } - var sliderOpen by remember { mutableStateOf(false) } + var sliderOpen by rememberSaveable { mutableStateOf(false) } + val mapViewModel = remember { MapViewModel(application) } val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) } + // test + fun openReport() { + showReport = true + sliderOpen = false + } + + // test + fun closeReport() { + showReport = false + } + + LaunchedEffect(mapViewModel.reopenReport) { + if (mapViewModel.reopenReport) { + showReport = true + mapViewModel.consumeReopenReport() + } + } + + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { AppTopBar() }, @@ -64,8 +88,7 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) { floatingActionButton = { LargeFloatingActionButton( onClick = { - showReport = true - sliderOpen = false + openReport() }, modifier = Modifier.offset(y = 64.dp), containerColor = ButtonColor @@ -77,11 +100,11 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) { ) { innerPadding -> ContentScreen( modifier = Modifier.padding(innerPadding), - mapViewModel, - albumViewModel, + mapViewModel = mapViewModel, + albumViewModel = albumViewModel, showReport = showReport, sliderOpen = sliderOpen, - onDismissReport = { showReport = false } + onDismissReport = ::closeReport ) } } @@ -115,7 +138,9 @@ fun ContentScreen( ) onDismissReport() }, - viewModel = albumViewModel + onClose = onDismissReport, + viewModel = albumViewModel, + mapViewModel = mapViewModel ) } @@ -166,16 +191,21 @@ fun AppTopBar( fun ReportOverlay( onCancel: () -> Unit, onAdd: (beschreibung: String, typ: String) -> Unit, - viewModel: AlbumViewModel + onClose: () -> Unit, + viewModel: AlbumViewModel, + mapViewModel: MapViewModel ) { val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState() val currentContext = LocalContext.current - - // State für Beschreibung und Typ - var beschreibung by remember { mutableStateOf("") } - var selectedTyp by remember { mutableStateOf("Schadenstyp wählen...") } + val hasPoint = mapViewModel.reportDraft.point != null var dropdownExpanded by remember { mutableStateOf(false) } + LaunchedEffect(viewState.selectedPictures) { + mapViewModel.updateReportDraft { + copy(photos = viewState.selectedPictures) + } + } + // Launcher für Bildauswahl aus Galerie val pickImageFromAlbumLauncher = rememberLauncherForActivityResult( ActivityResultContracts.PickMultipleVisualMedia(20) @@ -229,136 +259,187 @@ fun ReportOverlay( .background(Color.Black.copy(alpha = 0.25f)), contentAlignment = Alignment.Center ) { - Card( - modifier = Modifier - .fillMaxWidth(0.9f) - .heightIn(min = 400.dp), - shape = RoundedCornerShape(24.dp), - colors = CardColors( - containerColor = WidgetColor, - contentColor = ButtonColor, - disabledContainerColor = Color.White, - disabledContentColor = Color.White - ) - ) { - Column( + BoxWithConstraints { + val verticalMargin = 50.dp // <-- dein gewünschter Mindestabstand oben + unten + val maxCardHeight = maxHeight - verticalMargin * 2 + + Card( modifier = Modifier - .fillMaxWidth() - .padding(20.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) + .fillMaxWidth(0.9f) + .heightIn( + min = 400.dp, + max = maxCardHeight + ), + shape = RoundedCornerShape(24.dp), + colors = CardColors( + containerColor = WidgetColor, + contentColor = ButtonColor, + disabledContainerColor = Color.White, + disabledContentColor = Color.White + ) ) { - Text("Schadensbeschreibung:", color = Color.Black) - - // Typ-Auswahl (Dropdown) - Box { - OutlinedButton( - onClick = { dropdownExpanded = true }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Typ: $selectedTyp") - } - DropdownMenu( - expanded = dropdownExpanded, - onDismissRequest = { dropdownExpanded = false } - ) { - listOf("Straße", "Gehweg", "Fahrradweg", "Beleuchtung","Sonstiges").forEach { typ -> - DropdownMenuItem( - text = { Text(typ) }, - onClick = { - selectedTyp = typ - dropdownExpanded = false - } - ) - } - } - } - - // Kamera-Buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Button( - onClick = { startCamera() }, - modifier = Modifier.weight(1f) - ) { - Text(text = "Foto aufnehmen") - } - Button( - onClick = { - pickImageFromAlbumLauncher.launch( - PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) - ) - }, - modifier = Modifier.weight(1f) - ) { - Text(text = "Aus Galerie") - } - } - - // Textfeld für Beschreibung - TextField( - value = beschreibung, - onValueChange = { beschreibung = it }, + Column( modifier = Modifier .fillMaxWidth() - .height(220.dp), - placeholder = { Text("Beschreibung eingeben...") }, - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.White, - unfocusedContainerColor = Color.White - ) - ) + .padding(20.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text("Schadensbeschreibung:", color = Color.Black) - // Bilder-Grid - if (viewState.selectedPictures.isNotEmpty()) { - Text( - text = "Ausgewählte Bilder (${viewState.selectedPictures.size})", - color = Color.Black - ) - LazyVerticalGrid( - columns = GridCells.Fixed(3), - userScrollEnabled = false, - modifier = Modifier - .fillMaxWidth() - .heightIn(0.dp, 200.dp) - ) { - itemsIndexed(viewState.selectedPictures) { index, picture -> - Image( - modifier = Modifier.padding(4.dp), - bitmap = picture, - contentDescription = "Bild ${index + 1}", - contentScale = ContentScale.Crop - ) + // Typ-Auswahl (Dropdown) + Box { + OutlinedButton( + onClick = { dropdownExpanded = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Typ: ${mapViewModel.reportDraft.typ}") + } + DropdownMenu( + expanded = dropdownExpanded, + onDismissRequest = { dropdownExpanded = false } + ) { + listOf( + "Straße", + "Gehweg", + "Fahrradweg", + "Beleuchtung", + "Sonstiges" + ).forEach { typ -> + DropdownMenuItem( + text = { + Text(typ) + }, + onClick = { + mapViewModel.updateReportDraft { + copy(typ = typ) + } + dropdownExpanded = false + } + ) + } } } - } - // Buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - OutlinedButton( - onClick = onCancel, - colors = ButtonColors( - containerColor = ButtonColor, - contentColor = Color.White, - disabledContainerColor = Color.White, - disabledContentColor = Color.White - ) + // Kamera-Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - Text("Abbrechen", color = Color.Black) + Button( + onClick = { startCamera() }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Foto aufnehmen") + } + Button( + onClick = { + pickImageFromAlbumLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Aus Galerie") + } } - Button( - onClick = { onAdd(beschreibung, selectedTyp) }, - enabled = beschreibung.isNotBlank() + + // Textfeld für Beschreibung + TextField( + value = mapViewModel.reportDraft.beschreibung, + onValueChange = { + mapViewModel.updateReportDraft { + copy(beschreibung = it) + } + }, + modifier = Modifier + .fillMaxWidth() + .height(220.dp), + placeholder = { Text("Beschreibung eingeben...") }, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White + ) + ) + + // Bilder-Grid + if (viewState.selectedPictures.isNotEmpty()) { + Text( + text = "Ausgewählte Bilder (${viewState.selectedPictures.size})", + color = Color.Black + ) + LazyVerticalGrid( + columns = GridCells.Fixed(3), + userScrollEnabled = false, + modifier = Modifier + .fillMaxWidth() + .heightIn(0.dp, 200.dp) + ) { + itemsIndexed(viewState.selectedPictures) { index, picture -> + Image( + modifier = Modifier.padding(4.dp), + bitmap = picture, + contentDescription = "Bild ${index + 1}", + contentScale = ContentScale.Crop + ) + } + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween ) { - Text("Hinzufügen") + Button( + onClick = { + + }, + ) { + Text("Position aus Standort") + } + Button( + onClick = { + mapViewModel.startPickReportLocation() + onClose() + }, + ) { + Text(if (hasPoint) "Position neu setzen" else "Position manuell setzen") + } + } + + // Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + OutlinedButton( + onClick = { + mapViewModel.resetDraft() + viewModel.clearSelection() + onCancel() + }, + colors = ButtonColors( + containerColor = ButtonColor, + contentColor = Color.White, + disabledContainerColor = Color.White, + disabledContentColor = Color.White + ) + ) { + Text("Abbrechen", color = Color.Black) + } + Button( + onClick = { + mapViewModel.submitDraftToLayer() + viewModel.clearSelection() + onCancel() + }, + enabled = mapViewModel.reportDraft.isValid + ) { + Text("Hinzufügen") + } } } } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/snapandsolve/MapSegment.kt b/app/src/main/java/com/example/snapandsolve/MapSegment.kt index e38c3b3..413f430 100644 --- a/app/src/main/java/com/example/snapandsolve/MapSegment.kt +++ b/app/src/main/java/com/example/snapandsolve/MapSegment.kt @@ -20,7 +20,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat import com.arcgismaps.ApiKey import com.arcgismaps.ArcGISEnvironment +import com.arcgismaps.Color import com.arcgismaps.location.LocationDisplayAutoPanMode +import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol +import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle +import com.arcgismaps.mapping.view.GraphicsOverlay import com.arcgismaps.toolkit.geoviewcompose.MapView import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay import kotlinx.coroutines.launch @@ -68,7 +72,8 @@ fun MapSegment( arcGISMap = mapViewModel.map, locationDisplay = locationDisplay, mapViewProxy = mapViewModel.mapViewProxy, - onSingleTapConfirmed = {}//mapViewModel::onTap, + onSingleTapConfirmed = mapViewModel::onTap, + graphicsOverlays = listOf(mapViewModel.tempOverlay) ) { /* TODO */ } diff --git a/app/src/main/java/com/example/snapandsolve/MapViewModel.kt b/app/src/main/java/com/example/snapandsolve/MapViewModel.kt index a58c322..f6a5e85 100644 --- a/app/src/main/java/com/example/snapandsolve/MapViewModel.kt +++ b/app/src/main/java/com/example/snapandsolve/MapViewModel.kt @@ -1,14 +1,20 @@ import android.app.Application +import android.graphics.Bitmap +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.unit.dp import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.arcgismaps.LoadStatus import com.arcgismaps.data.ArcGISFeature import com.arcgismaps.data.CodedValueDomain +import com.arcgismaps.data.QueryParameters import com.arcgismaps.data.ServiceFeatureTable import com.arcgismaps.geometry.GeometryEngine import com.arcgismaps.geometry.Point @@ -17,25 +23,46 @@ import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.mapping.layers.FeatureLayer +import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol +import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle +import com.arcgismaps.mapping.symbology.SimpleRenderer +import com.arcgismaps.mapping.view.Graphic +import com.arcgismaps.mapping.view.GraphicsOverlay import com.arcgismaps.mapping.view.LocationDisplay +import com.arcgismaps.mapping.view.ScreenCoordinate +import com.arcgismaps.mapping.view.SingleTapConfirmedEvent import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy import kotlinx.coroutines.launch import java.io.ByteArrayOutputStream + class MapViewModel(application: Application) : AndroidViewModel(application) { val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply { initialViewpoint = Viewpoint(53.14, 8.20, 20000.0) } + var selectedOperation by mutableStateOf(FeatureOperationType.DEFAULT) + private set + var reopenReport by mutableStateOf(false) + private set var selectedFeature: ArcGISFeature? by mutableStateOf(null) val mapViewProxy = MapViewProxy() - + var reportDraft by mutableStateOf(ReportDraft()) + private set lateinit var featureLayer: FeatureLayer var snackBarMessage: String by mutableStateOf("") lateinit var serviceFeatureTable: ServiceFeatureTable var currentDamageType by mutableStateOf("") var damageTypeList: List = mutableListOf() var locationDisplay: LocationDisplay? = null + // Create a red circle simple marker symbol. + val redCircleSymbol = SimpleMarkerSymbol( + style = SimpleMarkerSymbolStyle.Circle, + color = com.arcgismaps.Color.red, + size = 10.0f + ) + var pointGraphic = Graphic(null, redCircleSymbol) + val tempOverlay = GraphicsOverlay() init { viewModelScope.launch { @@ -113,7 +140,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { if (photos.isNotEmpty()) { // Fix: erstellen eine Abfrage für die neue ID - val queryParameters = com.arcgismaps.data.QueryParameters().apply { + val queryParameters = QueryParameters().apply { objectIds.add(serverObjectId) } @@ -176,7 +203,213 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { private fun imageBitmapToByteArray(imageBitmap: ImageBitmap): ByteArray { val stream = ByteArrayOutputStream() - imageBitmap.asAndroidBitmap().compress(android.graphics.Bitmap.CompressFormat.JPEG, 80, stream) + imageBitmap.asAndroidBitmap().compress(Bitmap.CompressFormat.JPEG, 80, stream) return stream.toByteArray() } + + fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) { + + if (featureLayer.loadStatus.value != LoadStatus.Loaded) { + snackBarMessage = "Layer not loaded!" + return + } + + when (selectedOperation) { + FeatureOperationType.DEFAULT -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate) + FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate) + FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate) + FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate) + FeatureOperationType.PICK_REPORT_LOCATION -> pickReportLocation(singleTapConfirmedEvent.screenCoordinate) + else -> {} + } + } + + private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) { + featureLayer?.let { featureLayer -> + // Clear any existing selection. + featureLayer.clearSelection() + selectedFeature = null + viewModelScope.launch { + // Determine if a user tapped on a feature. + mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult -> + selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also { + featureLayer.selectFeature(it) + } + } + } + } + } + + private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) { + featureLayer?.let { featureLayer -> + // Clear any existing selection. + featureLayer.clearSelection() + selectedFeature = null + viewModelScope.launch { + // Determine if a user tapped on a feature. + mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult -> + // Get the identified feature. + val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature + identifiedFeature?.let { + val currentAttributeValue = it.attributes["typ"] as String + currentDamageType = currentAttributeValue + selectedFeature = it.also { + featureLayer.selectFeature(it) + } + } ?: run { + // Reset damage type if no feature identified. + currentDamageType = "" + } + } + } + } + } + + private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) { + + featureLayer?.let { featureLayer -> + when (selectedFeature) { + // When no feature is selected. + null -> { + viewModelScope.launch { + // Determine if a user tapped on a feature. + mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult -> + // Get the identified feature. + val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature + identifiedFeature?.let { + selectedFeature = it.also { + featureLayer.selectFeature(it) + } + } + } + } + } + // When a feature is selected, update its geometry to the tapped location. + else -> { + mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint -> + // Normalize the point - needed when the tapped location is over the international date line. + val destinationPoint = GeometryEngine.normalizeCentralMeridian(mapPoint) + viewModelScope.launch { + selectedFeature?.let { selectedFeature -> + // Load the feature. + selectedFeature.load().onSuccess { + // Update the geometry of the selected feature. + selectedFeature.geometry = destinationPoint + // Apply the edit to the feature table. + serviceFeatureTable?.updateFeature(selectedFeature) + // Push the update to the service with the service geodatabase. + serviceFeatureTable?.applyEdits()?.onSuccess { + snackBarMessage = "Moved feature ${selectedFeature.attributes["objectid"]}" + }?.onFailure { + snackBarMessage = + "Failed to move feature ${selectedFeature.attributes["objectid"]}" + } + } + } + } + } + } + } + } + } + + private fun pickReportLocation(screen: ScreenCoordinate) { + val mapPoint = mapViewProxy.screenToLocationOrNull(screen) + val p = mapPoint?.let { GeometryEngine.normalizeCentralMeridian(it) as? Point } + + if (p != null) { + reportDraft = reportDraft.copy(point = p) + tempOverlay.graphics.clear() + pointGraphic.geometry = p + tempOverlay.graphics.add(pointGraphic) + reopenReport = true + snackBarMessage = "Position gesetzt." + } else { + snackBarMessage = "Position konnte nicht gesetzt werden." + } + + selectedOperation = FeatureOperationType.DEFAULT + } + + + fun startPickReportLocation() { + selectedOperation = FeatureOperationType.PICK_REPORT_LOCATION + snackBarMessage = "Tippe auf die Karte, um die Position zu setzen." + } + + fun consumeReopenReport() { + reopenReport = false + } + + fun resetDraft() { + reportDraft = ReportDraft() + pointGraphic.geometry = null + selectedOperation = FeatureOperationType.DEFAULT + } + + fun updateReportDraft(update: ReportDraft.() -> ReportDraft) { + reportDraft = reportDraft.update() + } + + fun submitDraftToLayer() { + val draft = reportDraft + if (!draft.isValid) { + snackBarMessage = "Bitte Beschreibung, Typ, Position und Fotos setzen." + return + } + + viewModelScope.launch { + try { + // 1) Feature lokal erstellen + val feature = serviceFeatureTable.createFeature().apply { + geometry = draft.point + attributes["Beschreibung"] = draft.beschreibung + attributes["Typ"] = draft.typ + } + + // 2) Erst addFeature + applyEdits => Feature existiert am Server (ObjectID) + serviceFeatureTable.addFeature(feature).onSuccess { + // applyEditsWithPhotos macht bei dir: applyEdits -> ObjectID holen -> feature neu queryn -> attachments adden + applyEditsWithPhotos(feature as ArcGISFeature, draft.photos) + + // Draft/Preview zurücksetzen (am besten nach Erfolg; fürs Erste hier ok) + resetDraft() + }.onFailure { + snackBarMessage = "Fehler beim Hinzufügen: ${it.message}" + } + + } catch (e: Exception) { + snackBarMessage = "Fehler: ${e.message}" + } + } + } + + + + + + + +} + +enum class FeatureOperationType(val operationName: String, val instruction: String) { + DEFAULT("Default", ""), + DELETE("Delete feature", "Select an existing feature to delete it."), + UPDATE_ATTRIBUTE("Update attribute", "Select an existing feature to edit its attribute."), + UPDATE_GEOMETRY("Update geometry", "Select an existing feature and tap the map to move it to a new position."), + PICK_REPORT_LOCATION("Pick report location", "Tippe auf die Karte, um die Position zu setzen."), +} + +data class ReportDraft( + val beschreibung: String = "", + val typ: String = "Schadenstyp wählen...", + val photos: List = emptyList(), + val point: Point? = null +) { + val isValid: Boolean + get() = + beschreibung.isNotBlank() && + typ != "Schadenstyp wählen..." && + point != null && + photos.isNotEmpty() } \ No newline at end of file diff --git a/app/src/main/java/com/example/snapandsolve/camera/AlbumViewModel.kt b/app/src/main/java/com/example/snapandsolve/camera/AlbumViewModel.kt index 10d780a..b241dee 100644 --- a/app/src/main/java/com/example/snapandsolve/camera/AlbumViewModel.kt +++ b/app/src/main/java/com/example/snapandsolve/camera/AlbumViewModel.kt @@ -116,4 +116,7 @@ class AlbumViewModel(private val coroutineContext: CoroutineContext } } // endregion + fun clearSelection() { + _albumViewState.value = _albumViewState.value.copy(selectedPictures = emptyList()) + } } \ No newline at end of file