FeatureInfo Widget hinzugefügt
This commit is contained in:
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user