diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f8c4362f5..d7036f8fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Mapbox welcomes participation and contributions from everyone. +## Features ✨ and improvements 🏁 +* Add accuracy radius support for LocationComponent. ([#1016](https://github.com/mapbox/mapbox-maps-android/pull/1016)) + # 10.4.0-beta.1 ## Features ✨ and improvements 🏁 diff --git a/app/src/androidTest/java/com/mapbox/maps/testapp/locationcomponent/generated/LocationComponentAttributeParser2DefaultValueTest.kt b/app/src/androidTest/java/com/mapbox/maps/testapp/locationcomponent/generated/LocationComponentAttributeParser2DefaultValueTest.kt new file mode 100644 index 0000000000..f08d4a4283 --- /dev/null +++ b/app/src/androidTest/java/com/mapbox/maps/testapp/locationcomponent/generated/LocationComponentAttributeParser2DefaultValueTest.kt @@ -0,0 +1,42 @@ +// This file is generated. + +package com.mapbox.maps.testapp.locationcomponent.generated + +import android.graphics.Color +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.mapbox.maps.plugin.locationcomponent.location2 +import com.mapbox.maps.testapp.BaseMapTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +@LargeTest +class LocationComponentAttributeParser2DefaultValueTest : BaseMapTest() { + @Test + fun testAttributeParser() { + assertEquals( + "showAccuracyRing test failed..", + false, + mapView.location2.getSettings2().showAccuracyRing + ) + assertEquals( + "accuracyRingColor test failed..", + Color.parseColor("#4d89cff0"), + mapView.location2.getSettings2().accuracyRingColor + ) + assertEquals( + "accuracyRingBorderColor test failed..", + Color.parseColor("#4d89cff0"), + mapView.location2.getSettings2().accuracyRingBorderColor + ) + } +} + +// End of generated file. \ No newline at end of file diff --git a/app/src/androidTest/java/com/mapbox/maps/testapp/locationcomponent/generated/LocationComponentAttributeParser2Test.kt b/app/src/androidTest/java/com/mapbox/maps/testapp/locationcomponent/generated/LocationComponentAttributeParser2Test.kt new file mode 100644 index 0000000000..bc0e5d53ea --- /dev/null +++ b/app/src/androidTest/java/com/mapbox/maps/testapp/locationcomponent/generated/LocationComponentAttributeParser2Test.kt @@ -0,0 +1,52 @@ +// This file is generated. + +package com.mapbox.maps.testapp.locationcomponent.generated + +import android.graphics.Color +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.mapbox.maps.R +import com.mapbox.maps.plugin.locationcomponent.location2 +import com.mapbox.maps.testapp.BaseMapTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +@LargeTest +class LocationComponentAttributeParser2Test : BaseMapTest() { + override fun initialiseMapView() { + rule.scenario.onActivity { + it.runOnUiThread { + it.setContentView(com.mapbox.maps.testapp.R.layout.generated_test_locationcomponent) + mapView = it.findViewById(R.id.mapView) + } + } + } + + @Test + fun testAttributeParser() { + assertEquals( + "showAccuracyRing test failed..", + false, + mapView.location2.getSettings2().showAccuracyRing + ) + assertEquals( + "accuracyRingColor test failed..", + Color.BLACK, + mapView.location2.getSettings2().accuracyRingColor + ) + assertEquals( + "accuracyRingBorderColor test failed..", + Color.BLACK, + mapView.location2.getSettings2().accuracyRingBorderColor + ) + } +} + +// End of generated file. \ No newline at end of file diff --git a/app/src/main/java/com/mapbox/maps/testapp/examples/JavaInterfaceChecker.java b/app/src/main/java/com/mapbox/maps/testapp/examples/JavaInterfaceChecker.java index fa62e552e7..2b46fb1e99 100644 --- a/app/src/main/java/com/mapbox/maps/testapp/examples/JavaInterfaceChecker.java +++ b/app/src/main/java/com/mapbox/maps/testapp/examples/JavaInterfaceChecker.java @@ -83,6 +83,7 @@ import com.mapbox.maps.plugin.locationcomponent.LocationComponentPlugin; import com.mapbox.maps.plugin.locationcomponent.LocationComponentUtils; import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings; +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings2; import com.mapbox.maps.plugin.logo.LogoPlugin; import com.mapbox.maps.plugin.logo.LogoUtils; import com.mapbox.maps.plugin.logo.generated.LogoSettings; @@ -157,6 +158,13 @@ private void locationComponentSettings(LocationPuck locationPuck) { locationComponentSettings = new LocationComponentSettings(true, true, Color.BLACK, 1f, "id", "id", locationPuck); } + private void locationComponentSettings2() { + LocationComponentSettings2 locationComponentSettings2 = new LocationComponentSettings2(); + locationComponentSettings2 = new LocationComponentSettings2(true); + locationComponentSettings2 = new LocationComponentSettings2(true, Color.BLUE); + locationComponentSettings2 = new LocationComponentSettings2(true, Color.BLUE, Color.RED); + } + private void locationComponent(Context context, MapView mapView) { LocationComponentPlugin locationComponent = LocationComponentUtils.getLocationComponent(mapView); locationComponent.setLocationPuck(LocationComponentUtils.createDefault2DPuck(locationComponent, context)); diff --git a/app/src/main/java/com/mapbox/maps/testapp/examples/LocationComponentActivity.kt b/app/src/main/java/com/mapbox/maps/testapp/examples/LocationComponentActivity.kt index be6b4ac328..4f250cfd2d 100644 --- a/app/src/main/java/com/mapbox/maps/testapp/examples/LocationComponentActivity.kt +++ b/app/src/main/java/com/mapbox/maps/testapp/examples/LocationComponentActivity.kt @@ -100,6 +100,16 @@ class LocationComponentActivity : AppCompatActivity() { } return true } + R.id.action_accuracy_enabled -> { + binding.mapView.location2.updateSettings2 { showAccuracyRing = true } + item.isChecked = true + return true + } + R.id.action_accuracy_disable -> { + binding.mapView.location2.updateSettings2 { showAccuracyRing = false } + item.isChecked = true + return true + } else -> return super.onOptionsItemSelected(item) } } diff --git a/app/src/main/res/layout/generated_test_locationcomponent.xml b/app/src/main/res/layout/generated_test_locationcomponent.xml index 01b1d953b5..4be18189bc 100644 --- a/app/src/main/res/layout/generated_test_locationcomponent.xml +++ b/app/src/main/res/layout/generated_test_locationcomponent.xml @@ -7,6 +7,9 @@ mapbox:mapbox_locationComponentPulsingEnabled = "false" mapbox:mapbox_locationComponentPulsingColor = "#000000" mapbox:mapbox_locationComponentPulsingMaxRadius = "10dp" + mapbox:mapbox_locationComponentShowAccuracyRing = "false" + mapbox:mapbox_locationComponentAccuracyRingColor = "#000000" + mapbox:mapbox_locationComponentAccuracyRingBorderColor = "#000000" mapbox:mapbox_locationComponentLayerAbove = "testString" mapbox:mapbox_locationComponentLayerBelow = "testString" android:layout_width="match_parent" diff --git a/app/src/main/res/menu/menu_location_component.xml b/app/src/main/res/menu/menu_location_component.xml index 13489d359a..666e45bcb6 100644 --- a/app/src/main/res/menu/menu_location_component.xml +++ b/app/src/main/res/menu/menu_location_component.xml @@ -23,4 +23,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 595b452a3d..e6bbaa955f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,8 @@ Hide bearing image Stop pulsing Start pulsing + Disable Accuracy Ring + Enable Accuracy Ring Restrict San Francisco diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentExt.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentExt.kt index 3e32924048..16025b26c3 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentExt.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentExt.kt @@ -16,6 +16,13 @@ val MapPluginProviderDelegate.location: LocationComponentPlugin @JvmName("getLocationComponent") get() = this.getPlugin(Plugin.MAPBOX_LOCATION_COMPONENT_PLUGIN_ID)!! +/** + * Extension val to get the LocationComponentPlugin instance with interface LocationComponentPlugin2 to handle accuracy ring. + */ +val MapPluginProviderDelegate.location2: LocationComponentPlugin2 + @JvmName("getLocationComponent2") + get() = this.getPlugin(Plugin.MAPBOX_LOCATION_COMPONENT_PLUGIN_ID)!! + private fun Context.getCompatDrawable(@DrawableRes resId: Int) = ResourcesCompat.getDrawable( this.resources, resId, diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImpl.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImpl.kt index 36af677166..98bb4de3cc 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImpl.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImpl.kt @@ -14,18 +14,17 @@ import com.mapbox.maps.plugin.delegates.MapDelegateProvider import com.mapbox.maps.plugin.locationcomponent.LocationComponentConstants.LOCATION_INDICATOR_LAYER import com.mapbox.maps.plugin.locationcomponent.LocationComponentConstants.MODEL_LAYER import com.mapbox.maps.plugin.locationcomponent.animators.PuckAnimatorManager +import com.mapbox.maps.plugin.locationcomponent.generated.* import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentAttributeParser -import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings -import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettingsBase import java.lang.ref.WeakReference -import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CopyOnWriteArraySet /** * Default implementation of the LocationComponentPlugin, it renders the configured location puck * to the user's current location. */ -class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, - LocationComponentSettingsBase() { +class LocationComponentPluginImpl : LocationComponentPlugin2, LocationConsumer2, + LocationComponentSettingsBase2() { private lateinit var delegateProvider: MapDelegateProvider private lateinit var context: WeakReference @@ -39,11 +38,14 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, @VisibleForTesting(otherwise = PRIVATE) internal var isLocationComponentActivated = false - private val onIndicatorPositionChangedListener = - CopyOnWriteArrayList() + private val onIndicatorPositionChangedListeners = + CopyOnWriteArraySet() - private val onIndicatorBearingChangedListener = - CopyOnWriteArrayList() + private val onIndicatorBearingChangedListeners = + CopyOnWriteArraySet() + + private val onIndicatorAccuracyRadiusChangedListeners = + CopyOnWriteArraySet() /** * Adds a listener that gets invoked when indicator position changes. @@ -51,7 +53,7 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, * @param listener Listener that gets invoked when indicator position changes */ override fun addOnIndicatorPositionChangedListener(listener: OnIndicatorPositionChangedListener) { - onIndicatorPositionChangedListener.add(listener) + onIndicatorPositionChangedListeners.add(listener) } /** @@ -60,12 +62,12 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, * @param listener Listener that gets invoked when indicator position changes. */ override fun removeOnIndicatorPositionChangedListener(listener: OnIndicatorPositionChangedListener) { - onIndicatorPositionChangedListener.remove(listener) + onIndicatorPositionChangedListeners.remove(listener) } @VisibleForTesting(otherwise = PRIVATE) internal val indicatorPositionChangedListener = OnIndicatorPositionChangedListener { - for (listener in onIndicatorPositionChangedListener) { + for (listener in onIndicatorPositionChangedListeners) { listener.onIndicatorPositionChanged(it) } } @@ -76,7 +78,7 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, * @param listener Listener that gets invoked when indicator bearing changes */ override fun addOnIndicatorBearingChangedListener(listener: OnIndicatorBearingChangedListener) { - onIndicatorBearingChangedListener.add(listener) + onIndicatorBearingChangedListeners.add(listener) } /** @@ -85,7 +87,25 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, * @param listener Listener that gets invoked when indicator bearing changes. */ override fun removeOnIndicatorBearingChangedListener(listener: OnIndicatorBearingChangedListener) { - onIndicatorBearingChangedListener.remove(listener) + onIndicatorBearingChangedListeners.remove(listener) + } + + /** + * Adds a listener that gets invoked when indicator accuracy radius changes. + * + * @param listener Listener that gets invoked when indicator accuracy radius changes + */ + override fun addOnIndicatorAccuracyRadiusChangedListener(listener: OnIndicatorAccuracyRadiusChangedListener) { + onIndicatorAccuracyRadiusChangedListeners.add(listener) + } + + /** + * Removes a listener that gets invoked when indicator accuracy radius changes. + * + * @param listener Listener that gets invoked when indicator accuracy radius changes. + */ + override fun removeOnIndicatorAccuracyRadiusChangedListener(listener: OnIndicatorAccuracyRadiusChangedListener) { + onIndicatorAccuracyRadiusChangedListeners.remove(listener) } /** @@ -120,11 +140,18 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, @VisibleForTesting(otherwise = PRIVATE) internal val indicatorBearingChangedListener = OnIndicatorBearingChangedListener { - for (listener in onIndicatorBearingChangedListener) { + for (listener in onIndicatorBearingChangedListeners) { listener.onIndicatorBearingChanged(it) } } + @VisibleForTesting(otherwise = PRIVATE) + internal val indicatorAccuracyRadiusChangedListener = OnIndicatorAccuracyRadiusChangedListener { + for (listener in onIndicatorAccuracyRadiusChangedListeners) { + listener.onIndicatorAccuracyRadiusChanged(it) + } + } + /** * Set the LocationProvider, it will replace the default location provider provided by the LocationComponentPlugin. */ @@ -161,6 +188,7 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, if (locationPuckManager == null) { locationPuckManager = LocationPuckManager( settings = internalSettings, + settings2 = internalSettings2, delegateProvider = delegateProvider, positionManager = LocationComponentPositionManager( style, @@ -170,7 +198,8 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, layerSourceProvider = LayerSourceProvider(), animationManager = PuckAnimatorManager( indicatorPositionChangedListener, - indicatorBearingChangedListener + indicatorBearingChangedListener, + indicatorAccuracyRadiusChangedListener ) ) } @@ -211,6 +240,9 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, this.context = WeakReference(context) internalSettings = LocationComponentAttributeParser.parseLocationComponentSettings(context, attrs, pixelRatio) + internalSettings2 = + LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, pixelRatio) + if (internalSettings.enabled && locationProvider == null) { locationProvider = LocationProviderImpl(context) } @@ -227,6 +259,8 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, this.context = WeakReference(context) this.internalSettings = LocationComponentAttributeParser.parseLocationComponentSettings(context, attrs, pixelRatio) + this.internalSettings2 = + LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, pixelRatio) this.locationProvider = locationProvider this.locationPuckManager = locationPuckManager } @@ -253,6 +287,20 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, locationPuckManager?.updateCurrentBearing(*bearing, options = options) } + /** + * Called whenever the accuracy radius is updated. + * @param radius - supports multiple radius value to create more complex animations with intermediate points. + * Last [radius] value will always be the animator target for next animation. + * @param options - if specified explicitly will apply current animator option to radius animation. + * Otherwise default animator options will be used. + */ + override fun onAccuracyRadiusUpdated( + vararg radius: Double, + options: (ValueAnimator.() -> Unit)? + ) { + locationPuckManager?.updateAccuracyRadius(*radius, options = options) + } + /** * Update [ValueAnimator] options that will be used to animate between [Point] updates by default. * This will apply to all upcoming updates. @@ -269,6 +317,14 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, locationPuckManager?.updateBearingAnimator(options) } + /** + * Update [ValueAnimator] options that will be used to animate between accuracy radius [Double] updates by default. + * This will apply to all upcoming updates. + */ + override fun onPuckAccuracyRadiusAnimatorDefaultOptionsUpdated(options: ValueAnimator.() -> Unit) { + locationPuckManager?.updateAccuracyRadiusAnimator(options) + } + /** * Provides all map delegate instances. */ @@ -294,7 +350,8 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, if (internalSettings.enabled && !isLocationComponentActivated) { context.get()?.let { if (locationProvider == null) { - locationProvider = LocationProviderImpl(it) + locationProvider = + LocationProviderImpl(it) } activateLocationComponent() } @@ -305,4 +362,12 @@ class LocationComponentPluginImpl : LocationComponentPlugin, LocationConsumer, deactivateLocationComponent() } } + + override lateinit var internalSettings2: LocationComponentSettings2 + + override fun applySettings2() { + if (internalSettings.enabled) { + locationPuckManager?.updateSettings2(internalSettings2) + } + } } \ No newline at end of file diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRenderer.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRenderer.kt index 379d9d1f71..9584158708 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRenderer.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRenderer.kt @@ -46,12 +46,13 @@ internal class LocationIndicatorLayerRenderer( setLayerVisibility(true) } - override fun styleAccuracy(accuracyAlpha: Float, accuracyColor: Int) { + override fun styleAccuracy(accuracyColor: Int, accuracyBorderColor: Int) { val colorArray: FloatArray = colorToRgbaArray(accuracyColor) - colorArray[3] = accuracyAlpha + val borderColorArray: FloatArray = colorToRgbaArray(accuracyBorderColor) val rgbaExpression = buildRGBAExpression(colorArray) + val borderRgbaExpression = buildRGBAExpression(borderColorArray) layer.accuracyRadiusColor(rgbaExpression) - layer.accuracyRadiusBorderColor(rgbaExpression) + layer.accuracyRadiusBorderColor(borderRgbaExpression) } override fun setLatLng(latLng: Point) { diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationLayerRenderer.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationLayerRenderer.kt index 38ae9be438..d98e2d4aa9 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationLayerRenderer.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationLayerRenderer.kt @@ -18,7 +18,7 @@ internal interface LocationLayerRenderer { fun show() - fun styleAccuracy(accuracyAlpha: Float, accuracyColor: Int) + fun styleAccuracy(accuracyColor: Int, accuracyBorderColor: Int) fun setLatLng(latLng: Point) diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImpl.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImpl.kt index b22358d039..4ea9d733d0 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImpl.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImpl.kt @@ -19,7 +19,8 @@ import java.util.concurrent.CopyOnWriteArrayList * Default Location Provider implementation, it can be overwritten by users. */ internal class LocationProviderImpl(context: Context) : - LocationProvider, LocationEngineCallback { + LocationProvider, + LocationEngineCallback { private val contextWeekRef: WeakReference = WeakReference(context) private val locationEngine = LocationEngineProvider.getBestLocationEngine(context) @@ -63,6 +64,9 @@ internal class LocationProviderImpl(context: Context) : locationConsumers.forEach { consumer -> consumer.onLocationUpdated(Point.fromLngLat(location.longitude, location.latitude)) consumer.onBearingUpdated(location.bearing.toDouble()) + if (consumer is LocationConsumer2) { + consumer.onAccuracyRadiusUpdated(location.accuracy.toDouble()) + } } } diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManager.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManager.kt index a6049b01cf..fb2d0f06cd 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManager.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManager.kt @@ -13,10 +13,12 @@ import com.mapbox.maps.plugin.LocationPuck3D import com.mapbox.maps.plugin.delegates.MapDelegateProvider import com.mapbox.maps.plugin.locationcomponent.animators.PuckAnimatorManager import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings2 import kotlin.math.pow internal class LocationPuckManager( var settings: LocationComponentSettings, + var settings2: LocationComponentSettings2, private val delegateProvider: MapDelegateProvider, private val positionManager: LocationComponentPositionManager, private val layerSourceProvider: LayerSourceProvider, @@ -37,6 +39,11 @@ internal class LocationPuckManager( lastBearing = it } + private var lastAccuracyRadius: Double = 0.0 + private val onAccuracyRadiusUpdated: ((Double) -> Unit) = { + lastAccuracyRadius = it + } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal var locationLayerRenderer = when (val puck = settings.locationPuck) { @@ -50,9 +57,10 @@ internal class LocationPuckManager( fun initialize(style: StyleInterface) { if (!locationLayerRenderer.isRendererInitialised()) { - animationManager.setUpdateListeners(onLocationUpdated, onBearingUpdated) + animationManager.setUpdateListeners(onLocationUpdated, onBearingUpdated, onAccuracyRadiusUpdated) animationManager.setLocationLayerRenderer(locationLayerRenderer) animationManager.applyPulsingAnimationSettings(settings) + animationManager.applyAccuracyRadiusSettings(settings2) locationLayerRenderer.addLayers(positionManager) lastLocation?.let { updateCurrentPosition(it) @@ -98,6 +106,11 @@ internal class LocationPuckManager( } } + fun updateSettings2(settings2: LocationComponentSettings2) { + this.settings2 = settings2 + animationManager.applyAccuracyRadiusSettings(settings2) + } + // // Animations // @@ -129,6 +142,14 @@ internal class LocationPuckManager( ) } + fun updateAccuracyRadius(vararg radius: Double, options: (ValueAnimator.() -> Unit)? = null) { + val targets = doubleArrayOf(lastAccuracyRadius, *radius) + animationManager.animateAccuracyRadius( + *targets, + options = options + ) + } + fun updateLocationAnimator(block: ValueAnimator.() -> Unit) { animationManager.updatePositionAnimator(block) } @@ -137,6 +158,10 @@ internal class LocationPuckManager( animationManager.updateBearingAnimator(block) } + fun updateAccuracyRadiusAnimator(block: ValueAnimator.() -> Unit) { + animationManager.updateAccuracyRadiusAnimator(block) + } + // // Layer action // diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/ModelLayerRenderer.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/ModelLayerRenderer.kt index 07bd70f1d2..daabf8319b 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/ModelLayerRenderer.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/ModelLayerRenderer.kt @@ -50,7 +50,7 @@ internal class ModelLayerRenderer( setLayerVisibility(true) } - override fun styleAccuracy(accuracyAlpha: Float, accuracyColor: Int) { + override fun styleAccuracy(accuracyColor: Int, accuracyBorderColor: Int) { } override fun setLatLng(latLng: Point) { diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAccuracyRadiusAnimator.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAccuracyRadiusAnimator.kt new file mode 100644 index 0000000000..2fa6abfadf --- /dev/null +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAccuracyRadiusAnimator.kt @@ -0,0 +1,26 @@ +package com.mapbox.maps.plugin.locationcomponent.animators + +import android.graphics.Color +import androidx.annotation.ColorInt +import com.mapbox.maps.plugin.locationcomponent.OnIndicatorAccuracyRadiusChangedListener + +internal class PuckAccuracyRadiusAnimator(private val accuracyRadiusChangedListener: OnIndicatorAccuracyRadiusChangedListener) : + PuckAnimator(Evaluators.DOUBLE) { + + internal var enabled = false + @ColorInt + internal var accuracyCircleColor = Color.BLUE + @ColorInt + internal var accuracyCircleBorderColor = Color.BLUE + + override fun updateLayer(fraction: Float, value: Double) { + if (enabled) { + val resolvedValue = 0f.coerceAtLeast(value.toFloat()) + locationRenderer?.setAccuracyRadius(resolvedValue) + locationRenderer?.styleAccuracy(accuracyCircleColor, accuracyCircleBorderColor) + accuracyRadiusChangedListener.onIndicatorAccuracyRadiusChanged(value) + } else { + locationRenderer?.setAccuracyRadius(0f) + } + } +} \ No newline at end of file diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManager.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManager.kt index 916493b824..408c31816c 100644 --- a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManager.kt +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManager.kt @@ -4,46 +4,59 @@ import android.animation.ValueAnimator import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.PRIVATE import com.mapbox.geojson.Point +import com.mapbox.maps.plugin.locationcomponent.* import com.mapbox.maps.plugin.locationcomponent.LocationLayerRenderer -import com.mapbox.maps.plugin.locationcomponent.OnIndicatorBearingChangedListener -import com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListener import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings2 import com.mapbox.maps.util.MathUtils internal class PuckAnimatorManager( indicatorPositionChangedListener: OnIndicatorPositionChangedListener, - indicatorBearingChangedListener: OnIndicatorBearingChangedListener + indicatorBearingChangedListener: OnIndicatorBearingChangedListener, + indicatorAccuracyRadiusChangedListener: OnIndicatorAccuracyRadiusChangedListener ) { private var bearingAnimator = PuckBearingAnimator(indicatorBearingChangedListener) private var positionAnimator = PuckPositionAnimator(indicatorPositionChangedListener) + private var accuracyRadiusAnimator = + PuckAccuracyRadiusAnimator(indicatorAccuracyRadiusChangedListener) private var pulsingAnimator = PuckPulsingAnimator() @VisibleForTesting(otherwise = PRIVATE) constructor( indicatorPositionChangedListener: OnIndicatorPositionChangedListener, indicatorBearingChangedListener: OnIndicatorBearingChangedListener, + indicatorAccuracyRadiusChangedListener: OnIndicatorAccuracyRadiusChangedListener, bearingAnimator: PuckBearingAnimator, positionAnimator: PuckPositionAnimator, - pulsingAnimator: PuckPulsingAnimator - ) : this(indicatorPositionChangedListener, indicatorBearingChangedListener) { + pulsingAnimator: PuckPulsingAnimator, + radiusAnimator: PuckAccuracyRadiusAnimator + ) : this( + indicatorPositionChangedListener, + indicatorBearingChangedListener, + indicatorAccuracyRadiusChangedListener + ) { this.bearingAnimator = bearingAnimator this.positionAnimator = positionAnimator this.pulsingAnimator = pulsingAnimator + this.accuracyRadiusAnimator = radiusAnimator } fun setLocationLayerRenderer(renderer: LocationLayerRenderer) { bearingAnimator.setLocationLayerRenderer(renderer) positionAnimator.setLocationLayerRenderer(renderer) pulsingAnimator.setLocationLayerRenderer(renderer) + accuracyRadiusAnimator.setLocationLayerRenderer(renderer) } fun setUpdateListeners( onLocationUpdated: ((Point) -> Unit), - onBearingUpdated: ((Double) -> Unit) + onBearingUpdated: ((Double) -> Unit), + onAccuracyRadiusUpdated: ((Double) -> Unit) ) { positionAnimator.setUpdateListener(onLocationUpdated) bearingAnimator.setUpdateListener(onBearingUpdated) + accuracyRadiusAnimator.setUpdateListener(onAccuracyRadiusUpdated) } fun onStart() { @@ -56,13 +69,17 @@ internal class PuckAnimatorManager( bearingAnimator.cancelRunning() positionAnimator.cancelRunning() pulsingAnimator.cancelRunning() + accuracyRadiusAnimator.cancelRunning() } fun animateBearing( vararg targets: Double, options: (ValueAnimator.() -> Unit)? ) { - bearingAnimator.animate(*MathUtils.prepareOptimalBearingPath(targets).toTypedArray(), options = options) + bearingAnimator.animate( + *MathUtils.prepareOptimalBearingPath(targets).toTypedArray(), + options = options + ) } fun animatePosition( @@ -72,6 +89,21 @@ internal class PuckAnimatorManager( positionAnimator.animate(*targets, options = options) } + fun animateAccuracyRadius( + vararg targets: Double, + options: (ValueAnimator.() -> Unit)? + ) { + accuracyRadiusAnimator.animate(*targets.toTypedArray(), options = options) + } + + fun applyAccuracyRadiusSettings(accuracyRadiusSettings: LocationComponentSettings2) { + accuracyRadiusAnimator.apply { + enabled = accuracyRadiusSettings.showAccuracyRing + accuracyCircleColor = accuracyRadiusSettings.accuracyRingColor + accuracyCircleBorderColor = accuracyRadiusSettings.accuracyRingBorderColor + } + } + fun applyPulsingAnimationSettings(settings: LocationComponentSettings) { pulsingAnimator.apply { enabled = settings.pulsingEnabled @@ -92,4 +124,8 @@ internal class PuckAnimatorManager( fun updatePositionAnimator(block: ValueAnimator.() -> Unit) { positionAnimator.updateOptions(block) } + + fun updateAccuracyRadiusAnimator(block: ValueAnimator.() -> Unit) { + accuracyRadiusAnimator.updateOptions(block) + } } \ No newline at end of file diff --git a/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentAttributeParser2.kt b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentAttributeParser2.kt new file mode 100644 index 0000000000..2871d8ece3 --- /dev/null +++ b/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentAttributeParser2.kt @@ -0,0 +1,34 @@ +// This file is generated. + +package com.mapbox.maps.plugin.locationcomponent.generated + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import com.mapbox.maps.plugin.locationcomponent.R + +/** + * Utility class for parsing [AttributeSet] to [LocationComponentSettings]. + */ +internal object LocationComponentAttributeParser2 { + /** + * Parse [AttributeSet] to [LocationComponentSettings2]. + * + * @param context Context + * @param attrs AttributionSet + */ + fun parseLocationComponentSettings2(context: Context, attrs: AttributeSet?, pixelRatio: Float = 1.0f): LocationComponentSettings2 { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.mapbox_MapView, 0, 0) + try { + return LocationComponentSettings2( + showAccuracyRing = typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_locationComponentShowAccuracyRing, false), + accuracyRingColor = typedArray.getColor(R.styleable.mapbox_MapView_mapbox_locationComponentAccuracyRingColor, Color.parseColor("#4d89cff0")), + accuracyRingBorderColor = typedArray.getColor(R.styleable.mapbox_MapView_mapbox_locationComponentAccuracyRingBorderColor, Color.parseColor("#4d89cff0")), + ) + } finally { + typedArray.recycle() + } + } +} + +// End of generated file. \ No newline at end of file diff --git a/plugin-locationcomponent/src/main/res-public/values/public.xml b/plugin-locationcomponent/src/main/res-public/values/public.xml index e3d15ca281..e692e9e407 100644 --- a/plugin-locationcomponent/src/main/res-public/values/public.xml +++ b/plugin-locationcomponent/src/main/res-public/values/public.xml @@ -5,15 +5,24 @@ - + - + - + + + + + + + + + + diff --git a/plugin-locationcomponent/src/main/res/values/attrs.xml b/plugin-locationcomponent/src/main/res/values/attrs.xml index 91a0c64f71..7c83296032 100644 --- a/plugin-locationcomponent/src/main/res/values/attrs.xml +++ b/plugin-locationcomponent/src/main/res/values/attrs.xml @@ -4,12 +4,18 @@ - + - + - + + + + + + + diff --git a/plugin-locationcomponent/src/main/res/values/colors.xml b/plugin-locationcomponent/src/main/res/values/colors.xml index 565d1c99ca..530544a875 100644 --- a/plugin-locationcomponent/src/main/res/values/colors.xml +++ b/plugin-locationcomponent/src/main/res/values/colors.xml @@ -4,6 +4,6 @@ #7D7F80 #1E8CAB - #4A90E2 + #0590E2 #A1B0C0 diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImplTest.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImplTest.kt index a7b86093b9..411aa26428 100644 --- a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImplTest.kt +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImplTest.kt @@ -15,6 +15,7 @@ import com.mapbox.maps.extension.style.StyleInterface import com.mapbox.maps.plugin.LocationPuck2D import com.mapbox.maps.plugin.delegates.MapDelegateProvider import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentAttributeParser +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentAttributeParser2 import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings import io.mockk.* import org.junit.Assert.* @@ -49,6 +50,7 @@ class LocationComponentPluginImplTest { @Before fun setup() { mockkObject(LocationComponentAttributeParser) + mockkObject(LocationComponentAttributeParser2) mockkStatic(LocationEngineProvider::class) every { context.obtainStyledAttributes(any(), any(), 0, 0) } returns typedArray diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRendererTest.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRendererTest.kt index 4f1b3a9bbe..23debc636b 100644 --- a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRendererTest.kt +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationIndicatorLayerRendererTest.kt @@ -112,7 +112,7 @@ class LocationIndicatorLayerRendererTest { colorArray[3] = 1.0f val rgbaExpression = LocationIndicatorLayerRenderer.buildRGBAExpression(colorArray) - locationLayerRenderer.styleAccuracy(1.0f, Color.RED) + locationLayerRenderer.styleAccuracy(Color.RED, Color.RED) verify { layerWrapper.accuracyRadiusColor(capture(expressionSlot)) } assertEquals(rgbaExpression.toString(), expressionSlot.captured.toString()) diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImplTest.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImplTest.kt index 7f15fec8b5..51385888dc 100644 --- a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImplTest.kt +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationProviderImplTest.kt @@ -21,7 +21,6 @@ class LocationProviderImplTest { private val locationEngine = mockk(relaxed = true) private val locationConsumer1 = mockk(relaxed = true) private val locationConsumer2 = mockk(relaxed = true) - private val locationEngineRequestSlot = CapturingSlot() private val locationEngineCallbackSlot = CapturingSlot>() diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManagerTest.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManagerTest.kt index 87393eb6b5..216244cfac 100644 --- a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManagerTest.kt +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/LocationPuckManagerTest.kt @@ -12,6 +12,7 @@ import com.mapbox.maps.plugin.delegates.MapCameraManagerDelegate import com.mapbox.maps.plugin.delegates.MapDelegateProvider import com.mapbox.maps.plugin.locationcomponent.animators.PuckAnimatorManager import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings2 import com.mapbox.maps.util.captureVararg import io.mockk.CapturingSlot import io.mockk.every @@ -32,6 +33,7 @@ import org.robolectric.RobolectricTestRunner class LocationPuckManagerTest { private val settings = mockk(relaxed = true) + private val accuracyRadiusSettings = mockk(relaxed = true) private val delegateProvider = mockk(relaxed = true) private val mapCameraDelegate = mockk(relaxed = true) private val style = mockk(relaxed = true) @@ -61,6 +63,7 @@ class LocationPuckManagerTest { every { settings.enabled } returns true locationPuckManager = LocationPuckManager( settings, + accuracyRadiusSettings, delegateProvider, positionManager, layerSourceProvider, @@ -73,7 +76,7 @@ class LocationPuckManagerTest { fun testInitialise() { locationPuckManager.initialize(style) verify { animationManager.setLocationLayerRenderer(locationLayerRenderer) } - verify { animationManager.setUpdateListeners(any(), any()) } + verify { animationManager.setUpdateListeners(any(), any(), any()) } verify { animationManager.applyPulsingAnimationSettings(settings) } verify { locationLayerRenderer.addLayers(positionManager) } verify { locationLayerRenderer.initializeComponents(style) } @@ -85,7 +88,7 @@ class LocationPuckManagerTest { locationPuckManager.lastLocation = Point.fromLngLat(0.0, 0.0) locationPuckManager.initialize(style) verify { animationManager.setLocationLayerRenderer(locationLayerRenderer) } - verify { animationManager.setUpdateListeners(any(), any()) } + verify { animationManager.setUpdateListeners(any(), any(), any()) } verify { animationManager.applyPulsingAnimationSettings(settings) } verify { locationLayerRenderer.addLayers(positionManager) } verify { locationLayerRenderer.initializeComponents(style) } @@ -267,7 +270,7 @@ class LocationPuckManagerTest { locationPuckManager.initialize(style) verify(exactly = 0) { animationManager.setLocationLayerRenderer(locationLayerRenderer) } - verify(exactly = 0) { animationManager.setUpdateListeners(any(), any()) } + verify(exactly = 0) { animationManager.setUpdateListeners(any(), any(), any()) } verify(exactly = 0) { animationManager.applyPulsingAnimationSettings(settings) } verify(exactly = 0) { locationLayerRenderer.addLayers(positionManager) } verify(exactly = 0) { locationLayerRenderer.initializeComponents(style) } diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAccuracyRadiusAnimatorTest.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAccuracyRadiusAnimatorTest.kt new file mode 100644 index 0000000000..8d1832da5a --- /dev/null +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAccuracyRadiusAnimatorTest.kt @@ -0,0 +1,112 @@ +package com.mapbox.maps.plugin.locationcomponent.animators + +import android.animation.ValueAnimator +import android.os.Looper +import com.mapbox.maps.plugin.locationcomponent.OnIndicatorAccuracyRadiusChangedListener +import io.mockk.* +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows +import org.robolectric.annotation.LooperMode + +@RunWith(RobolectricTestRunner::class) +@LooperMode(LooperMode.Mode.PAUSED) +class PuckAccuracyRadiusAnimatorTest { + + private lateinit var accuracyRadiusAnimator: PuckAccuracyRadiusAnimator + private lateinit var userConfiguredAnimator: ValueAnimator + + private val accuracyRadiusChangedListener = mockk(relaxed = true) + + @Before + fun setUp() { + val animator = PuckAccuracyRadiusAnimator(accuracyRadiusChangedListener) + userConfiguredAnimator = spyk(animator.clone()) + animator.userConfiguredAnimator = userConfiguredAnimator + accuracyRadiusAnimator = spyk(animator) + } + + @Test + fun animatePredefined() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.animate(0.0, 10.0) + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify(exactly = 0) { accuracyRadiusAnimator.cancel() } + verify(exactly = 0) { userConfiguredAnimator.cancel() } + verify(exactly = 1) { accuracyRadiusAnimator.start() } + verify(exactly = 0) { userConfiguredAnimator.start() } + } + + @Test + fun animateCustomAnimatorOptions() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.animate(0.0, 10.0, options = {}) + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify(exactly = 1) { userConfiguredAnimator.start() } + verify(exactly = 0) { accuracyRadiusAnimator.cancel() } + verify(exactly = 0) { userConfiguredAnimator.cancel() } + verify(exactly = 0) { accuracyRadiusAnimator.start() } + } + + @Test + fun animateInterruptDefaultWithCustom() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.animate(0.0, 5.0) + accuracyRadiusAnimator.animate( + 0.0, 10.0, + options = { duration = 50 } + ) + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify(exactly = 1) { accuracyRadiusAnimator.cancel() } + verify(exactly = 0) { userConfiguredAnimator.cancel() } + verify(exactly = 1) { accuracyRadiusAnimator.start() } + verify(exactly = 1) { userConfiguredAnimator.start() } + } + + @Test + fun cancelRunningWhenNotRunning() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.cancelRunning() + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify(exactly = 0) { accuracyRadiusAnimator.cancel() } + verify(exactly = 0) { userConfiguredAnimator.cancel() } + } + + @Test + fun cancelRunningWhenRunning() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.animate(0.0, 10.0) + accuracyRadiusAnimator.cancelRunning() + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify(exactly = 1) { accuracyRadiusAnimator.cancel() } + verify(exactly = 0) { userConfiguredAnimator.cancel() } + } + + @Test + fun cancelRunningWhenRunningCustom() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.animate( + 0.0, 10.0, + options = { duration = 50 } + ) + accuracyRadiusAnimator.cancelRunning() + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify(exactly = 0) { accuracyRadiusAnimator.cancel() } + verify(exactly = 1) { userConfiguredAnimator.cancel() } + } + + @Test + fun updateOptions() { + Shadows.shadowOf(Looper.getMainLooper()).pause() + accuracyRadiusAnimator.updateOptions { + duration = 2000 + } + accuracyRadiusAnimator.animate(0.0, 10.0) + Shadows.shadowOf(Looper.getMainLooper()).idle() + verify { accuracyRadiusAnimator.start() } + Assert.assertEquals(2000, accuracyRadiusAnimator.duration) + } +} \ No newline at end of file diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManagerTest.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManagerTest.kt index 8df0196815..77b89790fa 100644 --- a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManagerTest.kt +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/animators/PuckAnimatorManagerTest.kt @@ -1,16 +1,19 @@ package com.mapbox.maps.plugin.locationcomponent.animators import android.animation.ValueAnimator +import android.graphics.Color import android.os.Looper import com.mapbox.geojson.Point import com.mapbox.maps.plugin.locationcomponent.LocationLayerRenderer import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettings2 import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.hamcrest.MatcherAssert import org.hamcrest.Matchers import org.junit.Assert +import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -25,17 +28,22 @@ class PuckAnimatorManagerTest { private lateinit var puckAnimatorManager: PuckAnimatorManager private val bearingAnimator: PuckBearingAnimator = PuckBearingAnimator(mockk(relaxUnitFun = true)) - private val positionAnimator: PuckPositionAnimator = PuckPositionAnimator(mockk(relaxUnitFun = true)) + private val positionAnimator: PuckPositionAnimator = + PuckPositionAnimator(mockk(relaxUnitFun = true)) + private val accuracyRadiusAnimator: PuckAccuracyRadiusAnimator = + PuckAccuracyRadiusAnimator(mockk(relaxUnitFun = true)) private val pulsingAnimator: PuckPulsingAnimator = mockk(relaxed = true) @Before fun setUp() { puckAnimatorManager = PuckAnimatorManager( + mockk(), mockk(), mockk(), bearingAnimator, positionAnimator, - pulsingAnimator + pulsingAnimator, + accuracyRadiusAnimator ) } @@ -47,6 +55,7 @@ class PuckAnimatorManagerTest { bearingAnimator.setLocationLayerRenderer(locationLayerRenderer) positionAnimator.setLocationLayerRenderer(locationLayerRenderer) pulsingAnimator.setLocationLayerRenderer(locationLayerRenderer) + accuracyRadiusAnimator.setLocationLayerRenderer(locationLayerRenderer) } } @@ -71,6 +80,7 @@ class PuckAnimatorManagerTest { bearingAnimator.cancelRunning() positionAnimator.cancelRunning() pulsingAnimator.cancelRunning() + accuracyRadiusAnimator.cancelRunning() } } @@ -78,9 +88,15 @@ class PuckAnimatorManagerTest { fun setUpdateListeners() { val positionUpdateListener: ((Point) -> Unit) = {} val bearingUpdateListener: ((Double) -> Unit) = {} - puckAnimatorManager.setUpdateListeners(positionUpdateListener, bearingUpdateListener) + val accuracyRadiusUpdateListener: ((Double) -> Unit) = {} + puckAnimatorManager.setUpdateListeners( + positionUpdateListener, + bearingUpdateListener, + accuracyRadiusUpdateListener + ) Assert.assertEquals(positionUpdateListener, positionAnimator.updateListener) Assert.assertEquals(bearingUpdateListener, bearingAnimator.updateListener) + Assert.assertEquals(accuracyRadiusUpdateListener, accuracyRadiusAnimator.updateListener) } @Test @@ -93,7 +109,7 @@ class PuckAnimatorManagerTest { animatedValue = it } Shadows.shadowOf(Looper.getMainLooper()).pause() - puckAnimatorManager.setUpdateListeners({}, updateListener) + puckAnimatorManager.setUpdateListeners({}, updateListener, {}) puckAnimatorManager.animateBearing(0.0, 10.0, options = options) verify { bearingAnimator.animate(0.0, 10.0, options = options) @@ -113,7 +129,7 @@ class PuckAnimatorManagerTest { animatedValue = it } Shadows.shadowOf(Looper.getMainLooper()).pause() - puckAnimatorManager.setUpdateListeners(updateListener, {}) + puckAnimatorManager.setUpdateListeners(updateListener, {}, {}) puckAnimatorManager.animatePosition(START_POINT, END_POINT, options = options) verify { positionAnimator.animate(START_POINT, END_POINT, options = options) @@ -124,24 +140,47 @@ class PuckAnimatorManagerTest { } @Test - fun applyPulsingAnimationSettingsOne() { + fun applyAccuracyRadiusSettingsOne() { + val settings = mockk(relaxed = true) + every { settings.accuracyRingColor } returns Color.BLUE + every { settings.showAccuracyRing } returns true + puckAnimatorManager.applyAccuracyRadiusSettings(settings) + assertTrue(accuracyRadiusAnimator.enabled) + assertEquals(Color.BLUE, accuracyRadiusAnimator.accuracyCircleColor) + } + + @Test + fun applyAccuracyRadiusSettingsTwo() { + val settings = mockk(relaxed = true) + every { settings.accuracyRingColor } returns Color.GREEN + every { settings.showAccuracyRing } returns false + puckAnimatorManager.applyAccuracyRadiusSettings(settings) + assertFalse(accuracyRadiusAnimator.enabled) + assertEquals(Color.GREEN, accuracyRadiusAnimator.accuracyCircleColor) + } + + @Test + fun applyAnimationSettingsOne() { val settings = mockk(relaxed = true) every { settings.pulsingEnabled } returns true every { settings.pulsingMaxRadius } returns 20.0f + puckAnimatorManager.applyPulsingAnimationSettings(settings) verify { + pulsingAnimator.enabled = true pulsingAnimator.maxRadius = 20.0 pulsingAnimator.animateInfinite() } } @Test - fun applyPulsingAnimationSettingsTwo() { + fun applyAnimationSettingsTwo() { val settings = mockk(relaxed = true) every { settings.pulsingEnabled } returns false every { settings.pulsingMaxRadius } returns 20.0f puckAnimatorManager.applyPulsingAnimationSettings(settings) verify { + pulsingAnimator.enabled = false pulsingAnimator.maxRadius = 20.0 pulsingAnimator.cancelRunning() } @@ -169,6 +208,17 @@ class PuckAnimatorManagerTest { Assert.assertEquals(5000, positionAnimator.duration) } + @Test + fun updateAccuracyRadiusAnimator() { + val options: (ValueAnimator.() -> Unit) = { + duration = 5000 + } + Shadows.shadowOf(Looper.getMainLooper()).pause() + puckAnimatorManager.updateAccuracyRadiusAnimator(options) + Shadows.shadowOf(Looper.getMainLooper()).idle() + Assert.assertEquals(5000, accuracyRadiusAnimator.duration) + } + companion object { private val START_POINT = Point.fromLngLat(-122.4194, 37.7749) private val END_POINT = Point.fromLngLat(-77.0369, 38.9072) diff --git a/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentAttributeParser2Test.kt b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentAttributeParser2Test.kt new file mode 100644 index 0000000000..fe5361f602 --- /dev/null +++ b/plugin-locationcomponent/src/test/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentAttributeParser2Test.kt @@ -0,0 +1,97 @@ +// This file is generated. + +package com.mapbox.maps.plugin.locationcomponent.generated + +import android.content.Context +import android.content.res.TypedArray +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class LocationComponentAttributeParser2Test { + private val context: Context = mockk(relaxed = true) + + private val attrs: AttributeSet = mockk(relaxUnitFun = true) + + private val typedArray: TypedArray = mockk(relaxUnitFun = true) + + private val drawable = mockk(relaxed = true) + + @Before + fun setUp() { + mockkStatic(Color::class) + every { Color.parseColor(any()) } returns Color.WHITE + every { context.obtainStyledAttributes(any(), any(), 0, 0) } returns typedArray + every { typedArray.getString(any()) } returns "pk.token" + every { typedArray.getBoolean(any(), any()) } returns true + every { typedArray.getInt(any(), any()) } returns 2 + every { typedArray.getColor(any(), any()) } returns Color.RED + every { typedArray.getDimension(any(), any()) } returns 10.0f + every { typedArray.getFloat(any(), any()) } returns 10.0f + every { typedArray.getDrawable(any()) } returns drawable + every { typedArray.hasValue(any()) } returns true + every { typedArray.recycle() } just Runs + } + + @After + fun cleanUp() { + clearAllMocks() + } + + @Test + fun testTypedArrayRecycle() { + every { typedArray.getBoolean(any(), any()) } returns true + val settings = LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, 1.2f) + verify { typedArray.recycle() } + } + + @Test + fun testTypedArrayRecycleWithException() { + every { typedArray.getBoolean(any(), any()) }.throws(Exception("")) + try { + val settings = LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, 1.2f) + } catch (e: Exception) { + // do nothing + } + verify { typedArray.recycle() } + } + + @Test + fun showAccuracyRingTestTrue() { + every { typedArray.getBoolean(any(), any()) } returns true + val settings = LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, 1.2f) + assertEquals(true, settings.showAccuracyRing) + } + + @Test + fun showAccuracyRingTestFalse() { + every { typedArray.getBoolean(any(), any()) } returns false + val settings = LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, 1.2f) + assertEquals(false, settings.showAccuracyRing) + } + @Test + fun accuracyRingColorTest() { + every { typedArray.getColor(any(), any()) } returns Color.parseColor("#4d89cff0") + val settings = LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, 1.2f) + assertEquals(Color.parseColor("#4d89cff0"), settings.accuracyRingColor) + } + @Test + fun accuracyRingBorderColorTest() { + every { typedArray.getColor(any(), any()) } returns Color.parseColor("#4d89cff0") + val settings = LocationComponentAttributeParser2.parseLocationComponentSettings2(context, attrs, 1.2f) + assertEquals(Color.parseColor("#4d89cff0"), settings.accuracyRingBorderColor) + } +} + +// End of generated file. \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPlugin2.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPlugin2.kt new file mode 100644 index 0000000000..f67ee37c12 --- /dev/null +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPlugin2.kt @@ -0,0 +1,23 @@ +package com.mapbox.maps.plugin.locationcomponent + +import com.mapbox.maps.plugin.locationcomponent.generated.LocationComponentSettingsInterface2 + +/** + * Define the interfaces for the Location plugin. + */ +interface LocationComponentPlugin2 : LocationComponentPlugin, LocationComponentSettingsInterface2 { + + /** + * Adds a listener that gets invoked when indicator accuracy radius changes. + * + * @param listener Listener that gets invoked when indicator accuracy radius changes + */ + fun addOnIndicatorAccuracyRadiusChangedListener(listener: OnIndicatorAccuracyRadiusChangedListener) + + /** + * Removes a listener that gets invoked when indicator accuracy radius changes. + * + * @param listener Listener that gets invoked when indicator accuracy radius changes. + */ + fun removeOnIndicatorAccuracyRadiusChangedListener(listener: OnIndicatorAccuracyRadiusChangedListener) +} \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationConsumer2.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationConsumer2.kt new file mode 100644 index 0000000000..1549df9663 --- /dev/null +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationConsumer2.kt @@ -0,0 +1,27 @@ +package com.mapbox.maps.plugin.locationcomponent + +import android.animation.ValueAnimator + +/** + * Defines the interface for LocationConsumer. + */ +interface LocationConsumer2 : LocationConsumer { + + /** + * Called whenever the accuracy radius is updated. + * @param radius - supports multiple radius value to create more complex animations with intermediate points. + * Last [radius] value will always be the animator target for next animation. + * @param options - if specified explicitly will apply current animator option to radius animation. + * Otherwise default animator options will be used. + */ + fun onAccuracyRadiusUpdated( + vararg radius: Double, + options: (ValueAnimator.() -> Unit)? = null + ) + + /** + * Update [ValueAnimator] options that will be used to animate between accuracy radius [Double] updates by default. + * This will apply to all upcoming updates. + */ + fun onPuckAccuracyRadiusAnimatorDefaultOptionsUpdated(options: ValueAnimator.() -> Unit) +} \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/OnIndicatorAccuracyRadiusChangedListener.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/OnIndicatorAccuracyRadiusChangedListener.kt new file mode 100644 index 0000000000..b20ee4712b --- /dev/null +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/OnIndicatorAccuracyRadiusChangedListener.kt @@ -0,0 +1,13 @@ +package com.mapbox.maps.plugin.locationcomponent + +/** + * Listener that gets invoked when indicator accuracy radius changes. + */ +fun interface OnIndicatorAccuracyRadiusChangedListener { + /** + * This method is called on each accuracy radius change of the location indicator, including each animation frame. + * + * @param radius indicator's accuracy radius + */ + fun onIndicatorAccuracyRadiusChanged(radius: Double) +} \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings.kt index f73f535c86..7965cc30ef 100644 --- a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings.kt +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings.kt @@ -15,17 +15,17 @@ data class LocationComponentSettings @JvmOverloads constructor( var enabled: Boolean = false, /** - * Whether the location puck is pulsing on the map. Only work for 2D location puck. + * Whether the location puck is pulsing on the map. Works for 2D location puck only. */ var pulsingEnabled: Boolean = false, /** - * The color of the pulsing circle. Only work for 2D location puck. + * The color of the pulsing circle. Works for 2D location puck only. */ var pulsingColor: Int = Color.parseColor("#4A90E2"), /** - * The maximum radius of the pulsing circle. Only work for 2D location puck. This property is specified in pixels. + * The maximum radius of the pulsing circle. Works for 2D location puck only. This property is specified in pixels. */ var pulsingMaxRadius: Float = 10f, diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings2.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings2.kt new file mode 100644 index 0000000000..9419147db0 --- /dev/null +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettings2.kt @@ -0,0 +1,27 @@ +// This file is generated. + +package com.mapbox.maps.plugin.locationcomponent.generated + +import android.graphics.Color +/** + * Shows a location puck on the map. + */ +data class LocationComponentSettings2 @JvmOverloads constructor( + + /** + * Whether show accuracy ring with location puck. Works for 2D location puck only. + */ + var showAccuracyRing: Boolean = false, + + /** + * The color of the accuracy ring. Works for 2D location puck only. + */ + var accuracyRingColor: Int = Color.parseColor("#4d89cff0"), + + /** + * The color of the accuracy ring border. Works for 2D location puck only. + */ + var accuracyRingBorderColor: Int = Color.parseColor("#4d89cff0"), +) + +// End of generated file. \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase.kt index 2b97584450..45cea61793 100644 --- a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase.kt +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase.kt @@ -52,7 +52,7 @@ abstract class LocationComponentSettingsBase : LocationComponentSettingsInterfac } /** - * Whether the location puck is pulsing on the map. Only work for 2D location puck. + * Whether the location puck is pulsing on the map. Works for 2D location puck only. */ override var pulsingEnabled: Boolean get() { @@ -64,7 +64,7 @@ abstract class LocationComponentSettingsBase : LocationComponentSettingsInterfac } /** - * The color of the pulsing circle. Only work for 2D location puck. + * The color of the pulsing circle. Works for 2D location puck only. */ override var pulsingColor: Int get() { @@ -76,7 +76,7 @@ abstract class LocationComponentSettingsBase : LocationComponentSettingsInterfac } /** - * The maximum radius of the pulsing circle. Only work for 2D location puck. + * The maximum radius of the pulsing circle. Works for 2D location puck only. */ override var pulsingMaxRadius: Float get() { diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase2.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase2.kt new file mode 100644 index 0000000000..8edb7a7a0c --- /dev/null +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsBase2.kt @@ -0,0 +1,77 @@ +// This file is generated. + +package com.mapbox.maps.plugin.locationcomponent.generated + +/** + * Abstract settings class for LocationComponentPlugin. + * + * This abstract class exposes all the required public APIs to configure the LocationComponentPlugin. + */ +abstract class LocationComponentSettingsBase2 : LocationComponentSettingsInterface2, LocationComponentSettingsBase() { + /** + * Shows a location puck on the map. + */ + protected abstract var internalSettings2: LocationComponentSettings2 + + /** + * Apply the changes to the LocationComponentSettings to the LocationComponentPlugin. + */ + protected abstract fun applySettings2() + + /** + * Get current locationcomponent configuration. + * + * @return locationcomponent settings + */ + override fun getSettings2(): LocationComponentSettings2 { + return internalSettings2.copy() + } + + /** + * Update locationcomponent configuration, the update will be applied to the plugin automatically. + * + * @param block the receiver function of LocationComponentSettings + */ + override fun updateSettings2(block: LocationComponentSettings2.() -> Unit) { + this.internalSettings2.apply(block) + applySettings2() + } + + /** + * Whether show accuracy ring with location puck. Works for 2D location puck only. + */ + override var showAccuracyRing: Boolean + get() { + return this.internalSettings2.showAccuracyRing + } + set(value) { + this.internalSettings2.showAccuracyRing = value + applySettings2() + } + + /** + * The color of the accuracy ring. Works for 2D location puck only. + */ + override var accuracyRingColor: Int + get() { + return this.internalSettings2.accuracyRingColor + } + set(value) { + this.internalSettings2.accuracyRingColor = value + applySettings2() + } + + /** + * The color of the accuracy ring border. Works for 2D location puck only. + */ + override var accuracyRingBorderColor: Int + get() { + return this.internalSettings2.accuracyRingBorderColor + } + set(value) { + this.internalSettings2.accuracyRingBorderColor = value + applySettings2() + } +} + +// End of generated file. \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface.kt index ac4228007b..56a49ec2f0 100644 --- a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface.kt +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface.kt @@ -28,17 +28,17 @@ interface LocationComponentSettingsInterface { var enabled: Boolean /** - * Whether the location puck is pulsing on the map. Only work for 2D location puck. + * Whether the location puck is pulsing on the map. Works for 2D location puck only. */ var pulsingEnabled: Boolean /** - * The color of the pulsing circle. Only work for 2D location puck. + * The color of the pulsing circle. Works for 2D location puck only. */ var pulsingColor: Int /** - * The maximum radius of the pulsing circle. Only work for 2D location puck. + * The maximum radius of the pulsing circle. Works for 2D location puck only. */ var pulsingMaxRadius: Float diff --git a/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface2.kt b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface2.kt new file mode 100644 index 0000000000..7a0aaccec9 --- /dev/null +++ b/sdk-base/src/main/java/com/mapbox/maps/plugin/locationcomponent/generated/LocationComponentSettingsInterface2.kt @@ -0,0 +1,39 @@ +// This file is generated. + +package com.mapbox.maps.plugin.locationcomponent.generated + +/** + * Interface that defines the public settings interface for LocationComponentPlugin. + */ +interface LocationComponentSettingsInterface2 : LocationComponentSettingsInterface { + /** + * Get current locationcomponent configuration. + * + * @return locationcomponent settings + */ + fun getSettings2(): LocationComponentSettings2 + + /** + * Update locationcomponent configuration, the update will be applied to the plugin automatically. + * + * @param block the receiver function of LocationComponentSettings + */ + fun updateSettings2(block: LocationComponentSettings2.() -> Unit) + + /** + * Whether show accuracy ring with location puck. Works for 2D location puck only. + */ + var showAccuracyRing: Boolean + + /** + * The color of the accuracy ring. Works for 2D location puck only. + */ + var accuracyRingColor: Int + + /** + * The color of the accuracy ring border. Works for 2D location puck only. + */ + var accuracyRingBorderColor: Int +} + +// End of generated file. \ No newline at end of file