diff --git a/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt b/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt index a42101bf4..7f481f45e 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt @@ -52,8 +52,9 @@ import com.rnmapbox.rnmbx.modules.RNMBXOfflineModule import com.rnmapbox.rnmbx.modules.RNMBXOfflineModuleLegacy import com.rnmapbox.rnmbx.modules.RNMBXSnapshotModule import com.rnmapbox.rnmbx.modules.RNMBXTileStoreModule -import com.rnmapbox.rnmbx.shape_animators.RNMBXMovePointShapeAnimatorModule -import com.rnmapbox.rnmbx.shape_animators.ShapeAnimatorManager +import com.rnmapbox.rnmbx.shapeAnimators.RNMBXChangeLineOffsetsShapeAnimatorModule +import com.rnmapbox.rnmbx.shapeAnimators.RNMBXMovePointShapeAnimatorModule +import com.rnmapbox.rnmbx.shapeAnimators.ShapeAnimatorManager import com.rnmapbox.rnmbx.utils.ViewTagResolver class RNMBXPackage : TurboReactPackage() { @@ -103,6 +104,7 @@ class RNMBXPackage : TurboReactPackage() { RNMBXImageModule.NAME -> return RNMBXImageModule(reactApplicationContext, getViewTagResolver(reactApplicationContext, s)) RNMBXPointAnnotationModule.NAME -> return RNMBXPointAnnotationModule(reactApplicationContext, getViewTagResolver(reactApplicationContext, s)) RNMBXMovePointShapeAnimatorModule.NAME -> return RNMBXMovePointShapeAnimatorModule(reactApplicationContext, getShapeAnimators(s)) + RNMBXChangeLineOffsetsShapeAnimatorModule.NAME -> return RNMBXChangeLineOffsetsShapeAnimatorModule(reactApplicationContext, getShapeAnimators(s)) } return null } @@ -292,6 +294,15 @@ class RNMBXPackage : TurboReactPackage() { false, isTurboModule ) + moduleInfos[RNMBXChangeLineOffsetsShapeAnimatorModule.NAME] = ReactModuleInfo( + RNMBXChangeLineOffsetsShapeAnimatorModule.NAME, + RNMBXChangeLineOffsetsShapeAnimatorModule.NAME, + false, + false, + false, + false, + isTurboModule + ) moduleInfos } } diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/AbstractEventEmitter.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/AbstractEventEmitter.kt index 046eadfee..9542502f5 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/AbstractEventEmitter.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/AbstractEventEmitter.kt @@ -10,6 +10,7 @@ import com.facebook.react.uimanager.ViewGroupManager import com.facebook.react.uimanager.events.Event import com.facebook.react.uimanager.events.EventDispatcher import com.rnmapbox.rnmbx.events.IEvent +import com.rnmapbox.rnmbx.utils.Logger /** * Created by nickitaliano on 8/23/17. @@ -40,14 +41,19 @@ abstract class AbstractEventEmitter(reactApplicationContext: Rea return } mRateLimitedEvents[eventCacheKey] = System.currentTimeMillis() - mEventDispatcher!!.dispatchEvent( - AbstractEvent( - event.iD, - event.key, - event.canCoalesce(), - event.toJSON() + + try { + mEventDispatcher!!.dispatchEvent( + AbstractEvent( + event.iD, + event.key, + event.canCoalesce(), + event.toJSON() + ) ) - ) + } catch (e: Exception) { + Logger.e("Error dispatching event:", e.toString()) + } } override fun addEventEmitters(context: ThemedReactContext, view: T) { diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSource.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSource.kt index 309434f1e..363ab8ba3 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSource.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSource.kt @@ -1,32 +1,28 @@ package com.rnmapbox.rnmbx.components.styles.sources import android.content.Context -import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource -import com.rnmapbox.rnmbx.utils.ImageEntry -import android.graphics.drawable.BitmapDrawable import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReadableMap -import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView -import com.rnmapbox.rnmbx.events.FeatureClickEvent import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap import com.mapbox.bindgen.Value import com.mapbox.geojson.Feature -import com.rnmapbox.rnmbx.events.AndroidCallbackEvent import com.mapbox.geojson.FeatureCollection import com.mapbox.geojson.GeoJson import com.mapbox.geojson.Geometry import com.mapbox.maps.* import com.mapbox.maps.extension.style.expressions.generated.Expression -import com.rnmapbox.rnmbx.shape_animators.ShapeAnimationConsumer -import com.rnmapbox.rnmbx.shape_animators.ShapeAnimator -import com.rnmapbox.rnmbx.shape_animators.ShapeAnimatorManager +import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource +import com.rnmapbox.rnmbx.components.RemovalReason +import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView +import com.rnmapbox.rnmbx.events.AndroidCallbackEvent +import com.rnmapbox.rnmbx.events.FeatureClickEvent +import com.rnmapbox.rnmbx.shapeAnimators.ShapeAnimationConsumer +import com.rnmapbox.rnmbx.shapeAnimators.ShapeAnimator import com.rnmapbox.rnmbx.utils.Logger +import com.rnmapbox.rnmbx.v11compat.feature.* import java.net.URL -import java.util.ArrayList -import java.util.HashMap -import com.rnmapbox.rnmbx.v11compat.feature.* +private const val LOG_TAG = "RNMBXShapeSource" class RNMBXShapeSource(context: Context, private val mManager: RNMBXShapeSourceManager) : RNMBXSource(context), ShapeAnimationConsumer { @@ -47,6 +43,7 @@ class RNMBXShapeSource(context: Context, private val mManager: RNMBXShapeSourceM } override fun addToMap(mapView: RNMBXMapView) { + // Wait for style before adding the source to the map mapView.getMapboxMap().getStyle { val map = mapView.getMapboxMap() @@ -54,6 +51,14 @@ class RNMBXShapeSource(context: Context, private val mManager: RNMBXShapeSourceM } } + override fun removeFromMap(mapView: RNMBXMapView, reason: RemovalReason): Boolean { + + if (reason == RemovalReason.VIEW_REMOVAL) { + mShapeAnimator?.unsubscribe(this) + } + return super.removeFromMap(mapView, reason) + } + override fun setId(id: Int) { super.setId(id) mManager.tagAssigned(id) @@ -85,7 +90,8 @@ class RNMBXShapeSource(context: Context, private val mManager: RNMBXShapeSourceM mShapeAnimator = shapeAnimator shapeAnimator.subscribe(this) - shapeUpdated(shapeAnimator.getShape()) + val shape = shapeAnimator.getShape() + shapeUpdated(shape) } } else { mShape = geoJSONStr @@ -108,8 +114,8 @@ class RNMBXShapeSource(context: Context, private val mManager: RNMBXShapeSourceM else -> { Logger.e( LOG_TAG, - "Cannot convert shape to GeoJSONSourceData, neitthe Geometry, nor Feature or FeatureCollection: $geoJson" - ); + "Cannot convert shape to Geometry, Feature, or FeatureCollection: $geoJson" + ) return null } } @@ -214,7 +220,7 @@ class RNMBXShapeSource(context: Context, private val mManager: RNMBXShapeSourceM ) ) { features -> if (features.isError) { - Logger.e("RNMBXShapeSource", String.format("Error: %s", features.error)) + Logger.e(LOG_TAG, String.format("Error: %s", features.error)) } else { val payload: WritableMap = WritableNativeMap() val result: MutableList = ArrayList( diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSourceManager.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSourceManager.kt index 870349967..637372d08 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSourceManager.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/sources/RNMBXShapeSourceManager.kt @@ -7,13 +7,12 @@ import com.rnmapbox.rnmbx.components.AbstractEventEmitter import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.bridge.ReadableType -import com.facebook.react.common.MapBuilder import com.facebook.react.viewmanagers.RNMBXShapeSourceManagerInterface import com.mapbox.bindgen.Value import com.mapbox.maps.extension.style.expressions.generated.Expression import com.rnmapbox.rnmbx.events.constants.EventKeys import com.rnmapbox.rnmbx.events.constants.eventMapOf -import com.rnmapbox.rnmbx.shape_animators.ShapeAnimatorManager +import com.rnmapbox.rnmbx.shapeAnimators.ShapeAnimatorManager import com.rnmapbox.rnmbx.utils.ExpressionParser import com.rnmapbox.rnmbx.utils.Logger import com.rnmapbox.rnmbx.utils.ViewTagResolver diff --git a/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/AnimatableElement.kt b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/AnimatableElement.kt new file mode 100644 index 000000000..5b4c32b0e --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/AnimatableElement.kt @@ -0,0 +1,38 @@ +package com.rnmapbox.rnmbx.shapeAnimators + +internal class AnimatableElement( + var source: T, + var progress: T, + var target: T, + var startedAtSec: Double, + var progressDurationSec: Double, + var totalDurationSec: Double, + /** A function returning the difference in meters between the two values. */ + var getDistanceRemaining: (a: T, b: T) -> Double +) { + fun distanceRemaining(): Double { + return getDistanceRemaining(source, target) + } + + fun durationRatio(): Double { + return if (totalDurationSec > 0.0) { + progressDurationSec / totalDurationSec + } else { + 1.0 + } + } + + fun setProgress(value: T, animatorAgeSec: Double) { + progress = value + progressDurationSec = (animatorAgeSec - startedAtSec) + } + + fun reset(_source: T, _progress: T, _target: T, durationSec: Double, animatorAgeSec: Double) { + this.source = _source + this.progress = _progress + this.target = _target + this.startedAtSec = animatorAgeSec + this.progressDurationSec = 0.0 + this.totalDurationSec = durationSec + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.kt b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.kt new file mode 100644 index 000000000..755ab5e9e --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/RNMBXChangeLineOffsetsShapeAnimatorModule.kt @@ -0,0 +1,217 @@ +package com.rnmapbox.rnmbx.shapeAnimators + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.module.annotations.ReactModule +import com.mapbox.geojson.GeoJson +import com.mapbox.geojson.LineString +import com.mapbox.geojson.Point +import com.mapbox.turf.TurfConstants.UNIT_METERS +import com.mapbox.turf.TurfMeasurement +import com.mapbox.turf.TurfMisc +import com.rnmapbox.rnmbx.NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec + +class ChangeLineOffsetsShapeAnimator(tag: Tag, _lineString: LineString, startOffset: Double, endOffset: Double): ShapeAnimatorCommon(tag) { + private var lineString = _lineString + private var startOfLine = AnimatableElement( + startOffset, + startOffset, + startOffset, + 0.0, + 0.0, + 0.0, + { a, b -> b - a } + ) + private var endOfLine = AnimatableElement( + endOffset, + endOffset, + endOffset, + 0.0, + 0.0, + 0.0, + { a, b -> b - a } + ) + + override fun getAnimatedShape(animatorAgeSec: Double): GeoJson { + if (startOfLine.durationRatio() < 1) { + startOfLine.setProgress( + startOfLine.source + (startOfLine.distanceRemaining() * startOfLine.durationRatio()), + animatorAgeSec + ) + } + + if (endOfLine.durationRatio() < 1) { + endOfLine.setProgress( + endOfLine.source + (endOfLine.distanceRemaining() * endOfLine.durationRatio()), + animatorAgeSec + ) + } + + if (startOfLine.durationRatio() >= 1 && endOfLine.durationRatio() >= 1) { + stop() + } + + if (lineString.coordinates().count() < 2) { + return emptyGeoJsonObj + } + + val totalDistance = TurfMeasurement.length(lineString, UNIT_METERS) + if (totalDistance == 0.0) { + return emptyGeoJsonObj + } + + if (startOfLine.progress + endOfLine.progress >= totalDistance) { + return emptyGeoJsonObj + } + + val trimmed = TurfMisc.lineSliceAlong( + lineString, + startOfLine.progress, + totalDistance - endOfLine.progress, + UNIT_METERS + ) + return trimmed + } + + fun setLineString(lineString: LineString, startOffset: Double?, endOffset: Double?) { + this.lineString = lineString + if (startOffset != null) { + startOfLine.reset( + startOffset, + startOffset, + startOffset, + 0.0, + getAnimatorAgeSec() + ) + } + if (endOffset != null) { + endOfLine.reset( + endOffset, + endOffset, + endOffset, + 0.0, + getAnimatorAgeSec() + ) + } + refresh() + } + + fun setStartOffset(offset: Double, durationSec: Double) { + if (durationSec == 0.0) { + startOfLine.reset( + offset, + offset, + offset, + durationSec, + getAnimatorAgeSec() + ) + refresh() + } else { + start() + startOfLine.reset( + startOfLine.progress, + startOfLine.progress, + offset, + durationSec, + getAnimatorAgeSec() + ) + } + } + + fun setEndOffset(offset: Double, durationSec: Double) { + if (durationSec == 0.0) { + endOfLine.reset( + offset, + offset, + offset, + durationSec, + getAnimatorAgeSec() + ) + refresh() + } else { + start() + endOfLine.reset( + endOfLine.progress, + endOfLine.progress, + offset, + durationSec, + getAnimatorAgeSec() + ) + } + } +} + +@ReactModule(name = RNMBXChangeLineOffsetsShapeAnimatorModule.NAME) +class RNMBXChangeLineOffsetsShapeAnimatorModule( + reactContext: ReactApplicationContext?, + val shapeAnimatorManager: ShapeAnimatorManager +): NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec(reactContext) { + companion object { + const val LOG_TAG = "RNMBXChangeLineOffsetsShapeAnimatorModule" + const val NAME = "RNMBXChangeLineOffsetsShapeAnimatorModule" + } + + override fun create( + tag: Double, + coordinates: ReadableArray, + startOffset: Double, + endOffset: Double, + promise: Promise? + ) { + val lineString = buildLineString(coordinates) + + shapeAnimatorManager.add( + ChangeLineOffsetsShapeAnimator( + tag.toLong(), + lineString, + startOffset, + endOffset + ) + ) + promise?.resolve(tag.toInt()) + } + + private fun getAnimator(tag: Double): ChangeLineOffsetsShapeAnimator { + return shapeAnimatorManager.get(tag.toLong()) as ChangeLineOffsetsShapeAnimator + } + + override fun setLineString(tag: Double, coordinates: ReadableArray?, startOffset: Double, endOffset: Double, promise: Promise?) { + val animator = getAnimator(tag) + + if (coordinates == null) { + return + } + + val _startOffset = if (startOffset != -1.0) startOffset else null + val _endOffset = if (endOffset != -1.0) endOffset else null + + val lineString = buildLineString(coordinates) + animator.setLineString(lineString, _startOffset, _endOffset) + promise?.resolve(true) + } + + override fun setStartOffset(tag: Double, offset: Double, duration: Double, promise: Promise?) { + val animator = getAnimator(tag) + animator.setStartOffset(offset, duration / 1000) + promise?.resolve(true) + } + + override fun setEndOffset(tag: Double, offset: Double, duration: Double, promise: Promise?) { + val animator = getAnimator(tag) + animator.setEndOffset(offset, duration / 1000) + promise?.resolve(true) + } +} + +private fun buildLineString(_coordinates: ReadableArray): LineString { + var coordinates: List = listOf() + + for (i in 0 until _coordinates.size()) { + val arr = _coordinates.getArray(i) + val coord = Point.fromLngLat(arr.getDouble(0), arr.getDouble(1)) + coordinates = coordinates.plus(coord) + } + + return LineString.fromLngLats(coordinates) +} \ No newline at end of file diff --git a/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/RNMBXMovePointShapeAnimatorModule.kt b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/RNMBXMovePointShapeAnimatorModule.kt new file mode 100644 index 000000000..436ba24fa --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/RNMBXMovePointShapeAnimatorModule.kt @@ -0,0 +1,108 @@ +package com.rnmapbox.rnmbx.shapeAnimators + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.module.annotations.ReactModule +import com.mapbox.geojson.GeoJson +import com.mapbox.geojson.LineString +import com.mapbox.geojson.Point +import com.mapbox.turf.TurfConstants.UNIT_METERS +import com.mapbox.turf.TurfMeasurement +import com.rnmapbox.rnmbx.NativeRNMBXMovePointShapeAnimatorModuleSpec + +class MovePointShapeAnimator(tag: Tag, coordinate: Point) : ShapeAnimatorCommon(tag) { + private var point = AnimatableElement( + coordinate, + coordinate, + coordinate, + 0.0, + 0.0, + 0.0, + { a, b -> TurfMeasurement.distance(a, b) } + ) + + override fun getAnimatedShape(animatorAgeSec: Double): GeoJson { + val line = LineString.fromLngLats(listOf(point.source, point.target)) + val lineLength = TurfMeasurement.length(line, UNIT_METERS) + if (lineLength == 0.0) { + stop() + } + + val ratio = point.durationRatio() + if (ratio >= 0 && ratio < 1) { + point.setProgress( + TurfMeasurement.along(line, lineLength * ratio, UNIT_METERS), + animatorAgeSec + ) + } else if (ratio >= 1) { + stop() + } + + return point.progress + } + + fun moveTo(coordinate: Point, durationSec: Double) { + if (durationSec == 0.0) { + point.reset( + coordinate, + coordinate, + coordinate, + durationSec, + getAnimatorAgeSec() + ) + refresh() + } else { + start() + point.reset( + point.progress, + point.progress, + coordinate, + durationSec, + getAnimatorAgeSec() + ) + } + } +} + +@ReactModule(name = RNMBXMovePointShapeAnimatorModule.NAME) +class RNMBXMovePointShapeAnimatorModule( + reactContext: ReactApplicationContext?, + val shapeAnimatorManager: ShapeAnimatorManager +): NativeRNMBXMovePointShapeAnimatorModuleSpec(reactContext) { + companion object { + const val LOG_TAG = "RNMBXMovePointShapeAnimatorModule" + const val NAME = "RNMBXMovePointShapeAnimatorModule" + } + + @ReactMethod + override fun create(tag: Double, startCoordinate: ReadableArray, promise: Promise) { + shapeAnimatorManager.add( + MovePointShapeAnimator( + tag.toLong(), + Point.fromLngLat( + startCoordinate.getDouble(0), + startCoordinate.getDouble(1) + ) + ) + ) + promise.resolve(tag.toInt()) + } + + @ReactMethod + override fun moveTo( + tag: Double, + coordinate: ReadableArray?, + duration: Double, + promise: Promise? + ) { + val animator = shapeAnimatorManager.get(tag.toLong()) as MovePointShapeAnimator + + val targetCoord = Point.fromLngLat( + coordinate!!.getDouble(0), + coordinate.getDouble(1) + ) + animator.moveTo(targetCoord, duration / 1000) + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/ShapeAnimatorCommon.kt b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/ShapeAnimatorCommon.kt new file mode 100644 index 000000000..28c350ac6 --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/shapeAnimators/ShapeAnimatorCommon.kt @@ -0,0 +1,142 @@ +package com.rnmapbox.rnmbx.shapeAnimators + +import android.util.Log +import com.facebook.react.bridge.UiThreadUtil.runOnUiThread +import com.mapbox.geojson.FeatureCollection +import com.mapbox.geojson.GeoJson +import com.rnmapbox.rnmbx.utils.Logger +import org.json.JSONObject +import java.util.Date +import java.util.Timer +import java.util.TimerTask + +typealias Tag = Long + +interface ShapeAnimationConsumer { + fun shapeUpdated(geoJson: GeoJson) +} + +abstract class ShapeAnimator(val tag: Tag) { + abstract fun getShape(): GeoJson + abstract fun getAnimatedShape(animatorAgeSec: Double): GeoJson + abstract fun subscribe(consumer: ShapeAnimationConsumer) + abstract fun unsubscribe(consumer: ShapeAnimationConsumer) + abstract fun refresh() + abstract fun start() + abstract fun stop() +} + +private const val LOG_TAG = "RNMBXShapeAnimator" + +abstract class ShapeAnimatorCommon(tag: Tag): ShapeAnimator(tag) { + val emptyGeoJsonObj: FeatureCollection = FeatureCollection.fromFeatures(listOf()) + + private var timer: Timer? = null + private var startedAt = Date() + + private val fps = 30.0 + private val period = 1.0 / fps + + /** The number of seconds the animator has been running continuously. */ + fun getAnimatorAgeSec(): Double { + val now = Date() + return (now.time - startedAt.time).toDouble() / 1000 + } + + // region subscribers + private var subscribers = mutableListOf() + + override fun subscribe(consumer: ShapeAnimationConsumer) { + if (subscribers.contains(consumer)) { + return + } + + subscribers.add(consumer) + } + + override fun unsubscribe(consumer: ShapeAnimationConsumer) { + subscribers.remove(consumer) + if (subscribers.isEmpty()) { + stop() + } + } + // endregion + + override fun refresh() { + val timestamp = getAnimatorAgeSec() +// Log.d( +// LOG_TAG, +// "Refreshing animator for tag $tag (timestamp: $timestamp, subscribers: ${subscribers.count()})" +// ) + + val shape = getAnimatedShape(timestamp) + runOnUiThread { + subscribers.forEach { + it.shapeUpdated(shape) + } + } + } + + override fun start() { + if (timer != null) { + Log.d(LOG_TAG, "Timer for animator $tag is already running (subscribers: ${subscribers.count()})") + return + } + + Log.d(LOG_TAG, "Started timer for animator $tag (subscribers: ${subscribers.count()})") + + startedAt = Date() + timer = Timer() + timer?.schedule(object : TimerTask() { + override fun run() { + refresh() + } + }, 0, (period * 1000).toLong()) + } + + override fun stop() { + if (timer == null) { + Log.d(LOG_TAG, "Timer for animator $tag is already stopped (subscribers: ${subscribers.count()})") + return + } + + Log.d(LOG_TAG,"Stopped timer for animator $tag (subscribers: ${subscribers.count()})") + + timer?.cancel() + timer = null + } + + override fun getShape(): GeoJson { + return getAnimatedShape(getAnimatorAgeSec()) + } +} + +class ShapeAnimatorManager { + private val animators = hashMapOf(); + + fun add(animator: ShapeAnimator) { + animators[animator.tag] = animator + } + + fun isShapeAnimatorTag(shape: String): Boolean { + return shape.startsWith("{\"__nativeTag\":") + } + + fun get(tag: String): ShapeAnimator? { + return if (isShapeAnimatorTag(tag)) { + val obj = JSONObject(tag) + val _tag = obj.getLong("__nativeTag") + get(_tag); + } else { + null + } + } + + fun get(tag: Tag): ShapeAnimator? { + val result = animators[tag] + if (result == null) { + Logger.e(LOG_TAG, "Shape animator for tag $tag was not found") + } + return result + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rnmapbox/rnmbx/shape_animators/RNMBXMovePointShapeAnimatorModule.kt b/android/src/main/java/com/rnmapbox/rnmbx/shape_animators/RNMBXMovePointShapeAnimatorModule.kt deleted file mode 100644 index 2edfcb468..000000000 --- a/android/src/main/java/com/rnmapbox/rnmbx/shape_animators/RNMBXMovePointShapeAnimatorModule.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.rnmapbox.rnmbx.shape_animators - -import com.facebook.react.bridge.ReadableArray -import com.rnmapbox.rnmbx.NativeRNMBXMovePointShapeAnimatorModuleSpec -import com.rnmapbox.rnmbx.components.annotation.RNMBXPointAnnotation - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.module.annotations.ReactModule -import com.google.gson.JsonObject -import com.mapbox.geojson.GeoJson -import com.mapbox.geojson.Point -import com.rnmapbox.rnmbx.NativeRNMBXPointAnnotationModuleSpec -import com.rnmapbox.rnmbx.utils.Logger -import com.rnmapbox.rnmbx.utils.ViewTagResolver -import org.json.JSONObject -import java.util.Date -import java.util.Timer -import java.util.TimerTask -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.DurationUnit - - -/// Simple dummy animator that moves the point lng, lat by 0.01, 0.01 each second. -class MovePointShapeAnimator(tag: Tag, val lng: Double, val lat: Double) : ShapeAnimatorCommon(tag) { - override fun getAnimatedShape(timeSinceStart: Duration): Pair { - return Pair(Point.fromLngLat(lng + timeSinceStart.toDouble(DurationUnit.SECONDS) * 0.01, lat + timeSinceStart.toDouble(DurationUnit.SECONDS) * 0.01), true); - } -} - - -@ReactModule(name = RNMBXMovePointShapeAnimatorModule.NAME) -class RNMBXMovePointShapeAnimatorModule(reactContext: ReactApplicationContext?, val shapeAnimatorManager: ShapeAnimatorManager) : - NativeRNMBXMovePointShapeAnimatorModuleSpec(reactContext) { - - companion object { - const val LOG_TAG = "RNMBXMovePointShapeAnimatorModule" - const val NAME = "RNMBXMovePointShapeAnimatorModule" - } - - @ReactMethod - override fun start(tag: Double, promise: Promise?) { - shapeAnimatorManager?.get(tag.toLong())?.let { - it.start() - } - } - - @ReactMethod - override fun create(tag: Double, from: ReadableArray, promise: Promise) { - shapeAnimatorManager.add(MovePointShapeAnimator(tag.toLong(), from.getDouble(0), from.getDouble(1))) - promise.resolve(tag.toInt()) - } -} \ No newline at end of file diff --git a/android/src/main/java/com/rnmapbox/rnmbx/shape_animators/ShapeAnimatorCommon.kt b/android/src/main/java/com/rnmapbox/rnmbx/shape_animators/ShapeAnimatorCommon.kt deleted file mode 100644 index d9bb35caf..000000000 --- a/android/src/main/java/com/rnmapbox/rnmbx/shape_animators/ShapeAnimatorCommon.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.rnmapbox.rnmbx.shape_animators - -import com.facebook.react.bridge.UiThreadUtil.runOnUiThread -import com.mapbox.geojson.GeoJson -import com.rnmapbox.rnmbx.utils.Logger -import org.json.JSONObject -import java.util.Date -import java.util.Timer -import java.util.TimerTask -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds - -typealias Tag = Long - -interface ShapeAnimationConsumer { - fun shapeUpdated(geoJson: GeoJson) -} -abstract class ShapeAnimator(val tag: Tag) { - - abstract fun getShape(): GeoJson; - abstract fun start() - - abstract fun subscribe(consumer: ShapeAnimationConsumer) - abstract fun unsubscribe(consumer: ShapeAnimationConsumer) -} - -abstract class ShapeAnimatorCommon(tag: Tag): ShapeAnimator(tag) { - var timer: Timer? = null - var progress: Duration? = null - - // region subscribers - var subscribers = mutableListOf() - - override fun subscribe(consumer: ShapeAnimationConsumer) { - subscribers.add(consumer) - } - - override fun unsubscribe(consumer: ShapeAnimationConsumer) { - subscribers.remove(consumer) - } - // endregion - - override fun start() { - timer?.let { it.cancel() } - timer = null - - val fps = 30.0 - val period = (1000.0 / fps).toLong() - val start = Date() - val timer = Timer() - this.timer = timer - val animator = this - - timer.schedule(object : TimerTask() { - override fun run() { - - val now = Date() - val diff = now.time - start.time - val progress = diff.milliseconds - animator.progress = progress - - val (shape,doContinue) = getAnimatedShape(progress) - if (!doContinue) { - timer.cancel() - } - runOnUiThread { - subscribers.forEach { it.shapeUpdated(shape) } - } - } - },0, period) - } - - override fun getShape(): GeoJson { - return getAnimatedShape(progress ?: 0.0.milliseconds).first - } - abstract fun getAnimatedShape(timeSinceStart: Duration): Pair - -} -class ShapeAnimatorManager { - private val animators = hashMapOf(); - fun add(animator: ShapeAnimator) { - animators.put(animator.tag, animator) - } - - fun isShapeAnimatorTag(shape: String): Boolean { - return shape.startsWith("{\"__nativeTag\":") - } - - fun get(tag: String): ShapeAnimator? { - if (isShapeAnimatorTag(tag)) { - val obj = JSONObject(tag) - val tag = obj.getLong("__nativeTag") - return get(tag); - } - else { - return null - } - } - fun get(tag: Tag): ShapeAnimator? { - val result = animators[tag] - if (result == null) { - Logger.e(LOG_TAG, "Shape animator for tag: $tag was not found") - } - return result - } - - companion object { - const val LOG_TAG = "RNMBXShapeAnimators" - } -} \ No newline at end of file diff --git a/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec.java b/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec.java new file mode 100644 index 000000000..2e04b9b05 --- /dev/null +++ b/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec.java @@ -0,0 +1,52 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.rnmapbox.rnmbx; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReactModuleWithSpec; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import javax.annotation.Nonnull; + +public abstract class NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { + public static final String NAME = "RNMBXChangeLineOffsetsShapeAnimatorModule"; + + public NativeRNMBXChangeLineOffsetsShapeAnimatorModuleSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod + @DoNotStrip + public abstract void create(double tag, ReadableArray coordinates, double startOffset, double endOffset, Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void setLineString(double tag, ReadableArray coordinates, double startOffset, double endOffset, Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void setStartOffset(double tag, double offset, double duration, Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void setEndOffset(double tag, double offset, double duration, Promise promise); +} diff --git a/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXMovePointShapeAnimatorModuleSpec.java b/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXMovePointShapeAnimatorModuleSpec.java index 6a97a15c1..4b52b8b16 100644 --- a/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXMovePointShapeAnimatorModuleSpec.java +++ b/android/src/main/old-arch/com/rnmapbox/rnmbx/NativeRNMBXMovePointShapeAnimatorModuleSpec.java @@ -36,9 +36,9 @@ public NativeRNMBXMovePointShapeAnimatorModuleSpec(ReactApplicationContext react @ReactMethod @DoNotStrip - public abstract void create(double tag, ReadableArray from, Promise promise); + public abstract void create(double tag, ReadableArray coordinate, Promise promise); @ReactMethod @DoNotStrip - public abstract void start(double tag, Promise promise); + public abstract void moveTo(double tag, ReadableArray coordinate, double duration, Promise promise); } diff --git a/docs/ShapeSource.md b/docs/ShapeSource.md index 426541a9a..4953e6e70 100644 --- a/docs/ShapeSource.md +++ b/docs/ShapeSource.md @@ -49,6 +49,7 @@ An HTTP(S) URL, absolute file URL, or local file URL relative to the current app | GeoJSON.Feature | GeoJSON.FeatureCollection | GeoJSON.Geometry +| ShapeAnimatorInterface ``` The contents of the source. A shape can represent a GeoJSON geometry, a feature, or a feature collection. diff --git a/docs/docs.json b/docs/docs.json index 1e91b20dc..8336eee07 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -5203,7 +5203,7 @@ { "name": "shape", "required": false, - "type": "\\| GeoJSON.GeometryCollection\n\\| GeoJSON.Feature\n\\| GeoJSON.FeatureCollection\n\\| GeoJSON.Geometry", + "type": "\\| GeoJSON.GeometryCollection\n\\| GeoJSON.Feature\n\\| GeoJSON.FeatureCollection\n\\| GeoJSON.Geometry\n\\| ShapeAnimatorInterface", "default": "none", "description": "The contents of the source. A shape can represent a GeoJSON geometry, a feature, or a feature collection." }, diff --git a/example/.gitignore b/example/.gitignore index 04447790f..ddc6d2342 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -37,6 +37,7 @@ build/ local.properties *.iml *.hprof +.cxx # node.js # @@ -71,4 +72,4 @@ buck-out/ /vendor/bundle # detox -artifacts \ No newline at end of file +artifacts diff --git a/example/Gemfile.lock b/example/Gemfile.lock index b536e8c4f..10beaf8fc 100644 --- a/example/Gemfile.lock +++ b/example/Gemfile.lock @@ -1,30 +1,29 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml - activesupport (6.1.6) + activesupport (7.0.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) claide (1.1.0) - cocoapods (1.11.3) + cocoapods (1.14.3) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) + cocoapods-core (= 1.14.3) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -32,10 +31,10 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.14.3) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -45,7 +44,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -54,44 +53,44 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.15.5) + ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (1.10.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) - json (2.6.1) - minitest (5.15.0) + json (2.7.1) + minitest (5.20.0) molinillo (0.8.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) public_suffix (4.0.7) - rexml (3.2.5) + rexml (3.2.6) ruby-macho (2.5.1) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (2.0.4) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.21.0) + xcodeproj (1.23.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) - zeitwerk (2.6.0) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.11, >= 1.11.2) + activesupport (>= 6.1.7.3, < 7.1.0) + cocoapods (~> 1.13) RUBY VERSION ruby 2.7.4p191 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 5f956024d..85a10623a 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -72,12 +72,12 @@ PODS: - hermes-engine/Pre-built (= 0.73.0) - hermes-engine/Pre-built (0.73.0) - libevent (2.1.12) - - MapboxCommon (23.8.5) - - MapboxCoreMaps (10.16.3): + - MapboxCommon (23.8.6) + - MapboxCoreMaps (10.16.4): - MapboxCommon (~> 23.8) - - MapboxMaps (10.16.3): - - MapboxCommon (= 23.8.5) - - MapboxCoreMaps (= 10.16.3) + - MapboxMaps (10.16.4): + - MapboxCommon (= 23.8.6) + - MapboxCoreMaps (= 10.16.4) - MapboxMobileEvents (= 1.0.10) - Turf (~> 2.0) - MapboxMobileEvents (1.0.10) @@ -1122,16 +1122,16 @@ PODS: - React-jsi (= 0.73.0) - React-logger (= 0.73.0) - React-perflogger (= 0.73.0) - - RNCAsyncStorage (1.19.5): + - RNCAsyncStorage (1.18.1): - React-Core - - rnmapbox-maps (10.1.4): - - MapboxMaps (~> 10.16.3) + - rnmapbox-maps (10.1.10): + - MapboxMaps (~> 10.16.4) - React - React-Core - - rnmapbox-maps/DynamicLibrary (= 10.1.4) + - rnmapbox-maps/DynamicLibrary (= 10.1.10) - Turf - - rnmapbox-maps/DynamicLibrary (10.1.4): - - MapboxMaps (~> 10.16.3) + - rnmapbox-maps/DynamicLibrary (10.1.10): + - MapboxMaps (~> 10.16.4) - React - React-Core - Turf @@ -1373,9 +1373,9 @@ SPEC CHECKSUMS: glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 34304f8c6e8fa68f63a5fe29af82f227d817d7a7 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - MapboxCommon: 677dd4449e9f7d863a4712cf271c198ed1753494 - MapboxCoreMaps: 304d4ff1de0b5873e4ce359a7ed107fe65f89bd6 - MapboxMaps: 89322569ce8b0e98ed2a84c3289e6bc402df6073 + MapboxCommon: 90f76693dc02438acbb5ea9a5b266a4c0eb1c875 + MapboxCoreMaps: 7b07d1ca8c454a4381daf09df901b9d6bf90bce0 + MapboxMaps: cbb38845a9bf49b124f0e937975d560a4e01894e MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 @@ -1420,14 +1420,14 @@ SPEC CHECKSUMS: React-runtimescheduler: 77543c74df984ce56c09d49d427149c53784aaf6 React-utils: 42708ea436853045ef1eaff29996813d9fbbe209 ReactCommon: 851280fb976399ca1aabc74cc2c3612069ea70a2 - RNCAsyncStorage: f2974eca860c16a3e56eea5771fda8d12e2d2057 - rnmapbox-maps: dc170ff38f6d00ce2fa489d70cb56f933994fef7 + RNCAsyncStorage: b90b71f45b8b97be43bc4284e71a6af48ac9f547 + rnmapbox-maps: c8178f723f84ca1067dea8599cfe4aaf2f9d20aa RNScreens: b582cb834dc4133307562e930e8fa914b8c04ef2 RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 Yoga: 20d6a900dcc8d61d5e3b799bbf627cc34474a8c4 -PODFILE CHECKSUM: e8746449227564eeceb4b6f3c93c930f2707aa0c +PODFILE CHECKSUM: 769bf362d25d9fedd9a23e2f1351a14e6aa3d8e3 -COCOAPODS: 1.14.2 +COCOAPODS: 1.14.3 diff --git a/example/ios/RNMapboxGLExample.xcodeproj/project.pbxproj b/example/ios/RNMapboxGLExample.xcodeproj/project.pbxproj index 32685d210..e9f30c4a3 100644 --- a/example/ios/RNMapboxGLExample.xcodeproj/project.pbxproj +++ b/example/ios/RNMapboxGLExample.xcodeproj/project.pbxproj @@ -7,11 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 02C925EFAD51B3C2F1BD6FFF /* libPods-RNMapboxGLExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EDFCE83912B4B4844380179 /* libPods-RNMapboxGLExample.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 51C0260825301F99008C5283 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C0260725301F99008C5283 /* LaunchScreen.storyboard */; }; - 6ABB4B94377E61C4A5D7C20B /* libPods-RNMapboxGLExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B3043593E8F1F7D43624CAB9 /* libPods-RNMapboxGLExample.a */; }; D8E751FC2B4D755200FEFCF7 /* DebugWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E751FB2B4D755200FEFCF7 /* DebugWorkaround.swift */; }; /* End PBXBuildFile section */ @@ -19,6 +19,7 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RNMapboxGLExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNMapboxGLExampleTests.m; sourceTree = ""; }; + 02C51202128FAD8C8E0C3FDC /* Pods-RNMapboxGLExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNMapboxGLExample.debug.xcconfig"; path = "Target Support Files/Pods-RNMapboxGLExample/Pods-RNMapboxGLExample.debug.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* RNMapboxGLExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RNMapboxGLExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RNMapboxGLExample/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = RNMapboxGLExample/AppDelegate.mm; sourceTree = ""; }; @@ -28,20 +29,19 @@ 1DDF0404A5884CA6A9492067 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; }; 364D3AF88B4E4C1AA568F0FA /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; }; 425D02CB50EE42159354FD53 /* Fontisto.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Fontisto.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf"; sourceTree = ""; }; - 4AE883B360A98252C7CCAFE6 /* Pods-RNMapboxGLExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNMapboxGLExample.release.xcconfig"; path = "Target Support Files/Pods-RNMapboxGLExample/Pods-RNMapboxGLExample.release.xcconfig"; sourceTree = ""; }; + 4EDFCE83912B4B4844380179 /* libPods-RNMapboxGLExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNMapboxGLExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 51C0260725301F99008C5283 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RNMapboxGLExample/LaunchScreen.storyboard; sourceTree = ""; }; 534B49DAC74D44ECB3980682 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; }; 644BCBB2CD7C4ABEBD187990 /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = ""; }; 6FB4F6BA6DC54135A417A04D /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; 7AEE0617675F472CBDA34326 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = ""; }; 887ED0EFC3674C8D8CE33CCC /* FontAwesome5_Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Regular.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf"; sourceTree = ""; }; + 915E90F30C17B6320963B251 /* Pods-RNMapboxGLExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNMapboxGLExample.release.xcconfig"; path = "Target Support Files/Pods-RNMapboxGLExample/Pods-RNMapboxGLExample.release.xcconfig"; sourceTree = ""; }; 952CC82CA637483F9B2B90CF /* AntDesign.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = AntDesign.ttf; path = "../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"; sourceTree = ""; }; - B3043593E8F1F7D43624CAB9 /* libPods-RNMapboxGLExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNMapboxGLExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B43791892EEA40C9938DB0F7 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; }; B895CA6319C04392AB717662 /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; }; CB1F3BED505C4EDE92AC1770 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = ""; }; CC44ECBD0B2A438C84D89432 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = ""; }; - D21EDDEE520265816231288F /* Pods-RNMapboxGLExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNMapboxGLExample.debug.xcconfig"; path = "Target Support Files/Pods-RNMapboxGLExample/Pods-RNMapboxGLExample.debug.xcconfig"; sourceTree = ""; }; D8E751FB2B4D755200FEFCF7 /* DebugWorkaround.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DebugWorkaround.swift; path = RNMapboxGLExample/DebugWorkaround.swift; sourceTree = ""; }; E164F01DAD324AE386612513 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -55,7 +55,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6ABB4B94377E61C4A5D7C20B /* libPods-RNMapboxGLExample.a in Frameworks */, + 02C925EFAD51B3C2F1BD6FFF /* libPods-RNMapboxGLExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -122,7 +122,7 @@ children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - B3043593E8F1F7D43624CAB9 /* libPods-RNMapboxGLExample.a */, + 4EDFCE83912B4B4844380179 /* libPods-RNMapboxGLExample.a */, ); name = Frameworks; sourceTree = ""; @@ -161,8 +161,8 @@ EE5EDD88BB42C11717A56BB9 /* Pods */ = { isa = PBXGroup; children = ( - D21EDDEE520265816231288F /* Pods-RNMapboxGLExample.debug.xcconfig */, - 4AE883B360A98252C7CCAFE6 /* Pods-RNMapboxGLExample.release.xcconfig */, + 02C51202128FAD8C8E0C3FDC /* Pods-RNMapboxGLExample.debug.xcconfig */, + 915E90F30C17B6320963B251 /* Pods-RNMapboxGLExample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -174,14 +174,14 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RNMapboxGLExample" */; buildPhases = ( - 69BB783634C881B7C089B276 /* [CP] Check Pods Manifest.lock */, + 75708B0E99F7F24530A6A329 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 6176D4192CF74C4F87C79F25 /* [CP] Embed Pods Frameworks */, - FD20406EBE62637D019BD0CD /* [CP] Copy Pods Resources */, + 4B8787C0AADDA481368669FB /* [CP] Embed Pods Frameworks */, + B1988138808B9A6AA7D886EF /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -249,7 +249,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 6176D4192CF74C4F87C79F25 /* [CP] Embed Pods Frameworks */ = { + 4B8787C0AADDA481368669FB /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -283,7 +283,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNMapboxGLExample/Pods-RNMapboxGLExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 69BB783634C881B7C089B276 /* [CP] Check Pods Manifest.lock */ = { + 75708B0E99F7F24530A6A329 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -305,26 +305,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - FD10A7F022414F080027D42C /* Start Packager */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Start Packager"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; - showEnvVarsInLog = 0; - }; - FD20406EBE62637D019BD0CD /* [CP] Copy Pods Resources */ = { + B1988138808B9A6AA7D886EF /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -347,7 +328,7 @@ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -367,13 +348,32 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RNMapboxGLExample/Pods-RNMapboxGLExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; + FD10A7F022414F080027D42C /* Start Packager */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Start Packager"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -392,7 +392,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D21EDDEE520265816231288F /* Pods-RNMapboxGLExample.debug.xcconfig */; + baseConfigurationReference = 02C51202128FAD8C8E0C3FDC /* Pods-RNMapboxGLExample.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -458,14 +458,13 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = RNMapboxGLExample; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4AE883B360A98252C7CCAFE6 /* Pods-RNMapboxGLExample.release.xcconfig */; + baseConfigurationReference = 915E90F30C17B6320963B251 /* Pods-RNMapboxGLExample.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -519,7 +518,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = RNMapboxGLExample; - SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -583,8 +581,15 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + USE_HERMES = true; }; name = Debug; }; @@ -639,8 +644,15 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-Wl", + "-ld_classic", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + USE_HERMES = true; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/example/package.json b/example/package.json index 12b4b3cb8..1c7004f45 100644 --- a/example/package.json +++ b/example/package.json @@ -36,7 +36,7 @@ "prop-types": "^15.7.2", "react": "18.2.0", "react-native": "0.73.0", - "react-native-safe-area-context": "4.8.0", + "react-native-safe-area-context": "4.8.2", "react-native-screens": "3.29.0", "react-native-vector-icons": "9.2.0", "@rnmapbox/maps": "link:../" diff --git a/example/src/examples/Animations/AnimatedLineOffsets.tsx b/example/src/examples/Animations/AnimatedLineOffsets.tsx new file mode 100644 index 000000000..72112c080 --- /dev/null +++ b/example/src/examples/Animations/AnimatedLineOffsets.tsx @@ -0,0 +1,253 @@ +import { Button, StyleProp, View, ViewStyle } from 'react-native'; +import { + Camera, + Logger, + MapView, + ShapeSource, + __experimental, + LineLayer, +} from '@rnmapbox/maps'; +import { Position } from 'geojson'; +import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; +import { Divider, Slider, Text } from '@rneui/base'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { lineString } from '@turf/helpers'; +import bbox from '@turf/bbox'; +import length from '@turf/length'; + +Logger.setLogLevel('verbose'); + +const baseCoordinates: Position[] = [ + [-83.53808787278204, 41.66430343748789], + [-83.53358035756604, 41.66640799713619], + [-83.52969888612948, 41.66177787510651], + [-83.51648936237063, 41.66809159532477], + [-83.51467383540873, 41.66706273499676], + [-83.51436081351842, 41.66519203773046], + [-83.50891423263205, 41.657147420131764], + [-83.51104278148429, 41.65625870887462], + [-83.51805447182102, 41.65186174645257], + [-83.51630154923672, 41.65017772390942], + [-83.51592592296878, 41.645733564160906], + [-83.51110538586248, 41.64582712857808], + [-83.51104278148429, 41.64353476124876], +]; + +const maxDuration = 5000; + +const randNorm = () => Math.random() - 0.5; + +const AnimatedLineOffsets = memo(() => { + const coordinates = useRef(baseCoordinates); + const startOffset = useRef(0); + const endOffset = useRef(0); + const duration = useRef(1000); + + const [startOffsetState, setStartOffsetState] = useState(startOffset.current); + const [endOffsetState, setEndOffsetState] = useState(endOffset.current); + const [durationState, setDurationState] = useState(duration.current); + + const animator = useMemo(() => { + return new __experimental.ChangeLineOffsetsShapeAnimator({ + coordinates: coordinates.current, + startOffset: startOffset.current, + endOffset: endOffset.current, + }); + }, []); + + const lineLength = useMemo(() => { + return length(lineString(coordinates.current), { units: 'meters' }); + }, []); + + const bounds = useMemo(() => { + const boundingBox = bbox(lineString(coordinates.current)); + return { + ne: [boundingBox[0], boundingBox[1]], + sw: [boundingBox[2], boundingBox[3]], + paddingTop: 20, + paddingBottom: 320, + paddingLeft: 20, + paddingRight: 20, + }; + }, []); + + const buildRandomizedLine = useCallback(() => { + return baseCoordinates.map((c) => { + return [c[0] + randNorm() * 0.001, c[1] + randNorm() * 0.001]; + }); + }, []); + + const contents = useMemo(() => { + const rowStyle: StyleProp = { + flex: 0, + flexDirection: 'row', + justifyContent: 'space-between', + }; + + const sliderProps = { + thumbTintColor: 'black', + thumbStyle: { width: 10, height: 10 }, + }; + + return ( + + + {'Randomize Line'} + +