diff --git a/samples/query-features-with-arcade-expression/README.md b/samples/query-features-with-arcade-expression/README.md index 7e18d404b..818feb156 100644 --- a/samples/query-features-with-arcade-expression/README.md +++ b/samples/query-features-with-arcade-expression/README.md @@ -16,25 +16,26 @@ Tap on any neighborhood to see the number of crimes in the last 60 days in a Tex 1. Create a `PortalItem` using the URL and ID. 2. Create an `ArcGISMap` using the portal item. -3. Set up a listener for taps on the map. -4. Identify the visible layer where it is tapped using `mapView.identifyLayer()` and get the feature. -5. Create the following `ArcadeExpression`: +3. Create a `MapViewProxy` to handle user interaction with the map view. +4. Provide behaviour for the `MapView`'s `onSingleTapConfirmed` parameter to react to taps on the map. +5. Identify the visible layer where it is tapped using `mapViewProxy.identify()` and get the feature from the result. +6. Create the following `ArcadeExpression`: ```kotlin expressionValue = "var crimes = FeatureSetByName(\$map, 'Crime in the last 60 days');\n" + "return Count(Intersects(\$feature, crimes));" ``` -6. Create an `ArcadeEvaluator` using the Arcade expression and `ArcadeProfile.FormCalculation`. -7. Create a map of profile variables with the following key-value pairs: +7. Create an `ArcadeEvaluator` using the Arcade expression and `ArcadeProfile.FormCalculation`. +8. Create a map of profile variables with the following key-value pairs: ```kotlin mapOf("\$feature" to feature, "\$map" to mapView.map) ``` -8. Call `ArcadeEvaluator.evaluate()` on the Arcade evaluator object and pass the profile variables map. -9. Get the `ArcadeEvaluationResult.result`. -10. Convert the result to a numerical value (integer) and populate the UI with the crime count. +9. Call `ArcadeEvaluator.evaluate()` on the Arcade evaluator object and pass the profile variables map. +10. Get the `ArcadeEvaluationResult.result`. +11. Convert the result to a numerical value (`Double`) and pass it to the UI. ## Relevant API @@ -51,8 +52,10 @@ This sample uses the [Crimes in Police Beats Sample](https://www.arcgis.com/home ## Additional information +This sample uses the `GeoView-Compose` module of the [ArcGIS Maps SDK for Kotlin Toolkit](https://developers.arcgis.com/kotlin/toolkit/) to implement a Composable MapView. + Visit [Getting Started](https://developers.arcgis.com/arcade/) on the *ArcGIS Developer* website to learn more about Arcade expressions. ## Tags -Arcade evaluator, Arcade expression, identify layers, portal, portal item, query +Arcade evaluator, Arcade expression, geoview-compose, identify layers, portal, portal item, query, toolkit diff --git a/samples/query-features-with-arcade-expression/README.metadata.json b/samples/query-features-with-arcade-expression/README.metadata.json index e198507a7..6cc4b97da 100644 --- a/samples/query-features-with-arcade-expression/README.metadata.json +++ b/samples/query-features-with-arcade-expression/README.metadata.json @@ -9,10 +9,12 @@ "keywords": [ "Arcade evaluator", "Arcade expression", + "geoview-compose", "identify layers", "portal", "portal item", "query", + "toolkit", "ArcadeEvaluationResult", "ArcadeEvaluator", "ArcadeExpression", @@ -31,7 +33,9 @@ "PortalItem" ], "snippets": [ - "src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/MainActivity.kt" + "src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/components/QueryFeaturesWithArcadeExpressionViewModel.kt", + "src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/MainActivity.kt", + "src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/screens/QueryFeaturesWithArcadeExpressionScreen.kt" ], "title": "Query features with arcade expression" } diff --git a/samples/query-features-with-arcade-expression/build.gradle.kts b/samples/query-features-with-arcade-expression/build.gradle.kts index f56e6c5e4..e3d8367b4 100644 --- a/samples/query-features-with-arcade-expression/build.gradle.kts +++ b/samples/query-features-with-arcade-expression/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.arcgismaps.android.library) + alias(libs.plugins.arcgismaps.android.library.compose) alias(libs.plugins.arcgismaps.kotlin.sample) alias(libs.plugins.gradle.secrets) } @@ -11,9 +12,7 @@ secrets { android { namespace = "com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression" - // For view based samples buildFeatures { - dataBinding = true buildConfig = true } } diff --git a/samples/query-features-with-arcade-expression/query-features-with-arcade-expression.png b/samples/query-features-with-arcade-expression/query-features-with-arcade-expression.png index 272920613..48c970ea5 100644 Binary files a/samples/query-features-with-arcade-expression/query-features-with-arcade-expression.png and b/samples/query-features-with-arcade-expression/query-features-with-arcade-expression.png differ diff --git a/samples/query-features-with-arcade-expression/src/main/AndroidManifest.xml b/samples/query-features-with-arcade-expression/src/main/AndroidManifest.xml index de1c23fc5..7b43ec50d 100644 --- a/samples/query-features-with-arcade-expression/src/main/AndroidManifest.xml +++ b/samples/query-features-with-arcade-expression/src/main/AndroidManifest.xml @@ -3,11 +3,11 @@ - + - diff --git a/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/MainActivity.kt b/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/MainActivity.kt index 49ae10fe4..a101f05da 100644 --- a/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/MainActivity.kt +++ b/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/MainActivity.kt @@ -1,5 +1,4 @@ -/* - * Copyright 2023 Esri +/* Copyright 2024 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,193 +16,38 @@ package com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression -import android.graphics.BitmapFactory -import android.graphics.drawable.BitmapDrawable import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.lifecycleScope +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable import com.arcgismaps.ApiKey import com.arcgismaps.ArcGISEnvironment -import com.arcgismaps.arcade.ArcadeEvaluator -import com.arcgismaps.arcade.ArcadeExpression -import com.arcgismaps.arcade.ArcadeProfile -import com.arcgismaps.data.ArcGISFeature -import com.arcgismaps.mapping.ArcGISMap -import com.arcgismaps.mapping.layers.Layer -import com.arcgismaps.mapping.symbology.PictureMarkerSymbol -import com.arcgismaps.mapping.view.Graphic -import com.arcgismaps.mapping.view.GraphicsOverlay -import com.arcgismaps.mapping.view.ScreenCoordinate -import com.arcgismaps.portal.Portal -import com.arcgismaps.mapping.PortalItem -import com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.databinding.QueryFeaturesWithArcadeExpressionActivityMainBinding -import com.google.android.material.snackbar.Snackbar -import kotlinx.coroutines.launch +import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme +import com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.screens.QueryFeaturesWithArcadeExpressionScreen -class MainActivity : AppCompatActivity() { - - // set up data binding for the activity - private val activityMainBinding: QueryFeaturesWithArcadeExpressionActivityMainBinding by lazy { - DataBindingUtil.setContentView(this, R.layout.query_features_with_arcade_expression_activity_main) - } - - private val mapView by lazy { - activityMainBinding.mapView - } - - private val infoTextView by lazy { - activityMainBinding.infoTextView - } - - // progress indicator - private val progressBar by lazy { - activityMainBinding.progressBar - } - - // setup the red pin marker image as a bitmap drawable - private val markerDrawable: BitmapDrawable by lazy { - // load the bitmap from resources and create a drawable - val bitmap = BitmapFactory.decodeResource(resources, R.drawable.map_pin_symbol) - BitmapDrawable(resources, bitmap) - } - - // setup the red pin marker as a Graphic - private val markerGraphic: Graphic by lazy { - // creates a symbol from the marker drawable - val markerSymbol = PictureMarkerSymbol.createWithImage(markerDrawable).apply { - // resize the symbol into a smaller size - width = 30f - height = 30f - // offset in +y axis so the marker spawned is right on the touch point - offsetY = 25f - } - // create the graphic from the symbol - Graphic(symbol = markerSymbol) - } - - // create a graphic overlay - private val graphicsOverlay = GraphicsOverlay() +class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // authentication with an API key or named user is // required to access basemaps and other location services ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN) - lifecycle.addObserver(mapView) - - // create a portal item with the itemId of the web map - val portal = Portal("https://www.arcgis.com/") - val portalItem = PortalItem(portal, "539d93de54c7422f88f69bfac2aebf7d") - // create and add a map with with portal item - val map = ArcGISMap(portalItem) - // add the marker graphic to the graphics overlay - graphicsOverlay.graphics.add(markerGraphic) - mapView.apply { - this.map = map - // add the graphics overlay to the MapView - graphicsOverlays.add(graphicsOverlay) - } - - lifecycleScope.launch { - // show an error and return if the map load failed - map.load().onFailure { - return@launch showError("Error loading map:${it.message}") - } - - // get the RPD Beats layer from the map's operational layers - val policeBeatsLayer = map.operationalLayers.firstOrNull { layer -> - layer.id == "RPD_Reorg_9254" - } ?: return@launch showError("Error finding RPD Beats layer") - // capture and collect when the user taps on the screen - mapView.onSingleTapConfirmed.collect { event -> - // update the marker location to where the user tapped on the map - event.mapPoint?.let { point -> - markerGraphic.geometry = point - mapView.setViewpointCenter(point) - } - // evaluate an Arcade expression on the tapped screen coordinate - evaluateArcadeExpression(event.screenCoordinate, map, policeBeatsLayer) + setContent { + SampleAppTheme { + QueryFeaturesWithArcadeExpressionApp() } } } - /** - * Evaluates an Arcade expression that returns crime in the last 60 days at the tapped - * [screenCoordinate] on the [map] with the [policeBeatsLayer] and displays the result - * in a textview - */ - private suspend fun evaluateArcadeExpression( - screenCoordinate: ScreenCoordinate, - map: ArcGISMap, - policeBeatsLayer: Layer - ) { - // show the progress indicator as the Arcade evaluation can take time to complete - progressBar.visibility = View.VISIBLE - // identify the layer and its elements based on the position tapped on the mapView and - // get the result - val result = mapView.identifyLayer( - layer = policeBeatsLayer, - screenCoordinate = screenCoordinate, - tolerance = 12.0, - returnPopupsOnly = false - ) - // get the result as an IdentifyLayerResult - val identifyLayerResult = result.getOrElse { error -> - // if the identifyLayer operation failed show an error and return - showError("Error identifying layer:${error.message}") - // reset the text view to show its default text - infoTextView.text = getString(R.string.tap_to_begin) - // dismiss the progress indicator - progressBar.visibility = View.GONE - return - } - // if there are no geoElements identified - if (identifyLayerResult.geoElements.isEmpty()) { - // since the layer is a feature layer, display that no features were found - infoTextView.text = getString(R.string.no_features_found) - // dismiss the progress indicator - progressBar.visibility = View.GONE - return + @Composable + private fun QueryFeaturesWithArcadeExpressionApp() { + Surface(color = MaterialTheme.colorScheme.background) { + QueryFeaturesWithArcadeExpressionScreen( + sampleName = getString(R.string.query_features_with_arcade_expression_app_name) + ) } - // get the first identified GeoElement as an ArcGISFeature - val identifiedFeature = identifyLayerResult.geoElements.first() as ArcGISFeature - // create a string containing the Arcade expression - val expressionValue = - "var crimes = FeatureSetByName(\$map, 'Crime in the last 60 days');\n" + - "return Count(Intersects(\$feature, crimes));" - // create an ArcadeExpression using the string expression - val arcadeExpression = ArcadeExpression(expressionValue) - // create an ArcadeEvaluator with the ArcadeExpression and an ArcadeProfile - val arcadeEvaluator = ArcadeEvaluator(arcadeExpression, ArcadeProfile.FormCalculation) - // create a map of profile variables with the feature and map as key value pairs - val profileVariables = mapOf("\$feature" to identifiedFeature, "\$map" to map) - // evaluate using the previously set profile variables and get the result - val evaluationResult = arcadeEvaluator.evaluate(profileVariables) - // get the result as an ArcadeEvaluationResult - val arcadeEvaluationResult = evaluationResult.getOrElse { error -> - // if the evaluation failed show an error and return - showError("Error evaluating Arcade expression:${error.message}") - // reset the text view to show its default text - infoTextView.text = getString(R.string.tap_to_begin) - // dismiss the progress indicator - progressBar.visibility = View.GONE - return - } - // get the crimes count from the arcadeEvaluationResult as a numerical double value - val crimesCount = arcadeEvaluationResult.result as Double - // display this result in a textview - infoTextView.text = getString(R.string.crime_info_text, crimesCount.toInt()) - // hide the progress indicator - progressBar.visibility = View.GONE - } - - private fun showError(message: String) { - Log.e(localClassName, message) - Snackbar.make(mapView, message, Snackbar.LENGTH_SHORT).show() } } diff --git a/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/components/QueryFeaturesWithArcadeExpressionViewModel.kt b/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/components/QueryFeaturesWithArcadeExpressionViewModel.kt new file mode 100644 index 000000000..29c58d12c --- /dev/null +++ b/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/components/QueryFeaturesWithArcadeExpressionViewModel.kt @@ -0,0 +1,191 @@ +/* Copyright 2024 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. + * + */ + +package com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.components + +import android.app.Application +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable +import androidx.compose.ui.unit.dp +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.arcgismaps.arcade.ArcadeEvaluator +import com.arcgismaps.arcade.ArcadeExpression +import com.arcgismaps.arcade.ArcadeProfile +import com.arcgismaps.data.ArcGISFeature +import com.arcgismaps.geometry.Point +import com.arcgismaps.mapping.ArcGISMap +import com.arcgismaps.mapping.PortalItem +import com.arcgismaps.mapping.layers.Layer +import com.arcgismaps.mapping.symbology.PictureMarkerSymbol +import com.arcgismaps.mapping.view.Graphic +import com.arcgismaps.mapping.view.GraphicsOverlay +import com.arcgismaps.mapping.view.ScreenCoordinate +import com.arcgismaps.portal.Portal +import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy +import com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.R +import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel +import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class QueryFeaturesWithArcadeExpressionViewModel(application: Application) : + AndroidViewModel(application) { + + // setup the red pin marker image as a bitmap drawable + private val markerDrawable: BitmapDrawable by lazy { + val bitmap = BitmapFactory.decodeResource(application.resources, R.drawable.map_pin_symbol) + BitmapDrawable(application.resources, bitmap) + } + + // setup the red pin marker as a Graphic + private val markerGraphic: Graphic by lazy { + val markerSymbol = PictureMarkerSymbol.createWithImage(markerDrawable).apply { + width = 30f + height = 30f + offsetY = 25f + } + + Graphic(symbol = markerSymbol) + } + + // data layer to be loaded from portal item + private var policeBeatsLayer: Layer? = null + + // state flow to expose query results and status to UI + private val _queryStateFlow = MutableStateFlow(QueryState(loadState = LoadState.LOADING)) + val queryStateFlow = _queryStateFlow.asStateFlow() + + val graphicsOverlay = GraphicsOverlay() + + // create a portal item with the itemId of the web map + private val portal = Portal("https://www.arcgis.com/") + private val portalItem = PortalItem(portal = portal, itemId = "539d93de54c7422f88f69bfac2aebf7d") + + // create a map from the portal item + val arcGISMap = ArcGISMap(portalItem) + + // create a map view proxy for handling interactions with the map view + val mapViewProxy = MapViewProxy() + + // create a message dialog view model for handling error messages + val messageDialogVM = MessageDialogViewModel() + + init { + viewModelScope.launch { + arcGISMap.load().onFailure { error -> + messageDialogVM.showMessageDialog( + "Failed to load map", + error.message.toString() + ) + } + + // get the RPD Beats layer from the map's operational layers + policeBeatsLayer = arcGISMap.operationalLayers.firstOrNull { layer -> + layer.id == "RPD_Reorg_9254" + } + + // update query state, map is ready for user interaction + _queryStateFlow.value = QueryState(loadState = LoadState.READY_TO_START) + } + + // add the marker graphic to the graphics overlay + graphicsOverlay.graphics.add(markerGraphic) + } + + /** + * Handle a tap on the map view from the user + */ + fun handleTap(point: Point, screenCoordinate: ScreenCoordinate) { + // update the marker location to where the user tapped on the map + markerGraphic.geometry = point + viewModelScope.launch { + // centre the viewpoint on where the user tapped on the map + mapViewProxy.setViewpointCenter(point) + + // evaluate an Arcade expression on the tapped screen coordinate + evaluateArcadeExpression(screenCoordinate) + } + } + + /** + * Evaluates an Arcade expression that returns crime in the last 60 days at the tapped + * [screenCoordinate] on the [arcGISMap] with the [policeBeatsLayer] and outputs the result + * to the [queryStateFlow] property. + */ + private suspend fun evaluateArcadeExpression(screenCoordinate: ScreenCoordinate) { + policeBeatsLayer?.let { layer -> + // show the loading spinner as the Arcade evaluation can take time to complete + _queryStateFlow.value = QueryState(loadState = LoadState.LOADING) + + // do an identify operation on the policeBeatsLayer, using the position tapped on the + // mapView, and get the result + val result = mapViewProxy.identify( + layer = layer, + screenCoordinate = screenCoordinate, + tolerance = 12.dp, + returnPopupsOnly = false + ) + + // get the result as an IdentifyLayerResult + val identifyLayerResult = result.getOrElse { error -> + // if the identify operation failed show an error and return + messageDialogVM.showMessageDialog( + "Error performing identify operation:", + error.message.toString() + ) + // reset the query results and loading indicator + _queryStateFlow.value = QueryState() + return + } + + if (identifyLayerResult.geoElements.isEmpty()) { + _queryStateFlow.value = QueryState(loadState = LoadState.LOADED) + return + } + + // get the first identified GeoElement as an ArcGISFeature + val identifiedFeature = identifyLayerResult.geoElements.first() as ArcGISFeature + // create a string containing the Arcade expression + val expressionValue = "var crimes = FeatureSetByName(\$map, 'Crime in the last 60 days');\n" + + "return Count(Intersects(\$feature, crimes));" + + // create an arcade expression from the string and configure an arcade evaluator + val arcadeExpression = ArcadeExpression(expressionValue) + val arcadeEvaluator = ArcadeEvaluator(arcadeExpression, ArcadeProfile.FormCalculation) + + // create a map of profile variables with the feature and arcGISMap as key-value pairs + val profileVariables = mapOf("\$feature" to identifiedFeature, "\$map" to arcGISMap) + // evaluate the arcade expression using these profile variables, and get the result + val evaluationResult = arcadeEvaluator.evaluate(profileVariables) + val arcadeEvaluationResult = evaluationResult.getOrElse { error -> + messageDialogVM.showMessageDialog("Error", error.message.toString()) + _queryStateFlow.value = QueryState() + return + } + + _queryStateFlow.value = QueryState(arcadeEvaluationResult.result as Double, LoadState.LOADED) + } + } +} + +data class QueryState(val crimes: Double? = null, val loadState: LoadState = LoadState.READY_TO_START) + +enum class LoadState { + READY_TO_START, + LOADING, + LOADED +} diff --git a/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/screens/QueryFeaturesWithArcadeExpressionScreen.kt b/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/screens/QueryFeaturesWithArcadeExpressionScreen.kt new file mode 100644 index 000000000..a6951ab2e --- /dev/null +++ b/samples/query-features-with-arcade-expression/src/main/java/com/esri/arcgismaps/sample/queryfeatureswitharcadeexpression/screens/QueryFeaturesWithArcadeExpressionScreen.kt @@ -0,0 +1,123 @@ +/* Copyright 2024 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. + * + */ + +package com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.arcgismaps.toolkit.geoviewcompose.MapView +import com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.R +import com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.components.LoadState +import com.esri.arcgismaps.sample.queryfeatureswitharcadeexpression.components.QueryFeaturesWithArcadeExpressionViewModel +import com.esri.arcgismaps.sample.sampleslib.components.MessageDialog +import com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar + +/** + * Main screen layout for the sample app + */ +@Composable +fun QueryFeaturesWithArcadeExpressionScreen(sampleName: String) { + val mapViewModel: QueryFeaturesWithArcadeExpressionViewModel = viewModel() + + val queryState by mapViewModel.queryStateFlow.collectAsStateWithLifecycle() + + Scaffold( + topBar = { SampleTopAppBar(title = sampleName) }, + content = { + Column( + modifier = Modifier + .fillMaxSize() + .padding(it), + ) { + Box( + Modifier + .fillMaxSize() + .weight(1f), + contentAlignment = Alignment.Center + ) { + MapView( + modifier = Modifier.fillMaxSize(), + arcGISMap = mapViewModel.arcGISMap, + graphicsOverlays = listOf(mapViewModel.graphicsOverlay), + mapViewProxy = mapViewModel.mapViewProxy, + onSingleTapConfirmed = { tapEvent -> + tapEvent.mapPoint?.let { point -> + mapViewModel.handleTap( + point = point, + screenCoordinate = tapEvent.screenCoordinate + ) + } + } + ) + if (queryState.loadState == LoadState.LOADING) { + CircularProgressIndicator( + modifier = Modifier.width(96.dp), + ) + } + } + Row( + Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.Center + ) { + val resultText = + queryState.crimes?.let { stringResource(R.string.crime_info_text, it.toInt()) } + ?: stringResource(R.string.no_features_found) + + Text( + text = when (queryState.loadState) { + LoadState.READY_TO_START -> stringResource(R.string.tap_to_begin) + LoadState.LOADING -> stringResource(R.string.loading) + LoadState.LOADED -> resultText + }, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + mapViewModel.messageDialogVM.apply { + if (dialogStatus) { + MessageDialog( + title = messageTitle, + description = messageDescription, + onDismissRequest = ::dismissDialog + ) + } + } + } + ) +} diff --git a/samples/query-features-with-arcade-expression/src/main/res/layout/query_features_with_arcade_expression_activity_main.xml b/samples/query-features-with-arcade-expression/src/main/res/layout/query_features_with_arcade_expression_activity_main.xml deleted file mode 100644 index 18cddafa4..000000000 --- a/samples/query-features-with-arcade-expression/src/main/res/layout/query_features_with_arcade_expression_activity_main.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - diff --git a/samples/query-features-with-arcade-expression/src/main/res/values/strings.xml b/samples/query-features-with-arcade-expression/src/main/res/values/strings.xml index f2a6344d1..945661c3a 100644 --- a/samples/query-features-with-arcade-expression/src/main/res/values/strings.xml +++ b/samples/query-features-with-arcade-expression/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Query features with arcade expression Tap to begin - Crime in the last 60 days: %1d + Crimes in the last 60 days: %1d No features found + Loading…