diff --git a/app/src/main/java/com/example/snapandsolve/MainScreen.kt b/app/src/main/java/com/example/snapandsolve/MainScreen.kt index b052dd7..7b9b216 100644 --- a/app/src/main/java/com/example/snapandsolve/MainScreen.kt +++ b/app/src/main/java/com/example/snapandsolve/MainScreen.kt @@ -4,6 +4,7 @@ import MapViewModel import android.Manifest import android.R.attr.enabled import android.app.Application +import android.graphics.BitmapFactory import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -30,9 +31,12 @@ 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.graphics.ImageBitmap +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 com.arcgismaps.data.ArcGISFeature // Hier holen wir die ArcGIS Klassen import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.BasemapStyle @@ -88,6 +92,8 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) { floatingActionButton = { LargeFloatingActionButton( onClick = { + mapViewModel.resetDraft() + mapViewModel.closeFeatureInfo() openReport() }, modifier = Modifier.offset(y = 64.dp), @@ -136,6 +142,14 @@ fun ContentScreen( ) } + if (mapViewModel.showFeatureInfo && mapViewModel.selectedFeature != null) { + FeatureInfoOverlay( + feature = mapViewModel.selectedFeature!!, + onClose = { mapViewModel.closeFeatureInfo() } + ) + } + + // Slider von Links SideSlider(visible = sliderOpen) { Text( @@ -332,7 +346,11 @@ fun ReportOverlay( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { - Button(onClick = { mapViewModel.pickCurrentLocation() }) { + Button( + onClick = { + mapViewModel.pickCurrentLocation() + } + ) { Text(if (hasPoint) "Neue Position aus Standort" else "Position aus Standort") } Button( @@ -346,3 +364,64 @@ fun ReportOverlay( } } } + +@Composable +fun FeatureInfoOverlay( + feature: ArcGISFeature, + onClose: () -> Unit +) { + val typ = feature.attributes["Typ"].toString() + val beschreibung = feature.attributes["Beschreibung"].toString() + val id = feature.attributes["OBJECTID"].toString() + var image by remember { mutableStateOf(null) } + + LaunchedEffect(feature) { + image = loadFirstAttachmentBitmap(feature) + } + + + OverlayShell( + title = "Meldung $id", + footer = { + OutlinedButton( + onClick = { + onClose() + } + ) { Text("Schließen", color = Color.Black) } + } + ) { + image?.let { + Image( + bitmap = it, + contentDescription = "Feature Bild", + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + contentScale = ContentScale.Crop + ) + } + Text("Typ: $typ", color = Color.Black) + Text("Beschreibung:", color = Color.Black) + Text(beschreibung, color = Color.Black) + } +} + +suspend fun loadFirstAttachmentBitmap( + feature: ArcGISFeature +): ImageBitmap? { + + // Feature muss geladen sein + feature.load().getOrThrow() + + // Attachments abrufen + val attachments = feature.fetchAttachments().getOrThrow() + + val first = attachments.firstOrNull() ?: return null + + // Attachment-Daten laden + val data = first.fetchData().getOrThrow() + + val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) + return bitmap.asImageBitmap() +} + diff --git a/app/src/main/java/com/example/snapandsolve/MapSegment.kt b/app/src/main/java/com/example/snapandsolve/MapSegment.kt index 898bfa3..9776a3e 100644 --- a/app/src/main/java/com/example/snapandsolve/MapSegment.kt +++ b/app/src/main/java/com/example/snapandsolve/MapSegment.kt @@ -21,10 +21,8 @@ import androidx.core.content.ContextCompat import com.arcgismaps.ApiKey import com.arcgismaps.ArcGISEnvironment import com.arcgismaps.Color +import com.arcgismaps.data.ArcGISFeature 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 @@ -127,4 +125,4 @@ fun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) { ) ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/snapandsolve/MapViewModel.kt b/app/src/main/java/com/example/snapandsolve/MapViewModel.kt index 08bd15f..7478835 100644 --- a/app/src/main/java/com/example/snapandsolve/MapViewModel.kt +++ b/app/src/main/java/com/example/snapandsolve/MapViewModel.kt @@ -40,7 +40,8 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { private set var reopenReport by mutableStateOf(false) private set - + var showFeatureInfo by mutableStateOf(false) + private set var selectedFeature: ArcGISFeature? by mutableStateOf(null) val mapViewProxy = MapViewProxy() var reportDraft by mutableStateOf(ReportDraft()) @@ -63,7 +64,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { init { tempOverlay.graphics.add(pointGraphic) viewModelScope.launch { - serviceFeatureTable = ServiceFeatureTable("https://services9.arcgis.com/UVxdrlZq3S3gqt7w/arcgis/rest/services/si_StrassenSchaeden/FeatureServer/0") + serviceFeatureTable = ServiceFeatureTable("https://services9.arcgis.com/UVxdrlZq3S3gqt7w/arcgis/rest/services/251120_StrassenSchaeden/FeatureServer/0") serviceFeatureTable.load().onSuccess { val typeDamageField = serviceFeatureTable.fields.firstOrNull { it.name == "Typ" } val attributeDomain = typeDamageField?.domain as? CodedValueDomain @@ -79,55 +80,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { } } - fun createFeatureAtCurrentLocation( - beschreibung: String, - typ: String, - photos: List = emptyList() - ) { - viewModelScope.launch { - println("DEBUG: createFeature gestartet") // Erscheint das in der Logcat? - try { - // 1. Location prüfen - val locationData = locationDisplay?.location?.value - val gpsPoint = locationData?.position - - if (gpsPoint == null) { - println("DEBUG: Standort ist NULL - GPS Signal fehlt!") - snackBarMessage = "Kein GPS Signal. Bitte kurz warten oder nach draußen gehen." - return@launch - } - - println("DEBUG: GPS gefunden: x=${gpsPoint.x}, y=${gpsPoint.y}") - - // 2. Feature erstellen - // Wichtig: explizit WGS84, falls es nicht schon hat - val pointWgs84 = Point(gpsPoint.x, gpsPoint.y, SpatialReference.wgs84()) - - val feature = serviceFeatureTable.createFeature().apply { - geometry = pointWgs84 - attributes["Typ"] = typ - attributes["Beschreibung"] = beschreibung - } - - println("DEBUG: Feature lokal erstellt. Sende an Server...") - - // 3. Hochladen - serviceFeatureTable.addFeature(feature).onSuccess { - println("DEBUG: addFeature erfolgreich") - applyEditsWithPhotos(feature as ArcGISFeature, photos) - }.onFailure { - println("DEBUG: addFeature FEHLER: ${it.message}") - snackBarMessage = "Fehler: ${it.message}" - } - - } catch (e: Exception) { - println("DEBUG: CRASH in createFeature: ${e.message}") - e.printStackTrace() - snackBarMessage = "Fehler: ${e.message}" - } - } - } - fun pickCurrentLocation() { // keine Coroutine nötig, das ist alles sync val pos = locationDisplay?.location?.value?.position @@ -146,7 +98,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { snackBarMessage = "Position aus GPS gesetzt." } - private suspend fun applyEditsWithPhotos(feature: ArcGISFeature, photos: List) { serviceFeatureTable.applyEdits().onSuccess { editResults -> val result = editResults.firstOrNull() @@ -231,7 +182,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { } when (selectedOperation) { - FeatureOperationType.DEFAULT -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate) + FeatureOperationType.DEFAULT -> selectFeatureAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate) @@ -282,7 +233,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { } private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) { - featureLayer?.let { featureLayer -> when (selectedFeature) { // When no feature is selected. @@ -398,12 +348,33 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { } } + fun selectFeatureAt(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 { + selectedFeature = it.also { + featureLayer.selectFeature(it) + showFeatureInfo = true + } + } + } + } + } + } - - - - - + fun closeFeatureInfo() { + showFeatureInfo = false + selectedFeature = null + featureLayer.clearSelection() + } } enum class FeatureOperationType(val operationName: String, val instruction: String) {