Add feature management functionality to MapPage and MapViewModel with feature creation, deletion, attribute updates, geometry updates, and dropdown UI component integration.
This commit is contained in:
@@ -1,26 +1,270 @@
|
|||||||
package de.jadehs.strassenschadenpro2
|
package de.jadehs.strassenschadenpro2
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.AndroidViewModel
|
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.ServiceFeatureTable
|
import com.arcgismaps.data.ServiceFeatureTable
|
||||||
|
import com.arcgismaps.geometry.GeometryEngine
|
||||||
import com.arcgismaps.mapping.ArcGISMap
|
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.view.ScreenCoordinate
|
||||||
|
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
|
||||||
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
|
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
|
||||||
import de.jadehs.strassenschadenpro2.pages.SettingsPage
|
import de.jadehs.strassenschadenpro2.pages.SettingsPage
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MapViewModel(application: Application): AndroidViewModel(application) {
|
class MapViewModel(application: Application): AndroidViewModel(application) {
|
||||||
val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle)
|
val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply {
|
||||||
|
initialViewpoint = Viewpoint(53.14, 8.20, 20000.0)
|
||||||
|
}
|
||||||
|
// Hold a reference to the selected feature.
|
||||||
|
var selectedFeature: ArcGISFeature? by mutableStateOf(null)
|
||||||
val mapViewProxy = MapViewProxy()
|
val mapViewProxy = MapViewProxy()
|
||||||
|
|
||||||
|
var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
|
||||||
|
|
||||||
lateinit var featureLayer: FeatureLayer
|
lateinit var featureLayer: FeatureLayer
|
||||||
|
|
||||||
|
// Create a snackbar message to display the result of feature operations.
|
||||||
|
var snackBarMessage: String by mutableStateOf("")
|
||||||
|
|
||||||
|
lateinit var serviceFeatureTable: ServiceFeatureTable
|
||||||
|
|
||||||
|
var currentDamageType by mutableStateOf("")
|
||||||
|
|
||||||
|
// The list of damage types to update the feature attribute.
|
||||||
|
var damageTypeList: List<String> = mutableListOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val serviceFeatureTable = ServiceFeatureTable("https://services9.arcgis.com/UVxdrlZq3S3gqt7w/ArcGIS/rest/services/StrassenSchaeden/FeatureServer/0")
|
viewModelScope.launch {
|
||||||
featureLayer = FeatureLayer.createWithFeatureTable(serviceFeatureTable)
|
serviceFeatureTable = ServiceFeatureTable("https://services9.arcgis.com/UVxdrlZq3S3gqt7w/ArcGIS/rest/services/StrassenSchaeden/FeatureServer/0")
|
||||||
map.operationalLayers.add(featureLayer)
|
serviceFeatureTable.load().onSuccess {
|
||||||
map.initialViewpoint = Viewpoint(53.14, 8.20, 20000.0)
|
// Get the field from the feature table that will be updated.
|
||||||
|
val typeDamageField = serviceFeatureTable.fields.first { it.name == "Typ" }
|
||||||
|
// Get the coded value domain for the field.
|
||||||
|
val attributeDomain = typeDamageField.domain as CodedValueDomain
|
||||||
|
// Add the damage types to the list.
|
||||||
|
attributeDomain.codedValues.forEach {
|
||||||
|
damageTypeList += it.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
featureLayer = FeatureLayer.createWithFeatureTable(serviceFeatureTable)
|
||||||
|
map.operationalLayers.add(featureLayer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
|
||||||
|
* selection.
|
||||||
|
*/
|
||||||
|
fun onFeatureOperationSelected(index: Int) {
|
||||||
|
currentFeatureOperation = FeatureOperationType.entries[index]
|
||||||
|
// Reset the selected feature when the operation changes.
|
||||||
|
featureLayer?.clearSelection()
|
||||||
|
selectedFeature = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directs the behaviour of tap's on the map view.
|
||||||
|
*/
|
||||||
|
fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
|
||||||
|
if (featureLayer?.loadStatus?.value != LoadStatus.Loaded) {
|
||||||
|
snackBarMessage = "Layer not loaded!"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when (currentFeatureOperation) {
|
||||||
|
FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
|
||||||
|
FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
|
||||||
|
FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
|
||||||
|
FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new feature at the tapped location with some default attributes
|
||||||
|
*/
|
||||||
|
private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
|
||||||
|
// Create the feature.
|
||||||
|
val feature = serviceFeatureTable?.createFeature()?.apply {
|
||||||
|
// Get the normalized geometry for the tapped location and use it as the feature's geometry.
|
||||||
|
mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
|
||||||
|
geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
|
||||||
|
// Set feature attributes.
|
||||||
|
attributes["Typ"] = "SL"
|
||||||
|
attributes["Beschreibung"] = "Earthquake"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
feature?.let {
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Add the feature to the table.
|
||||||
|
serviceFeatureTable?.addFeature(it)
|
||||||
|
// Apply the edits to the service on the service geodatabase.
|
||||||
|
serviceFeatureTable?.applyEdits()
|
||||||
|
// Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
|
||||||
|
it.refresh()
|
||||||
|
// Confirm feature addition.
|
||||||
|
snackBarMessage = "Created feature ${it.attributes["objectid"]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a feature at the tapped location in preparation for deletion.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the selected feature from the feature table and service geodatabase.
|
||||||
|
*/
|
||||||
|
fun deleteSelectedFeature() {
|
||||||
|
selectedFeature?.let {
|
||||||
|
// Get the feature's object id.
|
||||||
|
val featureId = it.attributes["objectid"] as Long
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Delete the feature from the feature table.
|
||||||
|
serviceFeatureTable?.deleteFeature(it)?.onSuccess {
|
||||||
|
snackBarMessage = "Deleted feature $featureId"
|
||||||
|
// Apply the edits to the service geodatabase.
|
||||||
|
serviceFeatureTable?.applyEdits()
|
||||||
|
selectedFeature = null
|
||||||
|
}?.onFailure {
|
||||||
|
snackBarMessage = "Failed to delete feature $featureId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a feature at the tapped location in preparation for attribute editing.
|
||||||
|
*/
|
||||||
|
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 = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the attribute value of the selected feature to the new value from the new damage type.
|
||||||
|
*/
|
||||||
|
fun onDamageTypeSelected(index: Int) {
|
||||||
|
// Get the new value.
|
||||||
|
currentDamageType = damageTypeList[index]
|
||||||
|
selectedFeature?.let { selectedFeature ->
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Load the feature.
|
||||||
|
selectedFeature.load().onSuccess {
|
||||||
|
// Update the attribute value.
|
||||||
|
selectedFeature.attributes["Typ"] = currentDamageType
|
||||||
|
// Update the table.
|
||||||
|
serviceFeatureTable?.updateFeature(selectedFeature)
|
||||||
|
// Update the service on the service geodatabase.
|
||||||
|
serviceFeatureTable?.applyEdits()?.onSuccess {
|
||||||
|
snackBarMessage =
|
||||||
|
"Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
|
||||||
|
*/
|
||||||
|
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"]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FeatureOperationType(val operationName: String, val instruction: String) {
|
||||||
|
CREATE("Create feature", "Tap on the map to create a new feature."),
|
||||||
|
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.")
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/* Copyright 2025 Esri
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
|
package de.jadehs.strassenschadenpro2.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MenuAnchorType
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable component to simplify the usage of an [ExposedDropdownMenuBox].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun DropDownMenuBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
textFieldValue: String,
|
||||||
|
textFieldLabel: String,
|
||||||
|
dropDownItemList: List<String>,
|
||||||
|
onIndexSelected: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
modifier = modifier,
|
||||||
|
expanded = expanded,
|
||||||
|
onExpandedChange = { expanded = !expanded }
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
label = { Text(textFieldLabel) },
|
||||||
|
value = textFieldValue,
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||||
|
modifier = Modifier.menuAnchor(type = MenuAnchorType.PrimaryNotEditable)
|
||||||
|
)
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
dropDownItemList.forEachIndexed { index, scalebarStyle ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(scalebarStyle) },
|
||||||
|
onClick = {
|
||||||
|
onIndexSelected(index)
|
||||||
|
expanded = false
|
||||||
|
})
|
||||||
|
// Show a divider between dropdown menu options
|
||||||
|
if (index < dropDownItemList.lastIndex) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,14 +7,23 @@ import android.content.pm.PackageManager
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
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
|
||||||
@@ -25,7 +34,9 @@ import com.arcgismaps.mapping.Viewpoint
|
|||||||
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 de.jadehs.strassenschadenpro2.BuildConfig
|
import de.jadehs.strassenschadenpro2.BuildConfig
|
||||||
|
import de.jadehs.strassenschadenpro2.FeatureOperationType
|
||||||
import de.jadehs.strassenschadenpro2.MapViewModel
|
import de.jadehs.strassenschadenpro2.MapViewModel
|
||||||
|
import de.jadehs.strassenschadenpro2.components.DropDownMenuBox
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -33,9 +44,13 @@ fun MapPage(modifier: Modifier = Modifier, application: Application) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var featureManagementDropdownIndex by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
ArcGISEnvironment.applicationContext = context.applicationContext
|
ArcGISEnvironment.applicationContext = context.applicationContext
|
||||||
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ARCGIS_TOKEN)
|
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ARCGIS_TOKEN)
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
val locationDisplay = rememberLocationDisplay().apply {
|
val locationDisplay = rememberLocationDisplay().apply {
|
||||||
setAutoPanMode(LocationDisplayAutoPanMode.Off)
|
setAutoPanMode(LocationDisplayAutoPanMode.Off)
|
||||||
}
|
}
|
||||||
@@ -58,10 +73,60 @@ fun MapPage(modifier: Modifier = Modifier, application: Application) {
|
|||||||
|
|
||||||
val mapViewModel = remember { MapViewModel(application) }
|
val mapViewModel = remember { MapViewModel(application) }
|
||||||
|
|
||||||
MapView(modifier = Modifier.fillMaxSize(),
|
Column(
|
||||||
arcGISMap = mapViewModel.map,
|
modifier = Modifier
|
||||||
locationDisplay = locationDisplay
|
.fillMaxSize(),
|
||||||
)
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
MapView(
|
||||||
|
modifier = Modifier.weight(80f),
|
||||||
|
arcGISMap = mapViewModel.map,
|
||||||
|
locationDisplay = locationDisplay,
|
||||||
|
mapViewProxy = mapViewModel.mapViewProxy,
|
||||||
|
onSingleTapConfirmed = mapViewModel::onTap,
|
||||||
|
) {
|
||||||
|
mapViewModel.selectedFeature?.let { selectedFeature ->
|
||||||
|
// Only show the delete button when on the delete feature operation and a feature is selected.
|
||||||
|
if (mapViewModel.currentFeatureOperation == FeatureOperationType.DELETE) {
|
||||||
|
Callout(geoElement = selectedFeature) {
|
||||||
|
Button(onClick = mapViewModel::deleteSelectedFeature) {
|
||||||
|
Text(text = "Delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only show the dropdown for damage type when on the update feature operation.
|
||||||
|
if (mapViewModel.currentFeatureOperation == FeatureOperationType.UPDATE_ATTRIBUTE) {
|
||||||
|
Callout(geoElement = selectedFeature) {
|
||||||
|
DropDownMenuBox(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
textFieldLabel = "Select damage type",
|
||||||
|
textFieldValue = mapViewModel.currentDamageType,
|
||||||
|
dropDownItemList = mapViewModel.damageTypeList,
|
||||||
|
onIndexSelected = { index ->
|
||||||
|
if (mapViewModel.selectedFeature != null) {
|
||||||
|
mapViewModel.onDamageTypeSelected(index)
|
||||||
|
} else {
|
||||||
|
coroutineScope.launch {
|
||||||
|
snackbarHostState.showSnackbar("Please select a feature to update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DropDownMenuBox(
|
||||||
|
modifier = Modifier.weight(20f).padding(end = 8.dp),
|
||||||
|
textFieldLabel = "Feature management operation",
|
||||||
|
textFieldValue = mapViewModel.currentFeatureOperation.operationName,
|
||||||
|
dropDownItemList = FeatureOperationType.entries.map { entry -> entry.operationName },
|
||||||
|
onIndexSelected = { index ->
|
||||||
|
mapViewModel.onFeatureOperationSelected(index)
|
||||||
|
featureManagementDropdownIndex = index
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkPermissions(context: Context): Boolean {
|
fun checkPermissions(context: Context): Boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user