diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 8cd5e3dc..13071d8d 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -112,6 +112,7 @@ def kotlin_version = getExtOrDefault('kotlinVersion', project.properties['lottie dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules diff --git a/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewManagerImpl.kt b/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewManagerImpl.kt index 48395d30..8a79ea30 100644 --- a/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewManagerImpl.kt +++ b/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewManagerImpl.kt @@ -2,6 +2,7 @@ package com.airbnb.android.react.lottie import android.os.Handler import android.os.Looper +import android.util.Log import android.view.View import android.view.View.OnAttachStateChangeListener import android.widget.ImageView @@ -11,6 +12,8 @@ import com.airbnb.lottie.RenderMode import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.MapBuilder import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.util.RNLog +import kotlinx.coroutines.* import java.io.BufferedReader import java.io.InputStreamReader import java.net.URL @@ -18,6 +21,7 @@ import kotlin.concurrent.thread internal object LottieAnimationViewManagerImpl { const val REACT_CLASS = "LottieAnimationView" + val coroutineScope = CoroutineScope(Dispatchers.Main) @JvmStatic val exportedViewConstants: Map @@ -135,12 +139,19 @@ internal object LottieAnimationViewManagerImpl { urlString: String?, propManagersMap: LottieAnimationViewPropertyManager ) { - thread { - BufferedReader(InputStreamReader(URL(urlString).openStream())).useLines { - Handler(Looper.getMainLooper()).post { - propManagersMap.animationJson = it.toString() - propManagersMap.commitChanges() + CoroutineScope(Dispatchers.Main).launch { + try { + val jsonString = withContext(Dispatchers.IO) { + URL(urlString).openStream().use { + BufferedReader(InputStreamReader(it)).useLines { lines -> + lines.joinToString("\n") + } + } } + propManagersMap.animationJson = jsonString + propManagersMap.commitChanges() + } catch (e: Exception) { + RNLog.l("Error while loading animation from URL") } } } @@ -158,7 +169,7 @@ internal object LottieAnimationViewManagerImpl { var mode: ImageView.ScaleType? = null when (resizeMode) { "cover" -> { - mode = ImageView.ScaleType.CENTER_CROP + mode = ImageView.ScaleType.FIT_XY } "contain" -> { mode = ImageView.ScaleType.CENTER_INSIDE @@ -226,6 +237,14 @@ internal object LottieAnimationViewManagerImpl { viewManager.loop = loop } + @JvmStatic + fun setAutoPlay( + autoPlay: Boolean, + viewManager: LottieAnimationViewPropertyManager + ) { + viewManager.autoPlay = autoPlay + } + @JvmStatic fun setEnableMergePaths( enableMergePaths: Boolean, diff --git a/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewPropertyManager.kt b/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewPropertyManager.kt index ec17817e..0bc89923 100644 --- a/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewPropertyManager.kt +++ b/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottieAnimationViewPropertyManager.kt @@ -1,28 +1,28 @@ package com.airbnb.android.react.lottie -import com.airbnb.lottie.LottieAnimationView -import java.lang.ref.WeakReference +import android.graphics.ColorFilter import android.widget.ImageView -import com.facebook.react.bridge.ReadableArray -import com.airbnb.lottie.RenderMode -import com.airbnb.lottie.TextDelegate +import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieDrawable -import com.facebook.react.bridge.ReadableType -import com.facebook.react.bridge.ColorPropConverter -import android.graphics.ColorFilter +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.RenderMode import com.airbnb.lottie.SimpleColorFilter +import com.airbnb.lottie.TextDelegate import com.airbnb.lottie.model.KeyPath import com.airbnb.lottie.value.LottieValueCallback -import com.airbnb.lottie.LottieProperty +import com.facebook.react.bridge.ColorPropConverter +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableType +import java.lang.ref.WeakReference import java.util.regex.Pattern /** - * Class responsible for applying the properties to the LottieView. - * The way react-native works makes it impossible to predict in which order properties will be set, - * also some of the properties of the LottieView needs to be set simultaneously. + * Class responsible for applying the properties to the LottieView. The way react-native works makes + * it impossible to predict in which order properties will be set, also some of the properties of + * the LottieView needs to be set simultaneously. * - * To solve this, instance of this class accumulates all changes to the view and applies them at - * the end of react transaction, so it could control how changes are applied. + * To solve this, instance of this class accumulates all changes to the view and applies them at the + * end of react transaction, so it could control how changes are applied. */ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { private val viewWeakReference: WeakReference @@ -48,6 +48,7 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { var animationJson: String? = null var progress: Float? = null var loop: Boolean? = null + var autoPlay: Boolean? = null var speed: Float? = null init { @@ -55,19 +56,18 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { } /** - * Updates the view with changed fields. - * Majority of the properties here are independent so they are has to be reset to null - * as soon as view is updated with the value. + * Updates the view with changed fields. Majority of the properties here are independent so they + * are has to be reset to null as soon as view is updated with the value. * - * The only exception from this rule is the group of the properties for the animation. - * For now this is animationName and cacheStrategy. These two properties are should be set + * The only exception from this rule is the group of the properties for the animation. For now + * this is animationName and cacheStrategy. These two properties are should be set * simultaneously if the dirty flag is set. */ fun commitChanges() { val view = viewWeakReference.get() ?: return textFilters?.let { - if(it.size() > 0) { + if (it.size() > 0) { val textDelegate = TextDelegate(view) for (i in 0 until textFilters!!.size()) { val current = textFilters!!.getMap(i) @@ -99,6 +99,13 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { loop = null } + autoPlay?.let { + if (it && !view.isAnimating) { + view.playAnimation() + } + autoPlay = null + } + speed?.let { view.speed = it speed = null @@ -114,9 +121,7 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { renderMode = null } - layerType?.let { - view.setLayerType(it, null) - } + layerType?.let { view.setLayerType(it, null) } imageAssetsFolder?.let { view.imageAssetsFolder = it @@ -133,11 +138,12 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { for (i in 0 until it.size()) { val current = it.getMap(i) - val color: Int = if (current.getType("color") == ReadableType.Map) { - ColorPropConverter.getColor(current.getMap("color"), view.context) - } else { - current.getInt("color") - } + val color: Int = + if (current.getType("color") == ReadableType.Map) { + ColorPropConverter.getColor(current.getMap("color"), view.context) + } else { + current.getInt("color") + } val path = current.getString("keypath") val pathWithGlobStar = "$path.**" @@ -153,4 +159,4 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) { } } } -} \ No newline at end of file +} diff --git a/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottiePackage.kt b/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottiePackage.kt index b6ed7239..3231692a 100644 --- a/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottiePackage.kt +++ b/packages/core/android/src/main/java/com/airbnb/android/react/lottie/LottiePackage.kt @@ -13,6 +13,6 @@ class LottiePackage : ReactPackage { } override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return listOf(LottieAnimationViewManager()) + return listOf(LottieAnimationViewManager(reactContext)) } } \ No newline at end of file diff --git a/packages/core/android/src/newarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt b/packages/core/android/src/newarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt index 6949ae7c..1bf76753 100644 --- a/packages/core/android/src/newarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt +++ b/packages/core/android/src/newarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt @@ -1,6 +1,7 @@ package com.airbnb.android.react.lottie import android.animation.Animator +import android.util.Log import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setColorFilters import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setEnableMergePaths import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setHardwareAcceleration @@ -14,22 +15,28 @@ import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setSourceN import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setSourceURL import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setSpeed import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setTextFilters +import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setAutoPlay import com.airbnb.lottie.LottieAnimationView import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap; import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter import com.facebook.react.uimanager.events.RCTModernEventEmitter import com.facebook.react.viewmanagers.LottieAnimationViewManagerDelegate import com.facebook.react.viewmanagers.LottieAnimationViewManagerInterface import java.util.* @ReactModule(name = LottieAnimationViewManagerImpl.REACT_CLASS) -class LottieAnimationViewManager : SimpleViewManager(), +class LottieAnimationViewManager(val reactContext: ReactContext) : + SimpleViewManager(), LottieAnimationViewManagerInterface { private val propManagersMap = WeakHashMap() @@ -52,16 +59,16 @@ class LottieAnimationViewManager : SimpleViewManager(), val event = Arguments.createMap() event.putBoolean("isCancelled", isCancelled) - val screenContext = view.context - if (screenContext is ThemedReactContext) { - screenContext.getJSModule(RCTModernEventEmitter::class.java) - ?.receiveEvent( - screenContext.surfaceId, - view.id, - "animationFinish", - event - ) - } + val screenContext = view.context as ThemedReactContext + + Log.d("Lottie", "view surface id ${screenContext.surfaceId} - view id ${view.id}") + reactContext.getJSModule(RCTModernEventEmitter::class.java) + ?.receiveEvent( + screenContext.surfaceId, + view.id, + "animationFinish", + event + ) } override fun getDelegate(): ViewManagerDelegate { @@ -84,11 +91,13 @@ class LottieAnimationViewManager : SimpleViewManager(), } override fun onAnimationEnd(animation: Animator) { - sendOnAnimationFinishEvent(view, false) +// TODO: fix crash +// sendOnAnimationFinishEvent(view, false) } override fun onAnimationCancel(animation: Animator) { - sendOnAnimationFinishEvent(view, true) +// TODO: fix crash +// sendOnAnimationFinishEvent(view, true) } override fun onAnimationRepeat(animation: Animator) { @@ -176,6 +185,11 @@ class LottieAnimationViewManager : SimpleViewManager(), setLoop(loop, getOrCreatePropertyManager(view)) } + @ReactProp(name = "autoPlay") + override fun setAutoPlay(view: LottieAnimationView, autoPlay: Boolean) { + setAutoPlay(autoPlay, getOrCreatePropertyManager(view)) + } + @ReactProp(name = "imageAssetsFolder") override fun setImageAssetsFolder(view: LottieAnimationView, imageAssetsFolder: String?) { setImageAssetsFolder(imageAssetsFolder, getOrCreatePropertyManager(view)) diff --git a/packages/core/android/src/oldarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt b/packages/core/android/src/oldarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt index f3934b8e..a19bf4bc 100644 --- a/packages/core/android/src/oldarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt +++ b/packages/core/android/src/oldarch/com/airbnb/android/react/lottie/LottieAnimationViewManager.kt @@ -7,6 +7,7 @@ import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.pause import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.play import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.reset import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.resume +import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setAutoPlay import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setColorFilters import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setEnableMergePaths import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setHardwareAcceleration @@ -31,9 +32,11 @@ import java.util.* class LottieAnimationViewManager : SimpleViewManager() { private val propManagersMap = - WeakHashMap() + WeakHashMap() - private fun getOrCreatePropertyManager(view: LottieAnimationView): LottieAnimationViewPropertyManager { + private fun getOrCreatePropertyManager( + view: LottieAnimationView + ): LottieAnimationViewPropertyManager { var result = propManagersMap[view] if (result == null) { result = LottieAnimationViewPropertyManager(view) @@ -48,12 +51,9 @@ class LottieAnimationViewManager : SimpleViewManager() { val screenContext = view.context if (screenContext is ThemedReactContext) { - screenContext.getJSModule(RCTEventEmitter::class.java) - ?.receiveEvent( - view.id, - "animationFinish", - event - ) + screenContext + .getJSModule(RCTEventEmitter::class.java) + ?.receiveEvent(view.id, "animationFinish", event) } } @@ -67,23 +67,25 @@ class LottieAnimationViewManager : SimpleViewManager() { public override fun createViewInstance(context: ThemedReactContext): LottieAnimationView { val view = LottieAnimationViewManagerImpl.createViewInstance(context) - view.addAnimatorListener(object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) { - //do nothing - } - - override fun onAnimationEnd(animation: Animator) { - sendOnAnimationFinishEvent(view, false) - } - - override fun onAnimationCancel(animation: Animator) { - sendOnAnimationFinishEvent(view, true) - } - - override fun onAnimationRepeat(animation: Animator) { - //do nothing - } - }) + view.addAnimatorListener( + object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) { + // do nothing + } + + override fun onAnimationEnd(animation: Animator) { + sendOnAnimationFinishEvent(view, false) + } + + override fun onAnimationCancel(animation: Animator) { + sendOnAnimationFinishEvent(view, true) + } + + override fun onAnimationRepeat(animation: Animator) { + // do nothing + } + } + ) return view } @@ -92,9 +94,9 @@ class LottieAnimationViewManager : SimpleViewManager() { } override fun receiveCommand( - view: LottieAnimationView, - commandName: String, - args: ReadableArray? + view: LottieAnimationView, + commandName: String, + args: ReadableArray? ) { when (commandName) { "play" -> play(view, args?.getInt(0) ?: -1, args?.getInt(1) ?: -1) @@ -102,7 +104,7 @@ class LottieAnimationViewManager : SimpleViewManager() { "pause" -> pause(view) "resume" -> resume(view) else -> { - //do nothing + // do nothing } } } @@ -139,8 +141,8 @@ class LottieAnimationViewManager : SimpleViewManager() { @ReactProp(name = "hardwareAccelerationAndroid") fun setHardwareAccelerationAndroid( - view: LottieAnimationView, - hardwareAccelerationAndroid: Boolean? + view: LottieAnimationView, + hardwareAccelerationAndroid: Boolean? ) { setHardwareAcceleration(hardwareAccelerationAndroid!!, getOrCreatePropertyManager(view)) } @@ -160,6 +162,11 @@ class LottieAnimationViewManager : SimpleViewManager() { setLoop(loop, getOrCreatePropertyManager(view)) } + @ReactProp(name = "autoPlay") + fun setAutoPlay(view: LottieAnimationView, autoPlay: Boolean) { + setAutoPlay(autoPlay, getOrCreatePropertyManager(view)) + } + @ReactProp(name = "imageAssetsFolder") fun setImageAssetsFolder(view: LottieAnimationView, imageAssetsFolder: String?) { setImageAssetsFolder(imageAssetsFolder, getOrCreatePropertyManager(view)) @@ -184,4 +191,4 @@ class LottieAnimationViewManager : SimpleViewManager() { super.onAfterUpdateTransaction(view) getOrCreatePropertyManager(view).commitChanges() } -} \ No newline at end of file +}