FeatureInfo Widget hinzugefügt

This commit is contained in:
2026-01-19 21:40:57 +01:00
parent c9b2b262a8
commit 33e95641d0
3 changed files with 112 additions and 64 deletions

View File

@@ -4,6 +4,7 @@ import MapViewModel
import android.Manifest import android.Manifest
import android.R.attr.enabled import android.R.attr.enabled
import android.app.Application import android.app.Application
import android.graphics.BitmapFactory
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -30,9 +31,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color 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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.arcgismaps.data.ArcGISFeature
// Hier holen wir die ArcGIS Klassen // Hier holen wir die ArcGIS Klassen
import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.BasemapStyle
@@ -88,6 +92,8 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
floatingActionButton = { floatingActionButton = {
LargeFloatingActionButton( LargeFloatingActionButton(
onClick = { onClick = {
mapViewModel.resetDraft()
mapViewModel.closeFeatureInfo()
openReport() openReport()
}, },
modifier = Modifier.offset(y = 64.dp), 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 // Slider von Links
SideSlider(visible = sliderOpen) { SideSlider(visible = sliderOpen) {
Text( Text(
@@ -332,7 +346,11 @@ fun ReportOverlay(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Button(onClick = { mapViewModel.pickCurrentLocation() }) { Button(
onClick = {
mapViewModel.pickCurrentLocation()
}
) {
Text(if (hasPoint) "Neue Position aus Standort" else "Position aus Standort") Text(if (hasPoint) "Neue Position aus Standort" else "Position aus Standort")
} }
Button( 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<ImageBitmap?>(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()
}

View File

@@ -21,10 +21,8 @@ import androidx.core.content.ContextCompat
import com.arcgismaps.ApiKey import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.Color import com.arcgismaps.Color
import com.arcgismaps.data.ArcGISFeature
import com.arcgismaps.location.LocationDisplayAutoPanMode 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.MapView
import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -40,7 +40,8 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
private set private set
var reopenReport by mutableStateOf(false) var reopenReport by mutableStateOf(false)
private set private set
var showFeatureInfo by mutableStateOf(false)
private set
var selectedFeature: ArcGISFeature? by mutableStateOf(null) var selectedFeature: ArcGISFeature? by mutableStateOf(null)
val mapViewProxy = MapViewProxy() val mapViewProxy = MapViewProxy()
var reportDraft by mutableStateOf(ReportDraft()) var reportDraft by mutableStateOf(ReportDraft())
@@ -63,7 +64,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
init { init {
tempOverlay.graphics.add(pointGraphic) tempOverlay.graphics.add(pointGraphic)
viewModelScope.launch { 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 { serviceFeatureTable.load().onSuccess {
val typeDamageField = serviceFeatureTable.fields.firstOrNull { it.name == "Typ" } val typeDamageField = serviceFeatureTable.fields.firstOrNull { it.name == "Typ" }
val attributeDomain = typeDamageField?.domain as? CodedValueDomain val attributeDomain = typeDamageField?.domain as? CodedValueDomain
@@ -79,55 +80,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
} }
} }
fun createFeatureAtCurrentLocation(
beschreibung: String,
typ: String,
photos: List<ImageBitmap> = 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() { fun pickCurrentLocation() {
// keine Coroutine nötig, das ist alles sync // keine Coroutine nötig, das ist alles sync
val pos = locationDisplay?.location?.value?.position val pos = locationDisplay?.location?.value?.position
@@ -146,7 +98,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
snackBarMessage = "Position aus GPS gesetzt." snackBarMessage = "Position aus GPS gesetzt."
} }
private suspend fun applyEditsWithPhotos(feature: ArcGISFeature, photos: List<ImageBitmap>) { private suspend fun applyEditsWithPhotos(feature: ArcGISFeature, photos: List<ImageBitmap>) {
serviceFeatureTable.applyEdits().onSuccess { editResults -> serviceFeatureTable.applyEdits().onSuccess { editResults ->
val result = editResults.firstOrNull() val result = editResults.firstOrNull()
@@ -231,7 +182,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
} }
when (selectedOperation) { when (selectedOperation) {
FeatureOperationType.DEFAULT -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.DEFAULT -> selectFeatureAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate) FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
@@ -282,7 +233,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
} }
private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) { private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
featureLayer?.let { featureLayer -> featureLayer?.let { featureLayer ->
when (selectedFeature) { when (selectedFeature) {
// When no feature is selected. // 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) { enum class FeatureOperationType(val operationName: String, val instruction: String) {