diff --git a/arcgis_map_sdk/README.md b/arcgis_map_sdk/README.md index e381f0077..f4077a6a4 100644 --- a/arcgis_map_sdk/README.md +++ b/arcgis_map_sdk/README.md @@ -18,14 +18,6 @@ dependencies: ``` -### Android only setup -(Android) In `/android/app/proguard-rules.pro` add: - -``` --keep class dev.fluttercommunity.arcgis_map_sdk_android.** { *; } -``` - - ### Use ArcgisMap Integrate the `ArcgisMap` Widget diff --git a/arcgis_map_sdk_android/android/build.gradle b/arcgis_map_sdk_android/android/build.gradle index 351a61d51..787eea59a 100644 --- a/arcgis_map_sdk_android/android/build.gradle +++ b/arcgis_map_sdk_android/android/build.gradle @@ -2,14 +2,17 @@ group 'dev.fluttercommunity.arcgis_map_sdk_android' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.0' + ext.kotlin_version = '2.1.20' repositories { google() mavenCentral() + maven { + url 'https://esri.jfrog.io/artifactory/arcgis' + } } dependencies { - classpath 'com.android.tools.build:gradle:8.3.2' + classpath 'com.android.tools.build:gradle:8.7.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -55,7 +58,8 @@ android { } defaultConfig { - minSdkVersion 23 + minSdkVersion 26 + consumerProguardFiles 'proguard-rules.pro' } } @@ -63,6 +67,6 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'com.esri.arcgisruntime:arcgis-android:100.15.0' - implementation 'com.google.code.gson:gson:2.8.8' + implementation 'com.esri:arcgis-maps-kotlin:200.7.0' + implementation 'com.google.code.gson:gson:2.13.1' } diff --git a/arcgis_map_sdk_android/android/gradle/wrapper/gradle-wrapper.properties b/arcgis_map_sdk_android/android/gradle/wrapper/gradle-wrapper.properties index 5a0656b6e..9b303851b 100644 --- a/arcgis_map_sdk_android/android/gradle/wrapper/gradle-wrapper.properties +++ b/arcgis_map_sdk_android/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Oct 24 09:10:14 CEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/arcgis_map_sdk_android/android/proguard-rules.pro b/arcgis_map_sdk_android/android/proguard-rules.pro new file mode 100644 index 000000000..d13fed0a6 --- /dev/null +++ b/arcgis_map_sdk_android/android/proguard-rules.pro @@ -0,0 +1,2 @@ +-keep class dev.fluttercommunity.arcgis_map_sdk_android.model.** { *; } +-keep class dev.fluttercommunity.arcgis_map_sdk_android.util.** { *; } \ No newline at end of file diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt index 3da25be16..dd7366cbf 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapView.kt @@ -4,31 +4,30 @@ import android.content.Context import android.graphics.Bitmap import android.view.LayoutInflater import android.view.View -import com.esri.arcgisruntime.ArcGISRuntimeEnvironment -import com.esri.arcgisruntime.concurrent.ListenableFuture -import com.esri.arcgisruntime.geometry.GeometryEngine -import com.esri.arcgisruntime.geometry.Point -import com.esri.arcgisruntime.geometry.PointCollection -import com.esri.arcgisruntime.geometry.Polyline -import com.esri.arcgisruntime.geometry.SpatialReferences -import com.esri.arcgisruntime.layers.ArcGISVectorTiledLayer -import com.esri.arcgisruntime.loadable.LoadStatus.FAILED_TO_LOAD -import com.esri.arcgisruntime.loadable.LoadStatus.LOADED -import com.esri.arcgisruntime.loadable.LoadStatus.LOADING -import com.esri.arcgisruntime.loadable.LoadStatus.NOT_LOADED -import com.esri.arcgisruntime.loadable.LoadStatusChangedEvent -import com.esri.arcgisruntime.location.AndroidLocationDataSource -import com.esri.arcgisruntime.location.LocationDataSource -import com.esri.arcgisruntime.mapping.ArcGISMap -import com.esri.arcgisruntime.mapping.Basemap -import com.esri.arcgisruntime.mapping.BasemapStyle -import com.esri.arcgisruntime.mapping.Viewpoint -import com.esri.arcgisruntime.mapping.view.AnimationCurve -import com.esri.arcgisruntime.mapping.view.Graphic -import com.esri.arcgisruntime.mapping.view.GraphicsOverlay -import com.esri.arcgisruntime.mapping.view.LocationDisplay.AutoPanMode.* -import com.esri.arcgisruntime.mapping.view.MapView -import com.esri.arcgisruntime.symbology.Symbol +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.coroutineScope +import com.arcgismaps.ApiKey +import com.arcgismaps.ArcGISEnvironment +import com.arcgismaps.LicenseKey +import com.arcgismaps.LoadStatus +import com.arcgismaps.geometry.GeometryEngine +import com.arcgismaps.geometry.Point +import com.arcgismaps.geometry.Polyline +import com.arcgismaps.geometry.SpatialReference +import com.arcgismaps.location.CustomLocationDataSource +import com.arcgismaps.location.LocationDataSourceStatus +import com.arcgismaps.location.LocationDisplayAutoPanMode +import com.arcgismaps.location.SystemLocationDataSource +import com.arcgismaps.mapping.ArcGISMap +import com.arcgismaps.mapping.Basemap +import com.arcgismaps.mapping.BasemapStyle +import com.arcgismaps.mapping.Viewpoint +import com.arcgismaps.mapping.layers.ArcGISVectorTiledLayer +import com.arcgismaps.mapping.symbology.Symbol +import com.arcgismaps.mapping.view.AnimationCurve +import com.arcgismaps.mapping.view.Graphic +import com.arcgismaps.mapping.view.GraphicsOverlay +import com.arcgismaps.mapping.view.MapView import com.google.gson.reflect.TypeToken import dev.fluttercommunity.arcgis_map_sdk_android.model.AnimationOptions import dev.fluttercommunity.arcgis_map_sdk_android.model.ArcgisMapOptions @@ -41,6 +40,8 @@ import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.platform.PlatformView +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import java.io.ByteArrayOutputStream import kotlin.math.exp import kotlin.math.ln @@ -56,6 +57,7 @@ internal class ArcgisMapView( private val viewId: Int, private val mapOptions: ArcgisMapOptions, private val binding: FlutterPluginBinding, + private val lifecycle: Lifecycle, ) : PlatformView { private val view: View = LayoutInflater.from(context).inflate(R.layout.vector_map_view, null) @@ -68,59 +70,66 @@ internal class ArcgisMapView( private lateinit var zoomStreamHandler: ZoomStreamHandler private lateinit var centerPositionStreamHandler: CenterPositionStreamHandler - private val methodChannel = MethodChannel(binding.binaryMessenger, "dev.fluttercommunity.arcgis_map_sdk/$viewId") override fun getView(): View = view init { - mapOptions.apiKey?.let(ArcGISRuntimeEnvironment::setApiKey) - mapOptions.licenseKey?.let(ArcGISRuntimeEnvironment::setLicense) + mapOptions.apiKey?.let { ArcGISEnvironment.apiKey = ApiKey.create(it) } + mapOptions.licenseKey?.let { ArcGISEnvironment.setLicense(LicenseKey.create(it)!!) } initialZoom = mapOptions.zoom.roundToInt() mapView = view.findViewById(R.id.mapView) - - mapOptions.isAttributionTextVisible?.let { mapView.isAttributionTextVisible = it } + lifecycle.addObserver(mapView) + mapOptions.isAttributionTextVisible?.let { mapView.isAttributionBarVisible = it } map.apply { - basemap = if (mapOptions.basemap != null) { + val basemap = if (mapOptions.basemap != null) { Basemap(mapOptions.basemap) } else { val layers = mapOptions.vectorTilesUrls.map { url -> ArcGISVectorTiledLayer(url) } - Basemap(layers, null) + Basemap(layers) } + setBasemap(basemap) + minScale = getMapScale(mapOptions.minZoom) maxScale = getMapScale(mapOptions.maxZoom) - addLoadStatusChangedListener(::onLoadStatusChanged) + lifecycle.coroutineScope.launch { + loadStatus.collect(::onLoadStatusChanged) + } } mapView.map = map mapView.graphicsOverlays.add(defaultGraphicsOverlay) - mapView.addMapScaleChangedListener { - if (mapView.mapScale.isNaN()) return@addMapScaleChangedListener + lifecycle.coroutineScope.launch { + mapView.mapScale.collect { scale -> + if (scale.isNaN()) return@collect - val zoomLevel = getZoomLevel(mapView) - zoomStreamHandler.addZoom(zoomLevel) + val zoomLevel = getZoomLevel(mapView) + zoomStreamHandler.addZoom(zoomLevel) + } } + lifecycle.coroutineScope.launch { + mapView.viewpointChanged.collect { + // The viewpoint listener is executed async which means that the map + // can be altered when this is called. If we reload the map or dispose the map + // we don't have a visibleArea or an extent which would throw null pointer in this case. + val center = mapView.visibleArea?.extent?.center ?: return@collect + val wgs84Center = + GeometryEngine.projectOrNull(center, SpatialReference.wgs84()) as? Point + + if (wgs84Center == null || wgs84Center.x.isNaN() || wgs84Center.y.isNaN()) { + return@collect + } - mapView.addViewpointChangedListener { - // The viewpoint listener is executed async which means that the map - // can be altered when this is called. If we reload the map or dispose the map - // we don't have a visibleArea or an extent which would throw null pointer in this case. - val center = mapView.visibleArea?.extent?.center ?: return@addViewpointChangedListener - val wgs84Center = GeometryEngine.project(center, SpatialReferences.getWgs84()) as? Point + val latLng = LatLng(longitude = wgs84Center.x, latitude = wgs84Center.y) - if (wgs84Center == null || wgs84Center.x.isNaN() || wgs84Center.y.isNaN()) { - return@addViewpointChangedListener + centerPositionStreamHandler.add(latLng) } - - val latLng = LatLng(longitude = wgs84Center.x, latitude = wgs84Center.y) - - centerPositionStreamHandler.add(latLng) } val viewPoint = Viewpoint( @@ -135,15 +144,12 @@ internal class ArcgisMapView( setupEventChannel() } - private fun onLoadStatusChanged(event: LoadStatusChangedEvent?) { - if (event == null) return - methodChannel.invokeMethod("onStatusChanged", event.jsonValue()) + private fun onLoadStatusChanged(status: LoadStatus?) { + if (status == null) return + methodChannel.invokeMethod("onStatusChanged", status.jsonValue()) } - override fun dispose() { - map.removeLoadStatusChangedListener(::onLoadStatusChanged) - mapView.dispose() - } + override fun dispose() {} // region helper @@ -164,8 +170,7 @@ internal class ArcgisMapView( "location_display_start_data_source" -> onStartLocationDisplayDataSource(result) "location_display_stop_data_source" -> onStopLocationDisplayDataSource(result) "location_display_set_default_symbol" -> onSetLocationDisplayDefaultSymbol( - call, - result + call, result ) "set_auto_pan_mode" -> onSetAutoPanMode(call = call, result = result) @@ -173,33 +178,27 @@ internal class ArcgisMapView( "set_wander_extent_factor" -> onSetWanderExtentFactor(call = call, result = result) "get_wander_extent_factor" -> onGetWanderExtentFactor(call = call, result = result) "location_display_set_accuracy_symbol" -> onSetLocationDisplayAccuracySymbol( - call, - result + call, result ) "location_display_set_ping_animation_symbol" -> onSetLocationDisplayPingAnimationSymbol( - call, - result + call, result ) "location_display_set_use_course_symbol_on_move" -> onSetLocationDisplayUseCourseSymbolOnMove( - call, - result + call, result ) "location_display_update_display_source_position_manually" -> onUpdateLocationDisplaySourcePositionManually( - call, - result + call, result ) "location_display_set_data_source_type" -> onSetLocationDisplayDataSourceType( - call, - result + call, result ) "update_is_attribution_text_visible" -> onUpdateIsAttributionTextVisible( - call, - result + call, result ) "export_image" -> onExportImage(result) @@ -216,16 +215,29 @@ internal class ArcgisMapView( return } - mapView.isAttributionTextVisible = isVisible + mapView.isAttributionBarVisible = isVisible result.success(true) } private fun onStartLocationDisplayDataSource(result: MethodChannel.Result) { - result.finishWithFuture { mapView.locationDisplay.locationDataSource.startAsync() } + lifecycle.coroutineScope.launch { + mapView.locationDisplay.dataSource.start().onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) + } + } } + private fun onStopLocationDisplayDataSource(result: MethodChannel.Result) { - result.finishWithFuture { mapView.locationDisplay.locationDataSource.stopAsync() } + lifecycle.coroutineScope.launch { + mapView.locationDisplay.dataSource.stop().onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) + } + } } private fun onSetLocationDisplayDefaultSymbol(call: MethodCall, result: MethodChannel.Result) { @@ -241,8 +253,7 @@ internal class ArcgisMapView( } private fun onSetLocationDisplayPingAnimationSymbol( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { finishOperationWithSymbol(call, result) { symbol -> mapView.locationDisplay.pingAnimationSymbol = symbol @@ -250,12 +261,11 @@ internal class ArcgisMapView( } private fun onSetLocationDisplayUseCourseSymbolOnMove( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { try { val active = call.arguments as Boolean - mapView.locationDisplay.isUseCourseSymbolOnMovement = active + mapView.locationDisplay.useCourseSymbolOnMovement = active result.success(true) } catch (e: Throwable) { result.finishWithError(e) @@ -263,25 +273,24 @@ internal class ArcgisMapView( } private fun onUpdateLocationDisplaySourcePositionManually( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { try { - val dataSource = - mapView.locationDisplay.locationDataSource as ManualLocationDisplayDataSource + val dataSource = mapView.locationDisplay.dataSource as CustomLocationDataSource + val provider = dataSource.currentProvider as CustomLocationProvider val optionParams = call.arguments as Map val position = optionParams.parseToClass() - - dataSource.setNewLocation(position) - result.success(true) + lifecycle.coroutineScope.launch { + provider.updateLocation(position) + result.success(true) + } } catch (e: Throwable) { result.finishWithError(e) } } private fun onSetAutoPanMode( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { try { val mode = call.arguments as String? @@ -295,7 +304,7 @@ internal class ArcgisMapView( } val autoPanMode = mode.autoPanModeFromString() if (autoPanMode != null) { - mapView.locationDisplay.autoPanMode = autoPanMode + mapView.locationDisplay.setAutoPanMode(autoPanMode) result.success(true) } else { result.error( @@ -310,16 +319,15 @@ internal class ArcgisMapView( } private fun onGetAutoPanMode( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { try { return result.success( - when (mapView.locationDisplay.autoPanMode) { - OFF -> "off" - RECENTER -> "recenter" - NAVIGATION -> "navigation" - COMPASS_NAVIGATION -> "compassNavigation" + when (mapView.locationDisplay.autoPanMode.value) { + LocationDisplayAutoPanMode.Off -> "off" + LocationDisplayAutoPanMode.Recenter -> "recenter" + LocationDisplayAutoPanMode.Navigation -> "navigation" + LocationDisplayAutoPanMode.CompassNavigation -> "compassNavigation" } ) } catch (e: Throwable) { @@ -328,8 +336,7 @@ internal class ArcgisMapView( } private fun onSetWanderExtentFactor( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { try { val factor = call.arguments as Double? @@ -349,14 +356,13 @@ internal class ArcgisMapView( } private fun onGetWanderExtentFactor( - call: MethodCall, - result: MethodChannel.Result + call: MethodCall, result: MethodChannel.Result ) { return result.success(mapView.locationDisplay.wanderExtentFactor) } private fun onSetLocationDisplayDataSourceType(call: MethodCall, result: MethodChannel.Result) { - if (mapView.locationDisplay.locationDataSource.status == LocationDataSource.Status.STARTED) { + if (mapView.locationDisplay.dataSource.status.value == LocationDataSourceStatus.Started) { result.error( "invalid_state", "Current data source is running. Make sure to stop it before setting a new data source", @@ -368,7 +374,9 @@ internal class ArcgisMapView( when (call.arguments) { "manual" -> { try { - mapView.locationDisplay.locationDataSource = ManualLocationDisplayDataSource() + mapView.locationDisplay.dataSource = CustomLocationDataSource { + CustomLocationProvider() + } result.success(true) } catch (e: Throwable) { result.finishWithError(e, info = "Setting datasource on mapview failed") @@ -377,7 +385,7 @@ internal class ArcgisMapView( "system" -> { try { - mapView.locationDisplay.locationDataSource = AndroidLocationDataSource(context) + mapView.locationDisplay.dataSource = SystemLocationDataSource() result.success(true) } catch (e: Throwable) { result.finishWithError(e, "Setting datasource on mapview failed") @@ -398,67 +406,75 @@ internal class ArcgisMapView( zoomStreamHandler = ZoomStreamHandler() centerPositionStreamHandler = CenterPositionStreamHandler() - EventChannel(binding.binaryMessenger, "dev.fluttercommunity.arcgis_map_sdk/$viewId/zoom") - .setStreamHandler(zoomStreamHandler) - EventChannel( binding.binaryMessenger, - "dev.fluttercommunity.arcgis_map_sdk/$viewId/centerPosition" - ) - .setStreamHandler(centerPositionStreamHandler) + "dev.fluttercommunity.arcgis_map_sdk/$viewId/zoom" + ).setStreamHandler(zoomStreamHandler) + + EventChannel( + binding.binaryMessenger, "dev.fluttercommunity.arcgis_map_sdk/$viewId/centerPosition" + ).setStreamHandler(centerPositionStreamHandler) } private fun onZoomIn(call: MethodCall, result: MethodChannel.Result) { - if (mapView.mapScale.isNaN()) { + if (mapView.mapScale.value.isNaN()) { result.error( - "Error", - "MapView.mapScale is NaN. Maybe the map is not completely loaded.", - null + "Error", "MapView.mapScale is NaN. Maybe the map is not completely loaded.", null ) return } - try { - val lodFactor = call.argument("lodFactor")!! - val currentZoomLevel = getZoomLevel(mapView) - val totalZoomLevel = currentZoomLevel + lodFactor - if (totalZoomLevel > mapOptions.maxZoom) { - return + val lodFactor = call.argument("lodFactor")!! + val currentZoomLevel = getZoomLevel(mapView) + val totalZoomLevel = currentZoomLevel + lodFactor + if (totalZoomLevel > mapOptions.maxZoom) { + return + } + val newScale = getMapScale(totalZoomLevel) + lifecycle.coroutineScope.launch { + mapView.setViewpointScale(newScale).onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) } - val newScale = getMapScale(totalZoomLevel) - result.finishWithFuture { mapView.setViewpointScaleAsync(newScale) } - } catch (e: Throwable) { - result.finishWithError(e) } } private fun onZoomOut(call: MethodCall, result: MethodChannel.Result) { - if (mapView.mapScale.isNaN()) { + if (mapView.mapScale.value.isNaN()) { result.error( - "Error", - "MapView.mapScale is NaN. Maybe the map is not completely loaded.", - null + "Error", "MapView.mapScale is NaN. Maybe the map is not completely loaded.", null ) return } - try { - val lodFactor = call.argument("lodFactor")!! - val currentZoomLevel = getZoomLevel(mapView) - val totalZoomLevel = currentZoomLevel - lodFactor - if (totalZoomLevel < mapOptions.minZoom) { - return + + val lodFactor = call.argument("lodFactor")!! + val currentZoomLevel = getZoomLevel(mapView) + val totalZoomLevel = currentZoomLevel - lodFactor + if (totalZoomLevel < mapOptions.minZoom) { + return + } + val newScale = getMapScale(totalZoomLevel) + lifecycle.coroutineScope.launch { + mapView.setViewpointScale(newScale).onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) } - val newScale = getMapScale(totalZoomLevel) - result.finishWithFuture { mapView.setViewpointScaleAsync(newScale) } - } catch (e: Throwable) { - result.finishWithError(e) } + } private fun onRotate(call: MethodCall, result: MethodChannel.Result) { val angleDegrees = call.arguments as Double - result.finishWithFuture { mapView.setViewpointRotationAsync(angleDegrees) } + lifecycle.coroutineScope.launch { + mapView.setViewpointRotation(angleDegrees).onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) + } + } } private fun onAddViewPadding(call: MethodCall, result: MethodChannel.Result) { @@ -468,10 +484,7 @@ internal class ArcgisMapView( // https://developers.arcgis.com/android/api-reference/reference/com/esri/arcgisruntime/mapping/view/MapView.html#setViewInsets(double,double,double,double) mapView.setViewInsets( - viewPadding.left, - viewPadding.top, - viewPadding.right, - viewPadding.bottom + viewPadding.left, viewPadding.top, viewPadding.right, viewPadding.bottom ) result.success(true) @@ -505,10 +518,12 @@ internal class ArcgisMapView( defaultGraphicsOverlay.graphics.addAll(newGraphic) - - when (val future = updateMap()) { - null -> result.success(false) - else -> result.finishWithFuture { future } + lifecycle.coroutineScope.launch { + updateMap().onSuccess { updateResult -> + result.success(updateResult) + }.onFailure { e -> + result.finishWithError(e) + } } } catch (e: Throwable) { result.finishWithError(e) @@ -516,22 +531,21 @@ internal class ArcgisMapView( } private fun onRemoveGraphic(call: MethodCall, result: MethodChannel.Result) { - try { - val graphicId = call.arguments as String + val graphicId = call.arguments as String + val graphicsToRemove = defaultGraphicsOverlay.graphics.filter { graphic -> + val id = graphic.attributes["id"] as? String + graphicId == id + } - val graphicsToRemove = defaultGraphicsOverlay.graphics.filter { graphic -> - val id = graphic.attributes["id"] as? String - graphicId == id + // Don't use removeAll because this will not trigger a redraw. + graphicsToRemove.forEach(defaultGraphicsOverlay.graphics::remove) + lifecycle.coroutineScope.launch { + updateMap().onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) } - - // Don't use removeAll because this will not trigger a redraw. - graphicsToRemove.forEach(defaultGraphicsOverlay.graphics::remove) - - updateMap() - result.success(true) - } catch (e: Throwable) { - result.finishWithError(e) } } @@ -544,25 +558,28 @@ internal class ArcgisMapView( val animationOptionMap = (arguments["animationOptions"] as Map?) - val animationOptions = - if (animationOptionMap.isNullOrEmpty()) null - else animationOptionMap.parseToClass() + val animationOptions = if (animationOptionMap.isNullOrEmpty()) null + else animationOptionMap.parseToClass() val scale = if (zoomLevel != null) { getMapScale(zoomLevel) - } else if (!mapView.mapScale.isNaN()) { - mapView.mapScale + } else if (!mapView.mapScale.value.isNaN()) { + mapView.mapScale.value } else { getMapScale(initialZoom) } val initialViewPort = Viewpoint(point.latitude, point.longitude, scale) - result.finishWithFuture { - mapView.setViewpointAsync( + lifecycle.coroutineScope.launch { + mapView.setViewpointAnimated( initialViewPort, (animationOptions?.duration?.toFloat() ?: 0F) / 1000, - animationOptions?.animationCurve ?: AnimationCurve.LINEAR, - ) + animationOptions?.animationCurve ?: AnimationCurve.Linear, + ).onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) + } } } catch (e: Throwable) { result.finishWithError(e) @@ -572,24 +589,31 @@ internal class ArcgisMapView( private fun onMoveCameraToPoints(call: MethodCall, result: MethodChannel.Result) { try { val arguments = call.arguments as Map - val latLongs = (arguments["points"] as ArrayList>) - .map { p -> parseToClass(p) } + val latLongs = (arguments["points"] as ArrayList>).map { p -> + parseToClass(p) + } val padding = arguments["padding"] as Double? val polyline = Polyline( - PointCollection(latLongs.map { latLng -> + latLongs.map { latLng -> Point( - latLng.longitude, - latLng.latitude + latLng.longitude, latLng.latitude ) - }), - SpatialReferences.getWgs84() + }, SpatialReference.wgs84() ) - result.finishWithFuture { - if (padding != null) mapView.setViewpointGeometryAsync(polyline.extent, padding) - else mapView.setViewpointGeometryAsync(polyline.extent) + lifecycle.coroutineScope.launch { + val viewpointResult = if (padding != null) { + mapView.setViewpointGeometry(polyline.extent, padding) + } else { + mapView.setViewpointGeometry(polyline.extent) + } + viewpointResult.onSuccess { + result.success(true) + }.onFailure { e -> + result.finishWithError(e) + } } } catch (e: Throwable) { result.finishWithError(e) @@ -599,10 +623,9 @@ internal class ArcgisMapView( private fun onToggleBaseMap(call: MethodCall, result: MethodChannel.Result) { try { val newStyle = gson.fromJson( - call.arguments as String, - object : TypeToken() {}.type + call.arguments as String, object : TypeToken() {}.type ) - map.basemap = Basemap(newStyle) + map.setBasemap(Basemap(newStyle)) result.success(true) } catch (e: Throwable) { result.finishWithError(e) @@ -610,26 +633,28 @@ internal class ArcgisMapView( } private fun onRetryLoad(result: MethodChannel.Result) { - try { - mapView.map?.retryLoadAsync() - result.success(true) - } catch (e: Exception) { - result.finishWithError(e) + lifecycle.coroutineScope.launch { + mapView.map?.retryLoad()?.onSuccess { + result.success(true) + }?.onFailure { e -> + result.finishWithError(e) + } } } private fun onExportImage(result: MethodChannel.Result) { - result.finishWithFuture( - mapResult = { bitmap -> + lifecycle.coroutineScope.launch { + mapView.exportImage().onSuccess { bitmapResult -> + val bitmap = bitmapResult.bitmap val stream = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) val byteArray = stream.toByteArray() bitmap.recycle() - byteArray - }, - getFuture = { mapView.exportImageAsync() } - ) - + result.success(byteArray) + }.onFailure { e -> + result.finishWithError(e) + } + } } /** @@ -637,7 +662,7 @@ internal class ArcgisMapView( * https://developers.arcgis.com/documentation/mapping-apis-and-services/reference/zoom-levels-and-scale/#conversion-tool * */ private fun getZoomLevel(mapView: MapView): Int { - val result = -1.443 * ln(mapView.mapScale) + 29.14 + val result = -1.443 * ln(mapView.mapScale.value) + 29.14 return result.roundToInt() } @@ -648,11 +673,11 @@ internal class ArcgisMapView( * The corresponding issue in the esri forum can be found here: * https://community.esri.com/t5/arcgis-runtime-sdk-for-android-questions/mapview-graphicsoverlays-add-does-not-update-the/m-p/1240825#M5931 */ - private fun updateMap(): ListenableFuture? { - if (mapView.mapScale.isNaN()) { - return null + private suspend fun updateMap(): Result { + if (mapView.mapScale.value.isNaN()) { + return Result.success(false) } - return mapView.setViewpointScaleAsync(mapView.mapScale) + return mapView.setViewpointScale(mapView.mapScale.value) } /** @@ -667,7 +692,7 @@ internal class ArcgisMapView( mapView.interactionOptions.apply { // don't set "isMagnifierEnabled" since we don't want to use this feature isPanEnabled = enabled - isFlickEnabled = enabled + isFlingEnabled = enabled isRotateEnabled = enabled isZoomEnabled = enabled isEnabled = enabled @@ -675,33 +700,6 @@ internal class ArcgisMapView( } // region helper methods - - /** - * Safely awaits the provide future and respond to the MethodChannel with the result - * or an error. - * - * @param mapResult optional transformation of the returned value of the future. If null will default to Boolean true. - * @param getFuture A callback that returns the future that will be awaited. This invocation is also caught. - */ - private fun MethodChannel.Result.finishWithFuture( - mapResult: (T) -> Any = { _ -> true }, - getFuture: () -> ListenableFuture - ) { - try { - val future = getFuture() - future.addDoneListener { - try { - val result = future.get() - success(mapResult(result)) - } catch (e: Throwable) { - finishWithError(e) - } - } - } catch (e: Throwable) { - finishWithError(e) - } - } - private fun MethodChannel.Result.finishWithError(e: Throwable, info: String? = null) { val msg = StringBuilder().apply { if (info != null) append(info) @@ -712,9 +710,7 @@ internal class ArcgisMapView( private fun finishOperationWithSymbol( - call: MethodCall, - result: MethodChannel.Result, - function: (Symbol) -> Unit + call: MethodCall, result: MethodChannel.Result, function: (Symbol) -> Unit ) { try { val map = call.arguments as Map @@ -725,22 +721,21 @@ internal class ArcgisMapView( result.finishWithError(e, info = "Error while adding graphic.") } } - // endregion } -private fun LoadStatusChangedEvent.jsonValue() = when (newLoadStatus) { - LOADED -> "loaded" - LOADING -> "loading" - FAILED_TO_LOAD -> "failedToLoad" - NOT_LOADED -> "notLoaded" +private fun LoadStatus.jsonValue() = when (this) { + LoadStatus.Loaded -> "loaded" + LoadStatus.Loading -> "loading" + is LoadStatus.FailedToLoad -> "failedToLoad" + LoadStatus.NotLoaded -> "notLoaded" else -> "unknown" } private fun String.autoPanModeFromString() = when (this) { - "compassNavigation" -> COMPASS_NAVIGATION - "navigation" -> NAVIGATION - "recenter" -> RECENTER - "off" -> OFF + "compassNavigation" -> LocationDisplayAutoPanMode.CompassNavigation + "navigation" -> LocationDisplayAutoPanMode.Navigation + "recenter" -> LocationDisplayAutoPanMode.Recenter + "off" -> LocationDisplayAutoPanMode.Off else -> null } diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapViewFactory.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapViewFactory.kt index 9dcccf68f..ff6d90a65 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapViewFactory.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ArcgisMapViewFactory.kt @@ -1,19 +1,33 @@ package dev.fluttercommunity.arcgis_map_sdk_android import android.content.Context +import android.content.ContextWrapper +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import dev.fluttercommunity.arcgis_map_sdk_android.model.ArcgisMapOptions import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformViewFactory -import dev.fluttercommunity.arcgis_map_sdk_android.ArcgisMapView -class ArcgisMapViewFactory(private val flutterPluginBinding: FlutterPluginBinding) : - PlatformViewFactory(StandardMessageCodec.INSTANCE) { +class ArcgisMapViewFactory( + private val flutterPluginBinding: FlutterPluginBinding, +) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { val optionParams = args as Map val params = optionParams.parseToClass() - return ArcgisMapView(context!!, viewId, params, flutterPluginBinding) + return ArcgisMapView( + context!!, viewId, params, flutterPluginBinding, getLifecycle(context)!! + ) + } + + private fun getLifecycle(context: Context): Lifecycle? { + var currentContext = context + while (currentContext is ContextWrapper) { + if (currentContext is LifecycleOwner) return currentContext.lifecycle + currentContext = currentContext.baseContext + } + return null } } diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/CustomLocationProvider.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/CustomLocationProvider.kt new file mode 100644 index 000000000..8b2860730 --- /dev/null +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/CustomLocationProvider.kt @@ -0,0 +1,33 @@ +package dev.fluttercommunity.arcgis_map_sdk_android + +import com.arcgismaps.location.CustomLocationDataSource +import com.arcgismaps.location.Location +import dev.fluttercommunity.arcgis_map_sdk_android.model.UserPosition +import dev.fluttercommunity.arcgis_map_sdk_android.model.toAGSPoint +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +class CustomLocationProvider : CustomLocationDataSource.LocationProvider { + private val _locations = MutableSharedFlow(replay = 1) + private val _headings = MutableSharedFlow(replay = 1) + + override val locations: Flow = _locations.asSharedFlow() + override val headings: Flow = _headings.asSharedFlow() + + suspend fun updateLocation(position: UserPosition) { + _locations.tryEmit( + Location.create( + position = position.latLng.toAGSPoint(), + horizontalAccuracy = position.accuracy ?: 0.0, + verticalAccuracy = position.accuracy ?: 0.0, + speed = position.velocity ?: 0.0, + course = position.heading ?: 0.0, + lastKnown = false, + ) + ) + position.heading?.let { + _headings.emit(it) + } + } +} \ No newline at end of file diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt deleted file mode 100644 index 293b24bea..000000000 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/ManualLocationDataSource.kt +++ /dev/null @@ -1,27 +0,0 @@ -package dev.fluttercommunity.arcgis_map_sdk_android - -import com.esri.arcgisruntime.location.LocationDataSource -import dev.fluttercommunity.arcgis_map_sdk_android.model.UserPosition -import dev.fluttercommunity.arcgis_map_sdk_android.model.toAGSPoint - -class ManualLocationDisplayDataSource : LocationDataSource() { - - override fun onStart() { - this.onStartCompleted(null) - } - - override fun onStop() { - - } - - fun setNewLocation(userPosition: UserPosition) { - val loc = Location( - userPosition.latLng.toAGSPoint(), - userPosition.accuracy ?: 0.0, - userPosition.velocity ?: 0.0, - userPosition.heading ?: 0.0, - false - ) - updateLocation(loc) - } -} diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/Parsing.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/Parsing.kt new file mode 100644 index 000000000..80181c741 --- /dev/null +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/Parsing.kt @@ -0,0 +1,267 @@ +package dev.fluttercommunity.arcgis_map_sdk_android + +import com.arcgismaps.mapping.BasemapStyle +import com.arcgismaps.mapping.symbology.SimpleLineSymbolMarkerPlacement +import com.arcgismaps.mapping.symbology.SimpleLineSymbolMarkerStyle +import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle +import com.arcgismaps.mapping.view.AnimationCurve +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.TypeAdapter +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter + +val gson: Gson by lazy { + GsonBuilder() + .registerTypeAdapter(BasemapStyleAdapter()) + .registerTypeAdapter(AnimationCurveAdapter()) + .registerTypeAdapter(MarkerPlacementAdapter()) + .registerTypeAdapter(MarkerStyleAdapter()) + .registerTypeAdapter(PolylineStyleAdapter()) + .create() +} + +private inline fun GsonBuilder.registerTypeAdapter(typeAdapter: TypeAdapter) = + registerTypeAdapter(T::class.java, typeAdapter) + +inline fun Map.parseToClass(): O = parseToClass(this) + +inline fun parseToClass(payload: Any): O { + val json = gson.toJson(payload) + return gson.fromJson(json, object : TypeToken() {}.type) +} + +fun toMap(data: Any): Map { + val jsonString = gson.toJson(data) + return gson.fromJson(jsonString, object : TypeToken>() {}.type) +} + +class AnimationCurveAdapter : TypeAdapter() { + override fun write(out: JsonWriter, value: AnimationCurve) { + out.value(value.getJsonValue()) + } + + override fun read(reader: JsonReader): AnimationCurve { + val jsonValue = reader.nextString() + return parseAnimationCurve(jsonValue) + } +} + +class BasemapStyleAdapter : TypeAdapter() { + override fun write(out: JsonWriter, value: BasemapStyle) { + out.value(value.getJsonValue()) + } + + override fun read(reader: JsonReader): BasemapStyle? { + val jsonValue = reader.nextString() + return parseBasemapStyle(jsonValue) + } +} + +class MarkerPlacementAdapter : TypeAdapter() { + override fun write(out: JsonWriter, value: SimpleLineSymbolMarkerPlacement) { + out.value(value.getJsonValue()) + } + + override fun read(reader: JsonReader): SimpleLineSymbolMarkerPlacement { + val jsonValue = reader.nextString() + return parseSimpleLineSymbolMarkerPlacement(jsonValue) + } +} + +fun SimpleLineSymbolMarkerPlacement.getJsonValue(): String { + return when (this) { + SimpleLineSymbolMarkerPlacement.Begin -> "begin" + SimpleLineSymbolMarkerPlacement.End -> "end" + SimpleLineSymbolMarkerPlacement.BeginAndEnd -> "beginEnd" + } +} + +fun parseSimpleLineSymbolMarkerPlacement(jsonValue: String): SimpleLineSymbolMarkerPlacement { + return when (jsonValue.lowercase()) { + "begin" -> SimpleLineSymbolMarkerPlacement.Begin + "end" -> SimpleLineSymbolMarkerPlacement.End + "beginEnd" -> SimpleLineSymbolMarkerPlacement.BeginAndEnd + else -> SimpleLineSymbolMarkerPlacement.Begin + } +} + +class MarkerStyleAdapter : TypeAdapter() { + override fun write(out: JsonWriter, value: SimpleLineSymbolMarkerStyle) { + out.value(value.getJsonValue()) + } + + override fun read(reader: JsonReader): SimpleLineSymbolMarkerStyle { + val jsonValue = reader.nextString() + return if (jsonValue == "arrow") { + SimpleLineSymbolMarkerStyle.Arrow + } else { + SimpleLineSymbolMarkerStyle.None + } + } +} + +fun SimpleLineSymbolMarkerStyle.getJsonValue(): String { + return when (this) { + SimpleLineSymbolMarkerStyle.None -> "none" + SimpleLineSymbolMarkerStyle.Arrow -> "arrow" + } +} + +class PolylineStyleAdapter : TypeAdapter() { + override fun write(out: JsonWriter, value: SimpleLineSymbolStyle) { + out.value(value.getJsonValue()) + } + + override fun read(reader: JsonReader): SimpleLineSymbolStyle { + val jsonValue = reader.nextString() + return parseSimpleLineSymbolStyle(jsonValue) + } +} + +fun SimpleLineSymbolStyle.getJsonValue(): String { + return when (this) { + SimpleLineSymbolStyle.Dash -> "dash" + SimpleLineSymbolStyle.DashDot -> "dashDot" + SimpleLineSymbolStyle.Dot -> "dot" + SimpleLineSymbolStyle.DashDotDot -> "dashDotDot" + SimpleLineSymbolStyle.LongDash -> "longDash" + SimpleLineSymbolStyle.LongDashDot -> "longDashDot" + SimpleLineSymbolStyle.Null -> "none" + SimpleLineSymbolStyle.ShortDash -> "shortDash" + SimpleLineSymbolStyle.ShortDashDot -> "shortDashDot" + SimpleLineSymbolStyle.ShortDashDotDot -> "shortDashDotDot" + SimpleLineSymbolStyle.ShortDot -> "shortDot" + SimpleLineSymbolStyle.Solid -> "solid" + } +} + +fun parseSimpleLineSymbolStyle(value: String): SimpleLineSymbolStyle { + return when (value) { + "dash" -> SimpleLineSymbolStyle.Dash + "dashDot" -> SimpleLineSymbolStyle.DashDot + "dot" -> SimpleLineSymbolStyle.Dot + "dashDotDot" -> SimpleLineSymbolStyle.DashDotDot + "longDash" -> SimpleLineSymbolStyle.LongDash + "longDashDot" -> SimpleLineSymbolStyle.LongDashDot + "none" -> SimpleLineSymbolStyle.Null + "shortDash" -> SimpleLineSymbolStyle.ShortDash + "shortDashDot" -> SimpleLineSymbolStyle.ShortDashDot + "shortDashDotDot" -> SimpleLineSymbolStyle.ShortDashDotDot + "shortDot" -> SimpleLineSymbolStyle.ShortDot + "solid" -> SimpleLineSymbolStyle.Solid + else -> SimpleLineSymbolStyle.Dash + } +} + + +fun AnimationCurve.getJsonValue(): String { + return when (this) { + AnimationCurve.Linear -> "linear" + AnimationCurve.EaseInExpo -> "easy" + AnimationCurve.EaseInCirc -> "easeIn" + AnimationCurve.EaseOutCirc -> "easeOut" + AnimationCurve.EaseInOutCirc -> "easeInOut" + else -> "linear" + } +} + +fun parseAnimationCurve(value: String): AnimationCurve { + return when (value.lowercase()) { + "linear" -> AnimationCurve.Linear + "easy" -> AnimationCurve.EaseInExpo + "easein" -> AnimationCurve.EaseInCirc + "easeout" -> AnimationCurve.EaseOutCirc + "easeinout" -> AnimationCurve.EaseInOutCirc + else -> AnimationCurve.Linear + } +} + +fun BasemapStyle.getJsonValue(): String? { + return when (this) { + BasemapStyle.ArcGISImagery -> "arcgisImagery" + BasemapStyle.ArcGISImageryStandard -> "arcgisImageryStandard" + BasemapStyle.ArcGISImageryLabels -> "arcgisImageryLabels" + BasemapStyle.ArcGISLightGray -> "arcgisLightGray" + BasemapStyle.ArcGISLightGray -> null + BasemapStyle.ArcGISLightGrayLabels -> null + BasemapStyle.ArcGISDarkGray -> "arcgisDarkGray" + BasemapStyle.ArcGISDarkGrayBase -> null + BasemapStyle.ArcGISDarkGrayLabels -> null + BasemapStyle.ArcGISNavigation -> "arcgisNavigation" + BasemapStyle.ArcGISNavigationNight -> "arcgisNavigationNight" + BasemapStyle.ArcGISStreets -> "arcgisStreets" + BasemapStyle.ArcGISStreetsNight -> "arcgisStreetsNight" + BasemapStyle.OsmStreetsRelief -> "arcgisStreetsRelief" + BasemapStyle.ArcGISTopographic -> "arcgisTopographic" + BasemapStyle.ArcGISOceans -> "arcgisOceans" + BasemapStyle.ArcGISOceansBase -> null + BasemapStyle.ArcGISOceansLabels -> null + BasemapStyle.ArcGISTerrain -> "arcgisTerrain" + BasemapStyle.ArcGISTerrainBase -> null + BasemapStyle.ArcGISTerrainDetail -> null + BasemapStyle.ArcGISCommunity -> "arcgisCommunity" + BasemapStyle.ArcGISChartedTerritory -> "arcgisChartedTerritory" + BasemapStyle.ArcGISColoredPencil -> "arcgisColoredPencil" + BasemapStyle.ArcGISNova -> "arcgisNova" + BasemapStyle.ArcGISModernAntique -> "arcgisModernAntique" + BasemapStyle.ArcGISMidcentury -> "arcgisMidcentury" + BasemapStyle.ArcGISNewspaper -> "arcgisNewspaper" + BasemapStyle.ArcGISHillshadeLight -> "arcgisHillshadeLight" + BasemapStyle.ArcGISHillshadeDark -> "arcgisHillshadeDark" + BasemapStyle.ArcGISStreetsReliefBase -> null + BasemapStyle.ArcGISTopographicBase -> null + BasemapStyle.ArcGISChartedTerritoryBase -> null + BasemapStyle.ArcGISModernAntiqueBase -> null + BasemapStyle.OsmStandard -> "osmStandard" + BasemapStyle.OsmStandardRelief -> "osmStandardRelief" + BasemapStyle.OsmStandardReliefBase -> null + BasemapStyle.OsmStreets -> "osmStreets" + BasemapStyle.OsmStreetsRelief -> "osmStreetsRelief" + BasemapStyle.OsmStreetsReliefBase -> null + BasemapStyle.OsmLightGray -> "osmLightGray" + BasemapStyle.OsmLightGrayBase -> null + BasemapStyle.OsmLightGrayLabels -> null + BasemapStyle.OsmDarkGray -> "osmDarkGray" + BasemapStyle.OsmDarkGrayBase -> null + BasemapStyle.OsmDarkGrayLabels -> null + BasemapStyle.OsmHybrid -> "hybrid" + else -> null + } +} + +fun parseBasemapStyle(value: String): BasemapStyle? { + return when (value) { + "arcgisImagery" -> BasemapStyle.ArcGISImagery + "arcgisImageryStandard" -> BasemapStyle.ArcGISImageryStandard + "arcgisImageryLabels" -> BasemapStyle.ArcGISImageryLabels + "arcgisLightGray" -> BasemapStyle.ArcGISLightGray + "arcgisDarkGray" -> BasemapStyle.ArcGISDarkGray + "arcgisNavigation" -> BasemapStyle.ArcGISNavigation + "arcgisNavigationNight" -> BasemapStyle.ArcGISNavigationNight + "arcgisStreets" -> BasemapStyle.ArcGISStreets + "arcgisStreetsNight" -> BasemapStyle.ArcGISStreetsNight + "arcgisStreetsRelief" -> BasemapStyle.OsmStreetsRelief + "arcgisTopographic" -> BasemapStyle.ArcGISTopographic + "arcgisOceans" -> BasemapStyle.ArcGISOceans + "arcgisTerrain" -> BasemapStyle.ArcGISTerrain + "arcgisCommunity" -> BasemapStyle.ArcGISCommunity + "arcgisChartedTerritory" -> BasemapStyle.ArcGISChartedTerritory + "arcgisColoredPencil" -> BasemapStyle.ArcGISColoredPencil + "arcgisNova" -> BasemapStyle.ArcGISNova + "arcgisModernAntique" -> BasemapStyle.ArcGISModernAntique + "arcgisMidcentury" -> BasemapStyle.ArcGISMidcentury + "arcgisNewspaper" -> BasemapStyle.ArcGISNewspaper + "arcgisHillshadeLight" -> BasemapStyle.ArcGISHillshadeLight + "arcgisHillshadeDark" -> BasemapStyle.ArcGISHillshadeDark + "osmStandard" -> BasemapStyle.OsmStandard + "osmStandardRelief" -> BasemapStyle.OsmStandardRelief + "osmStreets" -> BasemapStyle.OsmStreets + "osmStreetsRelief" -> BasemapStyle.OsmStreetsRelief + "osmLightGray" -> BasemapStyle.OsmLightGray + "osmDarkGray" -> BasemapStyle.OsmDarkGray + "hybrid" -> BasemapStyle.OsmHybrid + else -> null + } +} diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/AnimationOptions.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/AnimationOptions.kt index 713f8ead7..8ecfcf8ab 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/AnimationOptions.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/AnimationOptions.kt @@ -1,6 +1,6 @@ package dev.fluttercommunity.arcgis_map_sdk_android.model -import com.esri.arcgisruntime.mapping.view.AnimationCurve +import com.arcgismaps.mapping.view.AnimationCurve data class AnimationOptions( val duration: Double, diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ArcgisMapOptions.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ArcgisMapOptions.kt index 546b1a41c..aba296fe5 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ArcgisMapOptions.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/ArcgisMapOptions.kt @@ -1,6 +1,6 @@ package dev.fluttercommunity.arcgis_map_sdk_android.model -import com.esri.arcgisruntime.mapping.BasemapStyle +import com.arcgismaps.mapping.BasemapStyle data class ArcgisMapOptions( val apiKey: String?, diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/LatLng.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/LatLng.kt index 4c401c002..6fc7315e6 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/LatLng.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/LatLng.kt @@ -1,11 +1,11 @@ package dev.fluttercommunity.arcgis_map_sdk_android.model -import com.esri.arcgisruntime.geometry.Point -import com.esri.arcgisruntime.geometry.SpatialReferences +import com.arcgismaps.geometry.Point +import com.arcgismaps.geometry.SpatialReference data class LatLng( val longitude: Double, val latitude: Double, ) -fun LatLng.toAGSPoint() = Point(longitude, latitude, SpatialReferences.getWgs84()) +fun LatLng.toAGSPoint() = Point(longitude, latitude, SpatialReference.wgs84()) diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/symbol/SimpleLineSymbolPayload.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/symbol/SimpleLineSymbolPayload.kt index df8ec2973..a4fc702d3 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/symbol/SimpleLineSymbolPayload.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/model/symbol/SimpleLineSymbolPayload.kt @@ -1,18 +1,20 @@ package dev.fluttercommunity.arcgis_map_sdk_android.model.symbol -import com.esri.arcgisruntime.symbology.SimpleLineSymbol +import com.arcgismaps.mapping.symbology.SimpleLineSymbolMarkerPlacement +import com.arcgismaps.mapping.symbology.SimpleLineSymbolMarkerStyle +import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle import dev.fluttercommunity.arcgis_map_sdk_android.model.MapColor data class SimpleLineSymbolPayload( val color: MapColor?, val marker: LineSymbolMarker?, val miterLimit: Double?, - val style: SimpleLineSymbol.Style, + val style: SimpleLineSymbolStyle, val width: Double, ) data class LineSymbolMarker( val color: MapColor, - val placement: SimpleLineSymbol.MarkerPlacement, - val style: SimpleLineSymbol.MarkerStyle, + val placement: SimpleLineSymbolMarkerPlacement, + val style: SimpleLineSymbolMarkerStyle, ) diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/parsing.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/parsing.kt deleted file mode 100644 index e2554aef1..000000000 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/parsing.kt +++ /dev/null @@ -1,188 +0,0 @@ -package dev.fluttercommunity.arcgis_map_sdk_android - -import com.esri.arcgisruntime.mapping.BasemapStyle -import com.esri.arcgisruntime.mapping.view.AnimationCurve -import com.esri.arcgisruntime.symbology.SimpleLineSymbol -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.TypeAdapter -import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter - -val gson: Gson by lazy { - GsonBuilder() - .registerTypeAdapter(BasemapStyleAdapter()) - .registerTypeAdapter(AnimationCurveAdapter()) - .registerTypeAdapter(MarkerPlacementAdapter()) - .registerTypeAdapter(MarkerStyleAdapter()) - .registerTypeAdapter(PolylineStyleAdapter()) - .create() -} - -private inline fun GsonBuilder.registerTypeAdapter(typeAdapter: TypeAdapter) = - registerTypeAdapter(T::class.java, typeAdapter) - -inline fun Map.parseToClass(): O = parseToClass(this) - -inline fun parseToClass(payload: Any): O { - val json = gson.toJson(payload) - return gson.fromJson(json, object : TypeToken() {}.type) -} - -fun toMap(data: Any): Map { - val jsonString = gson.toJson(data) - return gson.fromJson(jsonString, object : TypeToken>() {}.type) -} - -class AnimationCurveAdapter : TypeAdapter() { - override fun write(out: JsonWriter, value: AnimationCurve) { - out.value(value.getJsonValue()) - } - - override fun read(reader: JsonReader): AnimationCurve { - val jsonValue = reader.nextString() - return AnimationCurve.values().first { it.getJsonValue() == jsonValue } - } -} - -class BasemapStyleAdapter : TypeAdapter() { - override fun write(out: JsonWriter, value: BasemapStyle) { - out.value(value.getJsonValue()) - } - - override fun read(reader: JsonReader): BasemapStyle { - val jsonValue = reader.nextString() - return BasemapStyle.values().first { it.getJsonValue() == jsonValue } - } -} - -class MarkerPlacementAdapter : TypeAdapter() { - override fun write(out: JsonWriter, value: SimpleLineSymbol.MarkerPlacement) { - out.value(value.getJsonValue()) - } - - override fun read(reader: JsonReader): SimpleLineSymbol.MarkerPlacement { - val jsonValue = reader.nextString() - return SimpleLineSymbol.MarkerPlacement.values().first { it.getJsonValue() == jsonValue } - } -} - -fun SimpleLineSymbol.MarkerPlacement.getJsonValue(): String { - return when (this) { - SimpleLineSymbol.MarkerPlacement.BEGIN -> "begin" - SimpleLineSymbol.MarkerPlacement.END -> "end" - SimpleLineSymbol.MarkerPlacement.BEGIN_AND_END -> "beginEnd" - } -} - -class MarkerStyleAdapter : TypeAdapter() { - override fun write(out: JsonWriter, value: SimpleLineSymbol.MarkerStyle) { - out.value(value.getJsonValue()) - } - - override fun read(reader: JsonReader): SimpleLineSymbol.MarkerStyle { - val jsonValue = reader.nextString() - return SimpleLineSymbol.MarkerStyle.values().firstOrNull { it.getJsonValue() == jsonValue } - ?: SimpleLineSymbol.MarkerStyle.NONE - } -} - -fun SimpleLineSymbol.MarkerStyle.getJsonValue(): String { - return when (this) { - SimpleLineSymbol.MarkerStyle.NONE -> "none" - SimpleLineSymbol.MarkerStyle.ARROW -> "arrow" - } -} - -class PolylineStyleAdapter : TypeAdapter() { - override fun write(out: JsonWriter, value: SimpleLineSymbol.Style) { - out.value(value.getJsonValue()) - } - - override fun read(reader: JsonReader): SimpleLineSymbol.Style { - val jsonValue = reader.nextString() - return SimpleLineSymbol.Style.values().firstOrNull { it.getJsonValue() == jsonValue } - ?: SimpleLineSymbol.Style.DASH - } -} - -fun SimpleLineSymbol.Style.getJsonValue(): String { - return when (this) { - SimpleLineSymbol.Style.DASH -> "dash" - SimpleLineSymbol.Style.DASH_DOT -> "dashDot" - SimpleLineSymbol.Style.DOT -> "dot" - SimpleLineSymbol.Style.DASH_DOT_DOT -> "dashDotDot" - SimpleLineSymbol.Style.LONG_DASH -> "longDash" - SimpleLineSymbol.Style.LONG_DASH_DOT -> "longDashDot" - SimpleLineSymbol.Style.NULL -> "none" - SimpleLineSymbol.Style.SHORT_DASH -> "shortDash" - SimpleLineSymbol.Style.SHORT_DASH_DOT -> "shortDashDot" - SimpleLineSymbol.Style.SHORT_DASH_DOT_DOT -> "shortDashDotDot" - SimpleLineSymbol.Style.SHORT_DOT -> "shortDot" - SimpleLineSymbol.Style.SOLID -> "solid" - } -} - - -fun AnimationCurve.getJsonValue(): String { - return when (this) { - AnimationCurve.LINEAR -> "linear" - AnimationCurve.EASE_IN_EXPO -> "easy" - AnimationCurve.EASE_IN_CIRC -> "easeIn" - AnimationCurve.EASE_OUT_CIRC -> "easeOut" - AnimationCurve.EASE_IN_OUT_CIRC -> "easeInOut" - else -> "linear" - } -} - -fun BasemapStyle.getJsonValue(): String? { - return when (this) { - BasemapStyle.ARCGIS_IMAGERY -> "arcgisImagery" - BasemapStyle.ARCGIS_IMAGERY_STANDARD -> "arcgisImageryStandard" - BasemapStyle.ARCGIS_IMAGERY_LABELS -> "arcgisImageryLabels" - BasemapStyle.ARCGIS_LIGHT_GRAY -> "arcgisLightGray" - BasemapStyle.ARCGIS_LIGHT_GRAY_BASE -> null - BasemapStyle.ARCGIS_LIGHT_GRAY_LABELS -> null - BasemapStyle.ARCGIS_DARK_GRAY -> "arcgisDarkGray" - BasemapStyle.ARCGIS_DARK_GRAY_BASE -> null - BasemapStyle.ARCGIS_DARK_GRAY_LABELS -> null - BasemapStyle.ARCGIS_NAVIGATION -> "arcgisNavigation" - BasemapStyle.ARCGIS_NAVIGATION_NIGHT -> "arcgisNavigationNight" - BasemapStyle.ARCGIS_STREETS -> "arcgisStreets" - BasemapStyle.ARCGIS_STREETS_NIGHT -> "arcgisStreetsNight" - BasemapStyle.ARCGIS_STREETS_RELIEF -> "arcgisStreetsRelief" - BasemapStyle.ARCGIS_TOPOGRAPHIC -> "arcgisTopographic" - BasemapStyle.ARCGIS_OCEANS -> "arcgisOceans" - BasemapStyle.ARCGIS_OCEANS_BASE -> null - BasemapStyle.ARCGIS_OCEANS_LABELS -> null - BasemapStyle.ARCGIS_TERRAIN -> "arcgisTerrain" - BasemapStyle.ARCGIS_TERRAIN_BASE -> null - BasemapStyle.ARCGIS_TERRAIN_DETAIL -> null - BasemapStyle.ARCGIS_COMMUNITY -> "arcgisCommunity" - BasemapStyle.ARCGIS_CHARTED_TERRITORY -> "arcgisChartedTerritory" - BasemapStyle.ARCGIS_COLORED_PENCIL -> "arcgisColoredPencil" - BasemapStyle.ARCGIS_NOVA -> "arcgisNova" - BasemapStyle.ARCGIS_MODERN_ANTIQUE -> "arcgisModernAntique" - BasemapStyle.ARCGIS_MIDCENTURY -> "arcgisMidcentury" - BasemapStyle.ARCGIS_NEWSPAPER -> "arcgisNewspaper" - BasemapStyle.ARCGIS_HILLSHADE_LIGHT -> "arcgisHillshadeLight" - BasemapStyle.ARCGIS_HILLSHADE_DARK -> "arcgisHillshadeDark" - BasemapStyle.ARCGIS_STREETS_RELIEF_BASE -> null - BasemapStyle.ARCGIS_TOPOGRAPHIC_BASE -> null - BasemapStyle.ARCGIS_CHARTED_TERRITORY_BASE -> null - BasemapStyle.ARCGIS_MODERN_ANTIQUE_BASE -> null - BasemapStyle.OSM_STANDARD -> "osmStandard" - BasemapStyle.OSM_STANDARD_RELIEF -> "osmStandardRelief" - BasemapStyle.OSM_STANDARD_RELIEF_BASE -> null - BasemapStyle.OSM_STREETS -> "osmStreets" - BasemapStyle.OSM_STREETS_RELIEF -> "osmStreetsRelief" - BasemapStyle.OSM_LIGHT_GRAY -> "osmLightGray" - BasemapStyle.OSM_LIGHT_GRAY_BASE -> null - BasemapStyle.OSM_LIGHT_GRAY_LABELS -> null - BasemapStyle.OSM_DARK_GRAY -> "osmDarkGray" - BasemapStyle.OSM_DARK_GRAY_BASE -> null - BasemapStyle.OSM_DARK_GRAY_LABELS -> null - BasemapStyle.OSM_STREETS_RELIEF_BASE -> null - } -} diff --git a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt index b6a66e9b1..4deecbaa1 100644 --- a/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt +++ b/arcgis_map_sdk_android/android/src/main/kotlin/dev/fluttercommunity/arcgis_map_sdk_android/util/GraphicsParser.kt @@ -1,17 +1,18 @@ package dev.fluttercommunity.arcgis_map_sdk_android.util import android.graphics.drawable.BitmapDrawable -import com.esri.arcgisruntime.geometry.Point -import com.esri.arcgisruntime.geometry.PointCollection -import com.esri.arcgisruntime.geometry.Polygon -import com.esri.arcgisruntime.geometry.Polyline -import com.esri.arcgisruntime.geometry.SpatialReferences -import com.esri.arcgisruntime.mapping.view.Graphic -import com.esri.arcgisruntime.symbology.PictureMarkerSymbol -import com.esri.arcgisruntime.symbology.SimpleFillSymbol -import com.esri.arcgisruntime.symbology.SimpleLineSymbol -import com.esri.arcgisruntime.symbology.SimpleMarkerSymbol -import com.esri.arcgisruntime.symbology.Symbol +import com.arcgismaps.Color +import com.arcgismaps.geometry.Point +import com.arcgismaps.geometry.Polygon +import com.arcgismaps.geometry.Polyline +import com.arcgismaps.geometry.SpatialReference +import com.arcgismaps.mapping.symbology.PictureMarkerSymbol +import com.arcgismaps.mapping.symbology.SimpleFillSymbol +import com.arcgismaps.mapping.symbology.SimpleLineSymbol +import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle +import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol +import com.arcgismaps.mapping.symbology.Symbol +import com.arcgismaps.mapping.view.Graphic import dev.fluttercommunity.arcgis_map_sdk_android.model.LatLng import dev.fluttercommunity.arcgis_map_sdk_android.model.symbol.PictureMarkerSymbolPayload import dev.fluttercommunity.arcgis_map_sdk_android.model.symbol.SimpleFillSymbolPayload @@ -65,7 +66,7 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { return points.map { subPoints -> Graphic().apply { - geometry = Polyline(PointCollection(subPoints.map { coordinateArray -> + geometry = Polyline(subPoints.map { coordinateArray -> val x = coordinateArray.elementAtOrNull(0) val y = coordinateArray.elementAtOrNull(1) val z = coordinateArray.elementAtOrNull(2) @@ -73,9 +74,9 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { throw Exception("Coordinate array needs at least 2 doubles. Got $coordinateArray") } - if (z != null) Point(x, y, z, SpatialReferences.getWgs84()) - else Point(x, y, SpatialReferences.getWgs84()) - })) + if (z != null) Point(x, y, z, SpatialReference.wgs84()) + else Point(x, y, SpatialReference.wgs84()) + }) symbol = parseSymbol(symbolMap) } } @@ -87,8 +88,7 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { return rings.map { ring -> Graphic().apply { - geometry = - Polygon(PointCollection(ring.map { LatLng(it[0], it[1]).toAGSPoint() })) + geometry = Polygon(ring.map { LatLng(it[0], it[1]).toAGSPoint() }) symbol = parseSymbol(symbolMap) } } @@ -110,11 +110,11 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { val payload = map.parseToClass() return SimpleMarkerSymbol().apply { - color = payload.color.toHexInt() + color = Color(payload.color.toHexInt()) size = payload.size.toFloat() outline = SimpleLineSymbol().apply { - style = SimpleLineSymbol.Style.SOLID - color = payload.outlineColor.toHexInt() + style = SimpleLineSymbolStyle.Solid + color = Color(payload.outlineColor.toHexInt()) width = payload.outlineWidth.toFloat() } } @@ -125,12 +125,13 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { // return local asset in case its a local path if (!payload.assetUri.isWebUrl()) { - return PictureMarkerSymbol(getBitmapFromAssetPath(payload.assetUri)).apply { - width = payload.width.toFloat() - height = payload.height.toFloat() - offsetX = payload.xOffset.toFloat() - offsetY = payload.yOffset.toFloat() - } + return PictureMarkerSymbol.createWithImage(getBitmapFromAssetPath(payload.assetUri)!!) + .apply { + width = payload.width.toFloat() + height = payload.height.toFloat() + offsetX = payload.xOffset.toFloat() + offsetY = payload.yOffset.toFloat() + } } return PictureMarkerSymbol(payload.assetUri).apply { @@ -142,9 +143,7 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { } private fun getBitmapFromAssetPath(asset: String): BitmapDrawable? { - val assetPath: String = binding - .flutterAssets - .getAssetFilePathBySubpath(asset) + val assetPath: String = binding.flutterAssets.getAssetFilePathBySubpath(asset) var inputStream: InputStream? = null val drawable: BitmapDrawable? @@ -161,10 +160,10 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { val payload = map.parseToClass() return SimpleFillSymbol().apply { - color = payload.fillColor.toHexInt() + color = Color(payload.fillColor.toHexInt()) outline = SimpleLineSymbol().apply { - style = SimpleLineSymbol.Style.SOLID - color = payload.outlineColor.toHexInt() + style = SimpleLineSymbolStyle.Solid + color = Color(payload.outlineColor.toHexInt()) width = payload.outlineWidth.toFloat() } } @@ -174,7 +173,7 @@ class GraphicsParser(private val binding: FlutterPluginBinding) { val payload = map.parseToClass() return SimpleLineSymbol().apply { - if (payload.color != null) color = payload.color.toHexInt() + if (payload.color != null) color = Color(payload.color.toHexInt()) payload.marker?.let { markerStyle = it.style markerPlacement = it.placement diff --git a/arcgis_map_sdk_android/android/src/main/res/layout/vector_map_view.xml b/arcgis_map_sdk_android/android/src/main/res/layout/vector_map_view.xml index b6c326981..486ca1bf2 100644 --- a/arcgis_map_sdk_android/android/src/main/res/layout/vector_map_view.xml +++ b/arcgis_map_sdk_android/android/src/main/res/layout/vector_map_view.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" tools:context=".MainActivity"> -