- Code bereinigt
- MapView in MapSegment ausgelagert - Punkt kann jetzt manuell gesetzt werden - Eingaben werden erstmal im ReportDraft gespeichert
This commit is contained in:
@@ -2,6 +2,7 @@ package com.example.snapandsolve
|
|||||||
|
|
||||||
import MapViewModel
|
import MapViewModel
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.R.attr.enabled
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
@@ -17,6 +18,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
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.FilterAlt
|
||||||
import androidx.compose.material.icons.filled.FormatListNumbered
|
import androidx.compose.material.icons.filled.FormatListNumbered
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
@@ -25,6 +28,7 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
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.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -44,10 +48,30 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(modifier: Modifier = Modifier, application: Application) {
|
fun MainScreen(modifier: Modifier = Modifier, application: Application) {
|
||||||
var showReport by rememberSaveable { mutableStateOf(false) }
|
var showReport by rememberSaveable { mutableStateOf(false) }
|
||||||
var sliderOpen by remember { mutableStateOf(false) }
|
var sliderOpen by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val mapViewModel = remember { MapViewModel(application) }
|
val mapViewModel = remember { MapViewModel(application) }
|
||||||
val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) }
|
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(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = { AppTopBar() },
|
topBar = { AppTopBar() },
|
||||||
@@ -64,8 +88,7 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
LargeFloatingActionButton(
|
LargeFloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
showReport = true
|
openReport()
|
||||||
sliderOpen = false
|
|
||||||
},
|
},
|
||||||
modifier = Modifier.offset(y = 64.dp),
|
modifier = Modifier.offset(y = 64.dp),
|
||||||
containerColor = ButtonColor
|
containerColor = ButtonColor
|
||||||
@@ -77,11 +100,11 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
|
|||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
ContentScreen(
|
ContentScreen(
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
mapViewModel,
|
mapViewModel = mapViewModel,
|
||||||
albumViewModel,
|
albumViewModel = albumViewModel,
|
||||||
showReport = showReport,
|
showReport = showReport,
|
||||||
sliderOpen = sliderOpen,
|
sliderOpen = sliderOpen,
|
||||||
onDismissReport = { showReport = false }
|
onDismissReport = ::closeReport
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +138,9 @@ fun ContentScreen(
|
|||||||
)
|
)
|
||||||
onDismissReport()
|
onDismissReport()
|
||||||
},
|
},
|
||||||
viewModel = albumViewModel
|
onClose = onDismissReport,
|
||||||
|
viewModel = albumViewModel,
|
||||||
|
mapViewModel = mapViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,16 +191,21 @@ fun AppTopBar(
|
|||||||
fun ReportOverlay(
|
fun ReportOverlay(
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onAdd: (beschreibung: String, typ: String) -> Unit,
|
onAdd: (beschreibung: String, typ: String) -> Unit,
|
||||||
viewModel: AlbumViewModel
|
onClose: () -> Unit,
|
||||||
|
viewModel: AlbumViewModel,
|
||||||
|
mapViewModel: MapViewModel
|
||||||
) {
|
) {
|
||||||
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
|
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
|
||||||
val currentContext = LocalContext.current
|
val currentContext = LocalContext.current
|
||||||
|
val hasPoint = mapViewModel.reportDraft.point != null
|
||||||
// State für Beschreibung und Typ
|
|
||||||
var beschreibung by remember { mutableStateOf("") }
|
|
||||||
var selectedTyp by remember { mutableStateOf("Schadenstyp wählen...") }
|
|
||||||
var dropdownExpanded by remember { mutableStateOf(false) }
|
var dropdownExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(viewState.selectedPictures) {
|
||||||
|
mapViewModel.updateReportDraft {
|
||||||
|
copy(photos = viewState.selectedPictures)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Launcher für Bildauswahl aus Galerie
|
// Launcher für Bildauswahl aus Galerie
|
||||||
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
|
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.PickMultipleVisualMedia(20)
|
ActivityResultContracts.PickMultipleVisualMedia(20)
|
||||||
@@ -229,10 +259,17 @@ fun ReportOverlay(
|
|||||||
.background(Color.Black.copy(alpha = 0.25f)),
|
.background(Color.Black.copy(alpha = 0.25f)),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
BoxWithConstraints {
|
||||||
|
val verticalMargin = 50.dp // <-- dein gewünschter Mindestabstand oben + unten
|
||||||
|
val maxCardHeight = maxHeight - verticalMargin * 2
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.9f)
|
.fillMaxWidth(0.9f)
|
||||||
.heightIn(min = 400.dp),
|
.heightIn(
|
||||||
|
min = 400.dp,
|
||||||
|
max = maxCardHeight
|
||||||
|
),
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(24.dp),
|
||||||
colors = CardColors(
|
colors = CardColors(
|
||||||
containerColor = WidgetColor,
|
containerColor = WidgetColor,
|
||||||
@@ -256,17 +293,27 @@ fun ReportOverlay(
|
|||||||
onClick = { dropdownExpanded = true },
|
onClick = { dropdownExpanded = true },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text("Typ: $selectedTyp")
|
Text("Typ: ${mapViewModel.reportDraft.typ}")
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = dropdownExpanded,
|
expanded = dropdownExpanded,
|
||||||
onDismissRequest = { dropdownExpanded = false }
|
onDismissRequest = { dropdownExpanded = false }
|
||||||
) {
|
) {
|
||||||
listOf("Straße", "Gehweg", "Fahrradweg", "Beleuchtung","Sonstiges").forEach { typ ->
|
listOf(
|
||||||
|
"Straße",
|
||||||
|
"Gehweg",
|
||||||
|
"Fahrradweg",
|
||||||
|
"Beleuchtung",
|
||||||
|
"Sonstiges"
|
||||||
|
).forEach { typ ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(typ) },
|
text = {
|
||||||
|
Text(typ)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
selectedTyp = typ
|
mapViewModel.updateReportDraft {
|
||||||
|
copy(typ = typ)
|
||||||
|
}
|
||||||
dropdownExpanded = false
|
dropdownExpanded = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -299,8 +346,12 @@ fun ReportOverlay(
|
|||||||
|
|
||||||
// Textfeld für Beschreibung
|
// Textfeld für Beschreibung
|
||||||
TextField(
|
TextField(
|
||||||
value = beschreibung,
|
value = mapViewModel.reportDraft.beschreibung,
|
||||||
onValueChange = { beschreibung = it },
|
onValueChange = {
|
||||||
|
mapViewModel.updateReportDraft {
|
||||||
|
copy(beschreibung = it)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(220.dp),
|
.height(220.dp),
|
||||||
@@ -335,13 +386,38 @@ fun ReportOverlay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text("Position aus Standort")
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
mapViewModel.startPickReportLocation()
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(if (hasPoint) "Position neu setzen" else "Position manuell setzen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = onCancel,
|
onClick = {
|
||||||
|
mapViewModel.resetDraft()
|
||||||
|
viewModel.clearSelection()
|
||||||
|
onCancel()
|
||||||
|
},
|
||||||
colors = ButtonColors(
|
colors = ButtonColors(
|
||||||
containerColor = ButtonColor,
|
containerColor = ButtonColor,
|
||||||
contentColor = Color.White,
|
contentColor = Color.White,
|
||||||
@@ -352,8 +428,12 @@ fun ReportOverlay(
|
|||||||
Text("Abbrechen", color = Color.Black)
|
Text("Abbrechen", color = Color.Black)
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = { onAdd(beschreibung, selectedTyp) },
|
onClick = {
|
||||||
enabled = beschreibung.isNotBlank()
|
mapViewModel.submitDraftToLayer()
|
||||||
|
viewModel.clearSelection()
|
||||||
|
onCancel()
|
||||||
|
},
|
||||||
|
enabled = mapViewModel.reportDraft.isValid
|
||||||
) {
|
) {
|
||||||
Text("Hinzufügen")
|
Text("Hinzufügen")
|
||||||
}
|
}
|
||||||
@@ -361,4 +441,5 @@ fun ReportOverlay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,11 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.core.content.ContextCompat
|
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.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
|
||||||
@@ -68,7 +72,8 @@ fun MapSegment(
|
|||||||
arcGISMap = mapViewModel.map,
|
arcGISMap = mapViewModel.map,
|
||||||
locationDisplay = locationDisplay,
|
locationDisplay = locationDisplay,
|
||||||
mapViewProxy = mapViewModel.mapViewProxy,
|
mapViewProxy = mapViewModel.mapViewProxy,
|
||||||
onSingleTapConfirmed = {}//mapViewModel::onTap,
|
onSingleTapConfirmed = mapViewModel::onTap,
|
||||||
|
graphicsOverlays = listOf(mapViewModel.tempOverlay)
|
||||||
) {
|
) {
|
||||||
/* TODO */
|
/* TODO */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import android.app.Application
|
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.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.arcgismaps.LoadStatus
|
import com.arcgismaps.LoadStatus
|
||||||
import com.arcgismaps.data.ArcGISFeature
|
import com.arcgismaps.data.ArcGISFeature
|
||||||
import com.arcgismaps.data.CodedValueDomain
|
import com.arcgismaps.data.CodedValueDomain
|
||||||
|
import com.arcgismaps.data.QueryParameters
|
||||||
import com.arcgismaps.data.ServiceFeatureTable
|
import com.arcgismaps.data.ServiceFeatureTable
|
||||||
import com.arcgismaps.geometry.GeometryEngine
|
import com.arcgismaps.geometry.GeometryEngine
|
||||||
import com.arcgismaps.geometry.Point
|
import com.arcgismaps.geometry.Point
|
||||||
@@ -17,25 +23,46 @@ import com.arcgismaps.mapping.ArcGISMap
|
|||||||
import com.arcgismaps.mapping.BasemapStyle
|
import com.arcgismaps.mapping.BasemapStyle
|
||||||
import com.arcgismaps.mapping.Viewpoint
|
import com.arcgismaps.mapping.Viewpoint
|
||||||
import com.arcgismaps.mapping.layers.FeatureLayer
|
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.LocationDisplay
|
||||||
|
import com.arcgismaps.mapping.view.ScreenCoordinate
|
||||||
|
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
|
||||||
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
|
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
|
||||||
class MapViewModel(application: Application) : AndroidViewModel(application) {
|
class MapViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply {
|
val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply {
|
||||||
initialViewpoint = Viewpoint(53.14, 8.20, 20000.0)
|
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)
|
var selectedFeature: ArcGISFeature? by mutableStateOf(null)
|
||||||
val mapViewProxy = MapViewProxy()
|
val mapViewProxy = MapViewProxy()
|
||||||
|
var reportDraft by mutableStateOf(ReportDraft())
|
||||||
|
private set
|
||||||
lateinit var featureLayer: FeatureLayer
|
lateinit var featureLayer: FeatureLayer
|
||||||
var snackBarMessage: String by mutableStateOf("")
|
var snackBarMessage: String by mutableStateOf("")
|
||||||
lateinit var serviceFeatureTable: ServiceFeatureTable
|
lateinit var serviceFeatureTable: ServiceFeatureTable
|
||||||
var currentDamageType by mutableStateOf("")
|
var currentDamageType by mutableStateOf("")
|
||||||
var damageTypeList: List<String> = mutableListOf()
|
var damageTypeList: List<String> = mutableListOf()
|
||||||
var locationDisplay: LocationDisplay? = null
|
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 {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -113,7 +140,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
if (photos.isNotEmpty()) {
|
if (photos.isNotEmpty()) {
|
||||||
// Fix: erstellen eine Abfrage für die neue ID
|
// Fix: erstellen eine Abfrage für die neue ID
|
||||||
val queryParameters = com.arcgismaps.data.QueryParameters().apply {
|
val queryParameters = QueryParameters().apply {
|
||||||
objectIds.add(serverObjectId)
|
objectIds.add(serverObjectId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +203,213 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
private fun imageBitmapToByteArray(imageBitmap: ImageBitmap): ByteArray {
|
private fun imageBitmapToByteArray(imageBitmap: ImageBitmap): ByteArray {
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
imageBitmap.asAndroidBitmap().compress(android.graphics.Bitmap.CompressFormat.JPEG, 80, stream)
|
imageBitmap.asAndroidBitmap().compress(Bitmap.CompressFormat.JPEG, 80, stream)
|
||||||
return stream.toByteArray()
|
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()
|
||||||
}
|
}
|
||||||
@@ -116,4 +116,7 @@ class AlbumViewModel(private val coroutineContext: CoroutineContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
fun clearSelection() {
|
||||||
|
_albumViewState.value = _albumViewState.value.copy(selectedPictures = emptyList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user