- Code bereinigt
- MapView in MapSegment ausgelagert - Punkt kann jetzt manuell gesetzt werden - Eingaben werden erstmal im ReportDraft gespeichert
This commit is contained in:
@@ -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<String> = 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<ImageBitmap> = emptyList(),
|
||||
val point: Point? = null
|
||||
) {
|
||||
val isValid: Boolean
|
||||
get() =
|
||||
beschreibung.isNotBlank() &&
|
||||
typ != "Schadenstyp wählen..." &&
|
||||
point != null &&
|
||||
photos.isNotEmpty()
|
||||
}
|
||||
Reference in New Issue
Block a user