diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Headers/Lottie-Swift.h b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Headers/Lottie-Swift.h deleted file mode 100644 index 446bdb226e5778469f99d49b223e7386cb822baf..0000000000000000000000000000000000000000 --- a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Headers/Lottie-Swift.h +++ /dev/null @@ -1,744 +0,0 @@ -// Generated by Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30) -#ifndef LOTTIE_SWIFT_H -#define LOTTIE_SWIFT_H -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgcc-compat" - -#if !defined(__has_include) -# define __has_include(x) 0 -#endif -#if !defined(__has_attribute) -# define __has_attribute(x) 0 -#endif -#if !defined(__has_feature) -# define __has_feature(x) 0 -#endif -#if !defined(__has_warning) -# define __has_warning(x) 0 -#endif - -#if __has_include() -# include -#endif - -#pragma clang diagnostic ignored "-Wauto-import" -#include -#include -#include -#include - -#if !defined(SWIFT_TYPEDEFS) -# define SWIFT_TYPEDEFS 1 -# if __has_include() -# include -# elif !defined(__cplusplus) -typedef uint_least16_t char16_t; -typedef uint_least32_t char32_t; -# endif -typedef float swift_float2 __attribute__((__ext_vector_type__(2))); -typedef float swift_float3 __attribute__((__ext_vector_type__(3))); -typedef float swift_float4 __attribute__((__ext_vector_type__(4))); -typedef double swift_double2 __attribute__((__ext_vector_type__(2))); -typedef double swift_double3 __attribute__((__ext_vector_type__(3))); -typedef double swift_double4 __attribute__((__ext_vector_type__(4))); -typedef int swift_int2 __attribute__((__ext_vector_type__(2))); -typedef int swift_int3 __attribute__((__ext_vector_type__(3))); -typedef int swift_int4 __attribute__((__ext_vector_type__(4))); -typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); -typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); -typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); -#endif - -#if !defined(SWIFT_PASTE) -# define SWIFT_PASTE_HELPER(x, y) x##y -# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) -#endif -#if !defined(SWIFT_METATYPE) -# define SWIFT_METATYPE(X) Class -#endif -#if !defined(SWIFT_CLASS_PROPERTY) -# if __has_feature(objc_class_property) -# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ -# else -# define SWIFT_CLASS_PROPERTY(...) -# endif -#endif - -#if __has_attribute(objc_runtime_name) -# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) -#else -# define SWIFT_RUNTIME_NAME(X) -#endif -#if __has_attribute(swift_name) -# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) -#else -# define SWIFT_COMPILE_NAME(X) -#endif -#if __has_attribute(objc_method_family) -# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) -#else -# define SWIFT_METHOD_FAMILY(X) -#endif -#if __has_attribute(noescape) -# define SWIFT_NOESCAPE __attribute__((noescape)) -#else -# define SWIFT_NOESCAPE -#endif -#if __has_attribute(ns_consumed) -# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) -#else -# define SWIFT_RELEASES_ARGUMENT -#endif -#if __has_attribute(warn_unused_result) -# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -# define SWIFT_WARN_UNUSED_RESULT -#endif -#if __has_attribute(noreturn) -# define SWIFT_NORETURN __attribute__((noreturn)) -#else -# define SWIFT_NORETURN -#endif -#if !defined(SWIFT_CLASS_EXTRA) -# define SWIFT_CLASS_EXTRA -#endif -#if !defined(SWIFT_PROTOCOL_EXTRA) -# define SWIFT_PROTOCOL_EXTRA -#endif -#if !defined(SWIFT_ENUM_EXTRA) -# define SWIFT_ENUM_EXTRA -#endif -#if !defined(SWIFT_CLASS) -# if __has_attribute(objc_subclassing_restricted) -# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA -# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA -# else -# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA -# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA -# endif -#endif -#if !defined(SWIFT_RESILIENT_CLASS) -# if __has_attribute(objc_class_stub) -# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) -# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) -# else -# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) -# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) -# endif -#endif - -#if !defined(SWIFT_PROTOCOL) -# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA -# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA -#endif - -#if !defined(SWIFT_EXTENSION) -# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) -#endif - -#if !defined(OBJC_DESIGNATED_INITIALIZER) -# if __has_attribute(objc_designated_initializer) -# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -# else -# define OBJC_DESIGNATED_INITIALIZER -# endif -#endif -#if !defined(SWIFT_ENUM_ATTR) -# if defined(__has_attribute) && __has_attribute(enum_extensibility) -# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) -# else -# define SWIFT_ENUM_ATTR(_extensibility) -# endif -#endif -#if !defined(SWIFT_ENUM) -# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type -# if __has_feature(generalized_swift_name) -# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type -# else -# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) -# endif -#endif -#if !defined(SWIFT_UNAVAILABLE) -# define SWIFT_UNAVAILABLE __attribute__((unavailable)) -#endif -#if !defined(SWIFT_UNAVAILABLE_MSG) -# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) -#endif -#if !defined(SWIFT_AVAILABILITY) -# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) -#endif -#if !defined(SWIFT_WEAK_IMPORT) -# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) -#endif -#if !defined(SWIFT_DEPRECATED) -# define SWIFT_DEPRECATED __attribute__((deprecated)) -#endif -#if !defined(SWIFT_DEPRECATED_MSG) -# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) -#endif -#if __has_feature(attribute_diagnose_if_objc) -# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) -#else -# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) -#endif -#if !defined(IBSegueAction) -# define IBSegueAction -#endif -#if __has_feature(modules) -#if __has_warning("-Watimport-in-framework-header") -#pragma clang diagnostic ignored "-Watimport-in-framework-header" -#endif -@import CoreGraphics; -@import Foundation; -@import ObjectiveC; -@import QuartzCore; -@import UIKit; -#endif - -#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" -#pragma clang diagnostic ignored "-Wduplicate-method-arg" -#if __has_warning("-Wpragma-clang-attribute") -# pragma clang diagnostic ignored "-Wpragma-clang-attribute" -#endif -#pragma clang diagnostic ignored "-Wunknown-pragmas" -#pragma clang diagnostic ignored "-Wnullability" - -#if __has_attribute(external_source_symbol) -# pragma push_macro("any") -# undef any -# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="Lottie",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) -# pragma pop_macro("any") -#endif - -@class NSCoder; -@class NSNumber; -@class UITouch; -@class UIEvent; - -/// Lottie comes prepacked with a two Animated Controls, AnimatedSwitch and -/// AnimatedButton. Both of these controls are built on top of AnimatedControl -/// AnimatedControl is a subclass of UIControl that provides an interactive -/// mechanism for controlling the visual state of an animation in response to -/// user actions. -/// The AnimatedControl will show and hide layers depending on the current -/// UIControl.State of the control. -/// Users of AnimationControl can set a Layer Name for each UIControl.State. -/// When the state is change the AnimationControl will change the visibility -/// of its layers. -/// NOTE: Do not initialize directly. This is intended to be subclassed. -SWIFT_CLASS("_TtC6Lottie15AnimatedControl") -@interface AnimatedControl : UIControl -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER; -@property (nonatomic, getter=isEnabled) BOOL enabled; -@property (nonatomic, getter=isSelected) BOOL selected; -@property (nonatomic, getter=isHighlighted) BOOL highlighted; -@property (nonatomic, readonly) CGSize intrinsicContentSize; -- (BOOL)beginTrackingWithTouch:(UITouch * _Nonnull)touch withEvent:(UIEvent * _Nullable)event SWIFT_WARN_UNUSED_RESULT; -- (BOOL)continueTrackingWithTouch:(UITouch * _Nonnull)touch withEvent:(UIEvent * _Nullable)event SWIFT_WARN_UNUSED_RESULT; -- (void)endTrackingWithTouch:(UITouch * _Nullable)touch withEvent:(UIEvent * _Nullable)event; -- (void)cancelTrackingWithEvent:(UIEvent * _Nullable)event; -- (nonnull instancetype)initWithFrame:(CGRect)frame SWIFT_UNAVAILABLE; -@end - - -/// An interactive button that plays an animation when pressed. -SWIFT_CLASS("_TtC6Lottie14AnimatedButton") -@interface AnimatedButton : AnimatedControl -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER; -- (BOOL)beginTrackingWithTouch:(UITouch * _Nonnull)touch withEvent:(UIEvent * _Nullable)event SWIFT_WARN_UNUSED_RESULT; -- (void)endTrackingWithTouch:(UITouch * _Nullable)touch withEvent:(UIEvent * _Nullable)event; -@property (nonatomic) UIAccessibilityTraits accessibilityTraits; -@end - - - -/// An interactive switch with an ‘On’ and ‘Off’ state. When the user taps on the -/// switch the state is toggled and the appropriate animation is played. -/// Both the ‘On’ and ‘Off’ have an animation play range associated with their state. -SWIFT_CLASS("_TtC6Lottie14AnimatedSwitch") -@interface AnimatedSwitch : AnimatedControl -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER; -- (void)endTrackingWithTouch:(UITouch * _Nullable)touch withEvent:(UIEvent * _Nullable)event; -@property (nonatomic) UIAccessibilityTraits accessibilityTraits; -@end - -@class CAAnimation; - -SWIFT_CLASS("_TtC6Lottie27AnimationCompletionDelegate") -@interface AnimationCompletionDelegate : NSObject -- (void)animationDidStop:(CAAnimation * _Nonnull)anim finished:(BOOL)flag; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -/// A view that can be added to a keypath of an AnimationView -SWIFT_CLASS("_TtC6Lottie16AnimationSubview") -@interface AnimationSubview : UIView -- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; -@end - - -/// A base CALayer that manages the frame and animations -/// of its sublayers and mask -SWIFT_CLASS("_TtC6Lottie18BaseAnimationLayer") -@interface BaseAnimationLayer : CALayer -- (void)layoutSublayers; -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; -@end - - -/// The base type of AnimationLayer that can contain other AnimationLayers -SWIFT_CLASS("_TtC6Lottie20BaseCompositionLayer") -@interface BaseCompositionLayer : BaseAnimationLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - - - - - - - - - - - - - - - - - - - - - - -@class NSString; -@class NSBundle; - -/// An Objective-C compatible wrapper around Lottie’s Animation class. -/// Use in tandem with CompatibleAnimationView when using Lottie in Objective-C -SWIFT_CLASS("_TtC6Lottie19CompatibleAnimation") -@interface CompatibleAnimation : NSObject -- (nonnull instancetype)initWithName:(NSString * _Nonnull)name bundle:(NSBundle * _Nonnull)bundle OBJC_DESIGNATED_INITIALIZER; -+ (CompatibleAnimation * _Nonnull)named:(NSString * _Nonnull)name SWIFT_WARN_UNUSED_RESULT; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -/// An Objective-C compatible wrapper around Lottie’s AnimationKeypath -SWIFT_CLASS("_TtC6Lottie26CompatibleAnimationKeypath") -@interface CompatibleAnimationKeypath : NSObject -/// Creates a keypath from a dot separated string. The string is separated by “.” -- (nonnull instancetype)initWithKeypath:(NSString * _Nonnull)keypath OBJC_DESIGNATED_INITIALIZER; -/// Creates a keypath from a list of strings. -- (nonnull instancetype)initWithKeys:(NSArray * _Nonnull)keys OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - -@class UIColor; - -/// An Objective-C compatible wrapper around Lottie’s LottieAnimationView. -SWIFT_CLASS("_TtC6Lottie23CompatibleAnimationView") -@interface CompatibleAnimationView : UIView -- (nonnull instancetype)initWithCompatibleAnimation:(CompatibleAnimation * _Nonnull)compatibleAnimation OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -@property (nonatomic, strong) CompatibleAnimation * _Nullable compatibleAnimation; -@property (nonatomic) CGFloat loopAnimationCount; -@property (nonatomic) UIViewContentMode contentMode; -@property (nonatomic) BOOL shouldRasterizeWhenIdle; -@property (nonatomic) CGFloat currentProgress; -@property (nonatomic) NSTimeInterval currentTime; -@property (nonatomic) CGFloat currentFrame; -@property (nonatomic, readonly) CGFloat realtimeAnimationFrame; -@property (nonatomic, readonly) CGFloat realtimeAnimationProgress; -@property (nonatomic) CGFloat animationSpeed; -@property (nonatomic) BOOL respectAnimationFrameRate; -@property (nonatomic, readonly) BOOL isAnimationPlaying; -- (void)play; -- (void)playWithCompletion:(void (^ _Nullable)(BOOL))completion; -- (void)playFromProgress:(CGFloat)fromProgress toProgress:(CGFloat)toProgress completion:(void (^ _Nullable)(BOOL))completion; -- (void)playFromFrame:(CGFloat)fromFrame toFrame:(CGFloat)toFrame completion:(void (^ _Nullable)(BOOL))completion; -- (void)playFromMarker:(NSString * _Nonnull)fromMarker toMarker:(NSString * _Nonnull)toMarker completion:(void (^ _Nullable)(BOOL))completion; -- (void)playWithMarker:(NSString * _Nonnull)marker completion:(void (^ _Nullable)(BOOL))completion; -- (void)stop; -- (void)pause; -- (void)reloadImages; -- (void)forceDisplayUpdate; -- (id _Nullable)getValueFor:(CompatibleAnimationKeypath * _Nonnull)keypath atFrame:(CGFloat)atFrame SWIFT_WARN_UNUSED_RESULT; -- (void)logHierarchyKeypaths; -- (void)setColorValue:(UIColor * _Nonnull)color forKeypath:(CompatibleAnimationKeypath * _Nonnull)keypath; -- (UIColor * _Nullable)getColorValueFor:(CompatibleAnimationKeypath * _Nonnull)keypath atFrame:(CGFloat)atFrame SWIFT_WARN_UNUSED_RESULT; -- (void)addSubview:(AnimationSubview * _Nonnull)subview forLayerAt:(CompatibleAnimationKeypath * _Nonnull)keypath; -- (CGRect)convertWithRect:(CGRect)rect toLayerAt:(CompatibleAnimationKeypath * _Nullable)keypath SWIFT_WARN_UNUSED_RESULT; -- (CGPoint)convertWithPoint:(CGPoint)point toLayerAt:(CompatibleAnimationKeypath * _Nullable)keypath SWIFT_WARN_UNUSED_RESULT; -- (CGFloat)progressTimeForMarker:(NSString * _Nonnull)named SWIFT_WARN_UNUSED_RESULT; -- (CGFloat)frameTimeForMarker:(NSString * _Nonnull)named SWIFT_WARN_UNUSED_RESULT; -- (CGFloat)durationFrameTimeForMarker:(NSString * _Nonnull)named SWIFT_WARN_UNUSED_RESULT; -@end - - -/// The base class for a child layer of CompositionContainer -SWIFT_CLASS("_TtC6Lottie16CompositionLayer") -@interface CompositionLayer : CALayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - - -/// The root CALayer of the Core Animation rendering engine -SWIFT_CLASS("_TtC6Lottie18CoreAnimationLayer") -@interface CoreAnimationLayer : BaseAnimationLayer -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (void)layoutSublayers; -- (void)display; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -@protocol CAAction; - -/// A CALayer subclass that renders text content using CoreText -SWIFT_CLASS("_TtC6Lottie19CoreTextRenderLayer") -@interface CoreTextRenderLayer : CALayer -- (id _Nullable)actionForKey:(NSString * _Nonnull)_ SWIFT_WARN_UNUSED_RESULT; -- (void)drawInContext:(CGContextRef _Nonnull)ctx; -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; -@end - - -SWIFT_CLASS("_TtC6Lottie10DebugLayer") -@interface DebugLayer : CALayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer SWIFT_UNAVAILABLE; -@end - - - - -/// A CAGradientLayer subclass used to render a gradient outside the normal layer bounds -///
    -///
  • -/// GradientFill.startPoint and GradientFill.endPoint are expressed -/// with respect to the bounds of the ShapeItemLayer. -///
  • -///
  • -/// The gradient itself is supposed to be rendered infinitely in all directions -/// (e.g. including outside of bounds). This is because ShapeItemLayer paths -/// don’t necessarily sit within the layer’s bounds. -///
  • -///
  • -/// To support this, GradientRenderLayer tracks a gradientReferenceBounds -/// that startPoint / endPoint are calculated relative to. -/// The actual bounds of this layer is padded by a large amount so that -/// the gradient can be drawn outside of the gradientReferenceBounds. -///
  • -///
-SWIFT_CLASS("_TtC6Lottie19GradientRenderLayer") -@interface GradientRenderLayer : CAGradientLayer -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; -@end - - - - -/// The CALayer type responsible for rendering Groups -SWIFT_CLASS("_TtC6Lottie10GroupLayer") -@interface GroupLayer : BaseAnimationLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -SWIFT_CLASS("_TtC6Lottie21ImageCompositionLayer") -@interface ImageCompositionLayer : CompositionLayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -@end - - -/// The CALayer type responsible for rendering ImageLayerModels -SWIFT_CLASS("_TtC6Lottie10ImageLayer") -@interface ImageLayer : BaseCompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - - -/// A BaseAnimationLayer subclass that renders its background color -/// as if the layer is infinitely large, without affecting its bounds -/// or the bounds of its sublayers -SWIFT_CLASS("_TtC6Lottie28InfiniteOpaqueAnimationLayer") -@interface InfiniteOpaqueAnimationLayer : BaseAnimationLayer -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (void)layoutSublayers; -@end - - -/// A layer that inverses the alpha output of its input layer. -/// WARNING: This is experimental and probably not very performant. -SWIFT_CLASS("_TtC6Lottie18InvertedMatteLayer") -@interface InvertedMatteLayer : CALayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (void)drawInContext:(CGContextRef _Nonnull)ctx; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -/// The base view for LottieAnimationView on iOS, tvOS, watchOS, and macCatalyst. -/// Enables the LottieAnimationView implementation to be shared across platforms. -SWIFT_CLASS("_TtC6Lottie23LottieAnimationViewBase") -@interface LottieAnimationViewBase : UIView -@property (nonatomic) UIViewContentMode contentMode; -- (void)didMoveToWindow; -- (void)layoutSubviews; -- (void)animationWillMoveToBackground; -- (void)animationWillEnterForeground; -- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder OBJC_DESIGNATED_INITIALIZER; -@end - - -IB_DESIGNABLE -SWIFT_CLASS("_TtC6Lottie19LottieAnimationView") -@interface LottieAnimationView : LottieAnimationViewBase -- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER; -@property (nonatomic, readonly) CGSize intrinsicContentSize; -/// Set animation name from Interface Builder -@property (nonatomic, copy) IBInspectable NSString * _Nullable animationName; -- (void)animationWillMoveToBackground; -- (void)animationWillEnterForeground; -@end - - - - -/// The base CALayer for the Main Thread rendering engine -/// This layer holds a single composition container and allows for animation of -/// the currentFrame property. -SWIFT_CLASS("_TtC6Lottie24MainThreadAnimationLayer") -@interface MainThreadAnimationLayer : CALayer -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -+ (BOOL)needsDisplayForKey:(NSString * _Nonnull)key SWIFT_WARN_UNUSED_RESULT; -- (id _Nullable)actionForKey:(NSString * _Nonnull)event SWIFT_WARN_UNUSED_RESULT; -- (void)display; -/// The animatable Current Frame Property -@property (nonatomic) CGFloat currentFrame; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - - -/// The CALayer type responsible for rendering the Mask of a BaseCompositionLayer -SWIFT_CLASS("_TtC6Lottie20MaskCompositionLayer") -@interface MaskCompositionLayer : CALayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (void)layoutSublayers; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -@interface MaskCompositionLayer (SWIFT_EXTENSION(Lottie)) -@end - - - -SWIFT_CLASS("_TtC6Lottie18MaskContainerLayer") -@interface MaskContainerLayer : CALayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - - -SWIFT_CLASS("_TtC6Lottie20NullCompositionLayer") -@interface NullCompositionLayer : CompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - - -/// The CALayer type responsible for rendering PreCompLayerModels -SWIFT_CLASS("_TtC6Lottie12PreCompLayer") -@interface PreCompLayer : BaseCompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - - -SWIFT_CLASS("_TtC6Lottie19PreCompositionLayer") -@interface PreCompositionLayer : CompositionLayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -@end - - -/// A layer that renders a child layer at some offset using a Repeater -SWIFT_CLASS("_TtC6Lottie13RepeaterLayer") -@interface RepeaterLayer : BaseAnimationLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -/// A CompositionLayer responsible for initializing and rendering shapes -SWIFT_CLASS("_TtC6Lottie21ShapeCompositionLayer") -@interface ShapeCompositionLayer : CompositionLayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -@end - - - -/// The base layer that holds Shapes and Shape Renderers -SWIFT_CLASS("_TtC6Lottie19ShapeContainerLayer") -@interface ShapeContainerLayer : CALayer -- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - -/// A CALayer type that renders an array of [ShapeItem]s, -/// from a Group in a ShapeLayerModel. -SWIFT_CLASS("_TtC6Lottie14ShapeItemLayer") -@interface ShapeItemLayer : BaseAnimationLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - -/// The CALayer type responsible for rendering ShapeLayerModels -SWIFT_CLASS("_TtC6Lottie10ShapeLayer") -@interface ShapeLayer : BaseCompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - -/// The layer responsible for rendering shape objects -SWIFT_CLASS("_TtC6Lottie16ShapeRenderLayer") -@interface ShapeRenderLayer : ShapeContainerLayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (void)drawInContext:(CGContextRef _Nonnull)ctx; -- (nonnull instancetype)init SWIFT_UNAVAILABLE; -+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); -@end - - - -SWIFT_CLASS("_TtC6Lottie21SolidCompositionLayer") -@interface SolidCompositionLayer : CompositionLayer -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -@end - - -SWIFT_CLASS("_TtC6Lottie10SolidLayer") -@interface SolidLayer : BaseCompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - -SWIFT_CLASS("_TtC6Lottie20TextCompositionLayer") -@interface TextCompositionLayer : CompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - -/// The CALayer type responsible for rendering TextLayers -SWIFT_CLASS("_TtC6Lottie9TextLayer") -@interface TextLayer : BaseCompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER; -/// Called by CoreAnimation to create a shadow copy of this layer -/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - -/// The CALayer type responsible for only rendering the transform of a LayerModel -SWIFT_CLASS("_TtC6Lottie14TransformLayer") -@interface TransformLayer : BaseCompositionLayer -- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)argument OBJC_DESIGNATED_INITIALIZER; -- (nonnull instancetype)initWithLayer:(id _Nonnull)layer OBJC_DESIGNATED_INITIALIZER; -@end - - -#if __has_attribute(external_source_symbol) -# pragma clang attribute pop -#endif -#pragma clang diagnostic pop -#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Info.plist b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Info.plist deleted file mode 100644 index c668eeb806d2678e5cc5b7098e4617cbbe415cc4..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Info.plist and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Lottie b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Lottie deleted file mode 100644 index c8ea9016aee93adc674125490e6034745de235b1..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Lottie and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo deleted file mode 100644 index 494b26314b90240bdebe665809cf4a037314ab46..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/arm64.swiftsourceinfo b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/arm64.swiftsourceinfo deleted file mode 100644 index 494b26314b90240bdebe665809cf4a037314ab46..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/arm64.swiftsourceinfo and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo deleted file mode 100644 index a73fce0f3d8c7f23b7d28abfce921df2b72bf4fd..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/x86_64.swiftsourceinfo b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/x86_64.swiftsourceinfo deleted file mode 100644 index a73fce0f3d8c7f23b7d28abfce921df2b72bf4fd..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/Project/x86_64.swiftsourceinfo and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftdoc b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftdoc deleted file mode 100644 index 7192b024043c8d9c1c81d1ba2b72281ac679da2c..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftdoc and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftinterface b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftinterface deleted file mode 100644 index 1a3bf1b5ce73d03a1b5726920bdbcf78c48b3072..0000000000000000000000000000000000000000 --- a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftinterface +++ /dev/null @@ -1,946 +0,0 @@ -// swift-interface-format-version: 1.0 -// swift-compiler-version: Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30) -// swift-module-flags: -target arm64-apple-ios11.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name Lottie -import Compression -import CoreFoundation -import CoreGraphics -import CoreText -import Foundation -import QuartzCore -import Swift -import UIKit -import _Concurrency -import zlib -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers final public class ImageAsset : Lottie.Asset { - final public let name: Swift.String - final public let directory: Swift.String - final public let width: Swift.Double - final public let height: Swift.Double - override final public func encode(to encoder: Swift.Encoder) throws - @objc deinit -} -extension QuartzCore.CALayer { - @nonobjc public func logLayerTree(withIndent: Swift.Int = 0) -} -public protocol AnimationFontProvider { - func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? -} -final public class DefaultFontProvider : Lottie.AnimationFontProvider { - public init() - final public func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? - @objc deinit -} -public enum LottieAnimationCache { - public static var shared: Lottie.AnimationCacheProvider? -} -public class DefaultAnimationCache : Lottie.AnimationCacheProvider { - public init() - public static let sharedCache: Lottie.DefaultAnimationCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func animation(forKey key: Swift.String) -> Lottie.LottieAnimation? - public func setAnimation(_ animation: Lottie.LottieAnimation, forKey key: Swift.String) - @objc deinit -} -public class FilepathImageProvider : Lottie.AnimationImageProvider { - public init(filepath: Swift.String) - public init(filepath: Foundation.URL) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -public protocol AnimationTextProvider : AnyObject { - func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String -} -final public class DictionaryTextProvider : Lottie.AnimationTextProvider { - public init(_ values: [Swift.String : Swift.String]) - final public func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -final public class DefaultTextProvider : Lottie.AnimationTextProvider { - public init() - final public func textFor(keypathName _: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -public enum CoordinateSpace : Swift.Int, Swift.Codable { - case type2d - case type3d - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class LottieAnimation : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - public init(dictionary: [Swift.String : Any]) throws - final public let startFrame: Lottie.AnimationFrameTime - final public let endFrame: Lottie.AnimationFrameTime - final public let framerate: Swift.Double - final public var markerNames: [Swift.String] { - get - } - @objc deinit - final public func encode(to encoder: Swift.Encoder) throws -} -extension UIKit.UIColor { - public var lottieColorValue: Lottie.LottieColor { - get - } -} -public protocol AnimationCacheProvider : AnyObject { - func animation(forKey: Swift.String) -> Lottie.LottieAnimation? - func setAnimation(_ animation: Lottie.LottieAnimation, forKey: Swift.String) - func clearCache() -} -public typealias LottieCompletionBlock = (Swift.Bool) -> Swift.Void -@_hasMissingDesignatedInitializers public class Asset : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - final public let id: Swift.String - @objc deinit - public func encode(to encoder: Swift.Encoder) throws -} -final public class SizeValueProvider { - public init(block: @escaping Lottie.SizeValueProvider.SizeValueBlock) - public init(_ size: CoreGraphics.CGSize) - public typealias SizeValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGSize - final public var size: CoreGraphics.CGSize { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -infix operator +| : DefaultPrecedence -infix operator +- : DefaultPrecedence -@objc final public class CompatibleAnimation : ObjectiveC.NSObject { - @objc public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main) - @objc deinit -} -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc @_Concurrency.MainActor(unsafe) final public class CompatibleAnimationView : UIKit.UIView { - @objc @_Concurrency.MainActor(unsafe) public init(compatibleAnimation: Lottie.CompatibleAnimation) - @objc @_Concurrency.MainActor(unsafe) override dynamic public init(frame: CoreGraphics.CGRect) - @objc @_Concurrency.MainActor(unsafe) final public var compatibleAnimation: Lottie.CompatibleAnimation? { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var loopAnimationCount: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) override final public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentProgress: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentFrame: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public func play() - @objc @_Concurrency.MainActor(unsafe) final public func play(completion: ((Swift.Bool) -> Swift.Void)?) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromProgress: CoreGraphics.CGFloat, toProgress: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromFrame: CoreGraphics.CGFloat, toFrame: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String, toMarker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func stop() - @objc @_Concurrency.MainActor(unsafe) final public func pause() - @objc @_Concurrency.MainActor(unsafe) final public func reloadImages() - @objc @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @objc @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> Any? - @objc @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @objc @_Concurrency.MainActor(unsafe) final public func setColorValue(_ color: UIKit.UIColor, forKeypath keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func getColorValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> UIKit.UIColor? - @objc @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func convert(rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGRect - @objc @_Concurrency.MainActor(unsafe) final public func convert(point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGPoint - @objc @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc deinit -} -public typealias AnimationFrameTime = CoreGraphics.CGFloat -public typealias AnimationProgressTime = CoreGraphics.CGFloat -public enum ColorFormatDenominator : Swift.Hashable { - case One - case OneHundred - case TwoFiftyFive - public static func == (a: Lottie.ColorFormatDenominator, b: Lottie.ColorFormatDenominator) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public struct LottieColor : Swift.Hashable { - public var r: Swift.Double - public var g: Swift.Double - public var b: Swift.Double - public var a: Swift.Double - public init(r: Swift.Double, g: Swift.Double, b: Swift.Double, a: Swift.Double, denominator: Lottie.ColorFormatDenominator = .One) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieColor, b: Lottie.LottieColor) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -final public class ColorValueProvider { - public init(block: @escaping Lottie.ColorValueProvider.ColorValueBlock) - public init(_ color: Lottie.LottieColor) - public init(_ keyframes: [Lottie.Keyframe]) - public typealias ColorValueBlock = (CoreGraphics.CGFloat) -> Lottie.LottieColor - final public var color: Lottie.LottieColor { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -public struct AnimationKeypath : Swift.Hashable, Swift.ExpressibleByStringLiteral { - public init(keypath: Swift.String) - public init(stringLiteral: Swift.String) - public init(keys: [Swift.String]) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.AnimationKeypath, b: Lottie.AnimationKeypath) -> Swift.Bool - public typealias ExtendedGraphemeClusterLiteralType = Swift.String - public typealias StringLiteralType = Swift.String - public typealias UnicodeScalarLiteralType = Swift.String - public var hashValue: Swift.Int { - get - } -} -public struct LottieConfiguration : Swift.Hashable { - public init(renderingEngine: Lottie.RenderingEngineOption = .automatic, decodingStrategy: Lottie.DecodingStrategy = .dictionaryBased) - public static var shared: Lottie.LottieConfiguration - public var renderingEngine: Lottie.RenderingEngineOption - public var decodingStrategy: Lottie.DecodingStrategy - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieConfiguration, b: Lottie.LottieConfiguration) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum RenderingEngineOption : Swift.Hashable { - case automatic - case specific(Lottie.RenderingEngine) - public static var mainThread: Lottie.RenderingEngineOption { - get - } - public static var coreAnimation: Lottie.RenderingEngineOption { - get - } -} -public enum RenderingEngine : Swift.Hashable { - case mainThread - case coreAnimation -} -extension Lottie.RenderingEngineOption : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -extension Lottie.RenderingEngine : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -public enum DecodingStrategy : Swift.Hashable { - case legacyCodable - case dictionaryBased - public static func == (a: Lottie.DecodingStrategy, b: Lottie.DecodingStrategy) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector1D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -public struct LottieVector2D : Swift.Codable, Swift.Hashable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector2D, b: Lottie.LottieVector2D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector3D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.LottieVector3D { - public var pointValue: CoreGraphics.CGPoint { - get - } - public var sizeValue: CoreGraphics.CGSize { - get - } -} -extension Lottie.LottieAnimationView { - @_Concurrency.MainActor(unsafe) convenience public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(filePath: Swift.String, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(url: Foundation.URL, imageProvider: Lottie.AnimationImageProvider? = nil, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimationView.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(asset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieName name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieFilePath filePath: Swift.String, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieUrl url: Foundation.URL, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, session: Foundation.URLSession = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieAsset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - public typealias DownloadClosure = (Swift.Error?) -> Swift.Void -} -public protocol AnyValueProvider { - var valueType: Any.Type { get } - var typeErasedStorage: Lottie.AnyValueProviderStorage { get } - func hasUpdate(frame: Lottie.AnimationFrameTime) -> Swift.Bool -} -extension Lottie.AnyValueProvider { - public func value(frame: Lottie.AnimationFrameTime) -> Any -} -public enum ValueProviderStorage where T : Lottie.AnyInterpolatable { - case singleValue(T) - case keyframes([Lottie.Keyframe]) - case closure((Lottie.AnimationFrameTime) -> T) -} -public enum AnyValueProviderStorage { - case singleValue(Any) - case keyframes([Lottie.Keyframe], interpolate: (Lottie.AnimationFrameTime) -> Any) - case closure((Lottie.AnimationFrameTime) -> Any) -} -public struct LottieVector1D : Swift.Hashable { - public init(_ value: Swift.Double) - public let value: Swift.Double - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector1D, b: Lottie.LottieVector1D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public struct LottieVector3D : Swift.Hashable { - public let x: Swift.Double - public let y: Swift.Double - public let z: Swift.Double - public init(x: Swift.Double, y: Swift.Double, z: Swift.Double) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector3D, b: Lottie.LottieVector3D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum LottieBackgroundBehavior { - case stop - case pause - case pauseAndRestore - case forceFinish - case continuePlaying - public static func `default`(for renderingEngine: Lottie.RenderingEngine) -> Lottie.LottieBackgroundBehavior - public static func == (a: Lottie.LottieBackgroundBehavior, b: Lottie.LottieBackgroundBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public enum LottieLoopMode { - case playOnce - case loop - case autoReverse - case `repeat`(Swift.Float) - case repeatBackwards(Swift.Float) -} -extension Lottie.LottieLoopMode : Swift.Equatable { - public static func == (lhs: Lottie.LottieLoopMode, rhs: Lottie.LottieLoopMode) -> Swift.Bool -} -@objc @_inheritsConvenienceInitializers @IBDesignable @_Concurrency.MainActor(unsafe) final public class LottieAnimationView : Lottie.LottieAnimationViewBase { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation?, imageProvider: Lottie.AnimationImageProvider? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(dotLottie: Lottie.DotLottieFile?, animationId: Swift.String? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) final public let configuration: Lottie.LottieConfiguration - @_Concurrency.MainActor(unsafe) final public var valueProviders: [Lottie.AnimationKeypath : Lottie.AnyValueProvider] { - get - } - @_Concurrency.MainActor(unsafe) final public var backgroundBehavior: Lottie.LottieBackgroundBehavior { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animationLoaded: ((_ animationView: Lottie.LottieAnimationView, _ animation: Lottie.LottieAnimation) -> Swift.Void)? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var imageProvider: Lottie.AnimationImageProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var textProvider: Lottie.AnimationTextProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var fontProvider: Lottie.AnimationFontProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var isAnimationQueued: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var loopMode: Lottie.LottieLoopMode { - get - set - } - @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentProgress: Lottie.AnimationProgressTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentFrame: Lottie.AnimationFrameTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: Lottie.AnimationFrameTime { - get - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: Lottie.AnimationProgressTime { - get - } - @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var viewportFrame: CoreGraphics.CGRect? { - get - set - } - @_Concurrency.MainActor(unsafe) @objc override final public var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) final public var currentRenderingEngine: Lottie.RenderingEngine? { - get - } - @_Concurrency.MainActor(unsafe) final public func loadAnimation(_ animationId: Swift.String? = nil, from dotLottieFile: Lottie.DotLottieFile) - @_Concurrency.MainActor(unsafe) final public func play(completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromProgress: Lottie.AnimationProgressTime? = nil, toProgress: Lottie.AnimationProgressTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromFrame: Lottie.AnimationFrameTime? = nil, toFrame: Lottie.AnimationFrameTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String? = nil, toMarker: Swift.String, playEndMarkerFrame: Swift.Bool = true, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func stop() - @_Concurrency.MainActor(unsafe) final public func pause() - @_Concurrency.MainActor(unsafe) final public func reloadImages() - @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @_Concurrency.MainActor(unsafe) final public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func getOriginalValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func convert(_ rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGRect? - @_Concurrency.MainActor(unsafe) final public func convert(_ point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGPoint? - @_Concurrency.MainActor(unsafe) final public func setNodeIsEnabled(isEnabled: Swift.Bool, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @objc deinit -} -@objc @_Concurrency.MainActor(unsafe) open class AnimatedControl : UIKit.UIControl { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isEnabled: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isSelected: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isHighlighted: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func continueTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func cancelTracking(with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) open func animationDidSet() - @_Concurrency.MainActor(unsafe) final public let animationView: Lottie.LottieAnimationView - @_Concurrency.MainActor(unsafe) public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) public func setLayer(named: Swift.String, forState: UIKit.UIControl.State) - @_Concurrency.MainActor(unsafe) public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @objc deinit -} -public enum DotLottieError : Swift.Error { - case invalidFileFormat - case invalidData - case animationNotAvailable - public static func == (a: Lottie.DotLottieError, b: Lottie.DotLottieError) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -final public class FloatValueProvider { - public init(block: @escaping Lottie.FloatValueProvider.CGFloatValueBlock) - public init(_ float: CoreGraphics.CGFloat) - public typealias CGFloatValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGFloat - final public var float: CoreGraphics.CGFloat { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc final public class CompatibleAnimationKeypath : ObjectiveC.NSObject { - @objc public init(keypath: Swift.String) - @objc public init(keys: [Swift.String]) - final public let animationKeypath: Lottie.AnimationKeypath - @objc deinit -} -extension Lottie.LottieColor : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.DotLottieFile { - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) -} -public protocol DotLottieCacheProvider { - func file(forKey: Swift.String) -> Lottie.DotLottieFile? - func setFile(_ lottie: Lottie.DotLottieFile, forKey: Swift.String) - func clearCache() -} -public class DotLottieCache : Lottie.DotLottieCacheProvider { - public init() - public static let sharedCache: Lottie.DotLottieCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func file(forKey key: Swift.String) -> Lottie.DotLottieFile? - public func setFile(_ lottie: Lottie.DotLottieFile, forKey key: Swift.String) - @objc deinit -} -extension Lottie.LottieAnimation { - public typealias DownloadClosure = (Lottie.LottieAnimation?) -> Swift.Void - final public var duration: Foundation.TimeInterval { - get - } - final public var bounds: CoreGraphics.CGRect { - get - } - final public var size: CoreGraphics.CGSize { - get - } - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func filepath(_ filepath: Swift.String, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func asset(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func from(data: Foundation.Data, strategy: Lottie.DecodingStrategy = LottieConfiguration.shared.decodingStrategy) throws -> Lottie.LottieAnimation - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) async -> Lottie.LottieAnimation? - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimation.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) - final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func progressTime(forFrame frameTime: Lottie.AnimationFrameTime, clamped: Swift.Bool = true) -> Lottie.AnimationProgressTime - final public func frameTime(forProgress progressTime: Lottie.AnimationProgressTime) -> Lottie.AnimationFrameTime - final public func time(forFrame frameTime: Lottie.AnimationFrameTime) -> Foundation.TimeInterval - final public func frameTime(forTime time: Foundation.TimeInterval) -> Lottie.AnimationFrameTime -} -extension Foundation.Bundle : @unchecked Swift.Sendable { -} -public enum LayerType : Swift.Int, Swift.Codable { - case precomp - case solid - case image - case null - case shape - case text - public init(from decoder: Swift.Decoder) throws - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum MatteType : Swift.Int, Swift.Codable { - case none - case add - case invert - case unknown - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum BlendMode : Swift.Int, Swift.Codable { - case normal - case multiply - case screen - case overlay - case darken - case lighten - case colorDodge - case colorBurn - case hardLight - case softLight - case difference - case exclusion - case hue - case saturation - case color - case luminosity - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class GradientValueProvider { - public init(block: @escaping Lottie.GradientValueProvider.ColorsValueBlock, locations: Lottie.GradientValueProvider.ColorLocationsBlock? = nil) - public init(_ colors: [Lottie.LottieColor], locations: [Swift.Double] = []) - public typealias ColorsValueBlock = (CoreGraphics.CGFloat) -> [Lottie.LottieColor] - public typealias ColorLocationsBlock = (CoreGraphics.CGFloat) -> [Swift.Double] - final public var colors: [Lottie.LottieColor] { - get - set - } - final public var locations: [Swift.Double] { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage<[Swift.Double]> { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedSwitch : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) override open func animationDidSet() - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - public enum CancelBehavior { - case reverse - case none - public static func == (a: Lottie.AnimatedSwitch.CancelBehavior, b: Lottie.AnimatedSwitch.CancelBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } - } - @_Concurrency.MainActor(unsafe) public var cancelBehavior: Lottie.AnimatedSwitch.CancelBehavior - @_Concurrency.MainActor(unsafe) public var animateUpdateWhenChangingAnimation: Swift.Bool - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public var isOn: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) public func setIsOn(_ isOn: Swift.Bool, animated: Swift.Bool, shouldFireHaptics: Swift.Bool = true) - @_Concurrency.MainActor(unsafe) public func setProgressForState(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, forOnState: Swift.Bool) - @objc deinit -} -public class BundleImageProvider : Lottie.AnimationImageProvider { - public init(bundle: Foundation.Bundle, searchPath: Swift.String?) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) final public class AnimationSubview : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LottieAnimationViewBase : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic public func didMoveToWindow() - @_Concurrency.MainActor(unsafe) @objc override dynamic public func layoutSubviews() - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@_hasMissingDesignatedInitializers final public class DotLottieFile { - @objc deinit -} -final public class LottieLogger { - public init(assert: @escaping Lottie.LottieLogger.Assert = { condition, message, file, line in - // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assert(condition(), message(), file: file, line: line) - }, assertionFailure: @escaping Lottie.LottieLogger.AssertionFailure = { message, file, line in - // If we default to `Swift.assertionFailure` directly with - // `assertionFailure: AssertionFailure = Swift.assertionFailure`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assertionFailure(message(), file: file, line: line) - }, warn: @escaping Lottie.LottieLogger.Warn = { message, _, _ in - }, info: @escaping Lottie.LottieLogger.Info = { message in - }) - public typealias Assert = (_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias AssertionFailure = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Warn = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Info = (_ message: @autoclosure () -> Swift.String) -> Swift.Void - public static var shared: Lottie.LottieLogger - final public func assert(_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func assertionFailure(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func warn(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func info(_ message: @autoclosure () -> Swift.String = String()) - @objc deinit -} -extension Lottie.LottieLogger { - public static var printToConsole: Lottie.LottieLogger { - get - } -} -@available(*, deprecated, message: "Use DefaultAnimationCache instead, which is thread-safe and automatically responds to memory pressure.") -public typealias LRUAnimationCache = Lottie.DefaultAnimationCache -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedButton : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, event: UIKit.UIControl.Event) - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromMarker fromName: Swift.String, toMarker toName: Swift.String, event: UIKit.UIControl.Event) - @objc deinit -} -final public class PointValueProvider { - public init(block: @escaping Lottie.PointValueProvider.PointValueBlock) - public init(_ point: CoreGraphics.CGPoint) - public typealias PointValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGPoint - final public var point: CoreGraphics.CGPoint { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -final public class Keyframe { - public init(_ value: T, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - public init(value: T, time: Lottie.AnimationFrameTime, isHold: Swift.Bool = false, inTangent: Lottie.LottieVector2D? = nil, outTangent: Lottie.LottieVector2D? = nil, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - final public let value: T - final public let time: Lottie.AnimationFrameTime - final public let isHold: Swift.Bool - final public let inTangent: Lottie.LottieVector2D? - final public let outTangent: Lottie.LottieVector2D? - final public let spatialInTangent: Lottie.LottieVector3D? - final public let spatialOutTangent: Lottie.LottieVector3D? - @objc deinit -} -extension Lottie.Keyframe : Swift.Equatable where T : Swift.Equatable { - public static func == (lhs: Lottie.Keyframe, rhs: Lottie.Keyframe) -> Swift.Bool -} -extension Lottie.Keyframe : Swift.Hashable where T : Swift.Hashable { - final public func hash(into hasher: inout Swift.Hasher) - final public var hashValue: Swift.Int { - get - } -} -public protocol AnimationImageProvider { - func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? -} -public protocol Interpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -public protocol SpatialInterpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -public protocol AnyInterpolatable { - func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.Interpolatable { - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent _: CoreGraphics.CGPoint?, spatialInTangent _: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.SpatialInterpolatable { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Swift.Double : Lottie.Interpolatable { -} -extension CoreGraphics.CGFloat : Lottie.Interpolatable { -} -extension Swift.Float : Lottie.Interpolatable { -} -extension Lottie.Interpolatable where Self : Swift.BinaryFloatingPoint { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -extension CoreGraphics.CGRect : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGRect, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGRect -} -extension CoreGraphics.CGSize : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGSize, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGSize -} -extension CoreGraphics.CGPoint : Lottie.SpatialInterpolatable { - public func interpolate(to: CoreGraphics.CGPoint, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> CoreGraphics.CGPoint -} -extension Lottie.LottieColor : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieColor, amount: CoreGraphics.CGFloat) -> Lottie.LottieColor -} -extension Lottie.LottieVector1D : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieVector1D, amount: CoreGraphics.CGFloat) -> Lottie.LottieVector1D -} -extension Lottie.LottieVector2D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector2D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector2D -} -extension Lottie.LottieVector3D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector3D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector3D -} -extension Swift.Array : Lottie.Interpolatable, Lottie.AnyInterpolatable where Element : Lottie.Interpolatable { - public func interpolate(to: [Element], amount: CoreGraphics.CGFloat) -> [Element] -} -extension Lottie.CoordinateSpace : Swift.Equatable {} -extension Lottie.CoordinateSpace : Swift.Hashable {} -extension Lottie.CoordinateSpace : Swift.RawRepresentable {} -extension Lottie.SizeValueProvider : Lottie.AnyValueProvider {} -extension Lottie.ColorValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LottieBackgroundBehavior : Swift.Equatable {} -extension Lottie.LottieBackgroundBehavior : Swift.Hashable {} -extension Lottie.DotLottieError : Swift.Equatable {} -extension Lottie.DotLottieError : Swift.Hashable {} -extension Lottie.FloatValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LayerType : Swift.Equatable {} -extension Lottie.LayerType : Swift.Hashable {} -extension Lottie.LayerType : Swift.RawRepresentable {} -extension Lottie.MatteType : Swift.Equatable {} -extension Lottie.MatteType : Swift.Hashable {} -extension Lottie.MatteType : Swift.RawRepresentable {} -extension Lottie.BlendMode : Swift.Equatable {} -extension Lottie.BlendMode : Swift.Hashable {} -extension Lottie.BlendMode : Swift.RawRepresentable {} -extension Lottie.GradientValueProvider : Lottie.AnyValueProvider {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Equatable {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Hashable {} -extension Lottie.PointValueProvider : Lottie.AnyValueProvider {} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftmodule b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftmodule deleted file mode 100644 index 29f2d01fac795fff9bc151d4036471a2ac3940a0..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64-apple-ios.swiftmodule and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftdoc b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftdoc deleted file mode 100644 index 7192b024043c8d9c1c81d1ba2b72281ac679da2c..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftdoc and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftinterface b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftinterface deleted file mode 100644 index 1a3bf1b5ce73d03a1b5726920bdbcf78c48b3072..0000000000000000000000000000000000000000 --- a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftinterface +++ /dev/null @@ -1,946 +0,0 @@ -// swift-interface-format-version: 1.0 -// swift-compiler-version: Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30) -// swift-module-flags: -target arm64-apple-ios11.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name Lottie -import Compression -import CoreFoundation -import CoreGraphics -import CoreText -import Foundation -import QuartzCore -import Swift -import UIKit -import _Concurrency -import zlib -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers final public class ImageAsset : Lottie.Asset { - final public let name: Swift.String - final public let directory: Swift.String - final public let width: Swift.Double - final public let height: Swift.Double - override final public func encode(to encoder: Swift.Encoder) throws - @objc deinit -} -extension QuartzCore.CALayer { - @nonobjc public func logLayerTree(withIndent: Swift.Int = 0) -} -public protocol AnimationFontProvider { - func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? -} -final public class DefaultFontProvider : Lottie.AnimationFontProvider { - public init() - final public func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? - @objc deinit -} -public enum LottieAnimationCache { - public static var shared: Lottie.AnimationCacheProvider? -} -public class DefaultAnimationCache : Lottie.AnimationCacheProvider { - public init() - public static let sharedCache: Lottie.DefaultAnimationCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func animation(forKey key: Swift.String) -> Lottie.LottieAnimation? - public func setAnimation(_ animation: Lottie.LottieAnimation, forKey key: Swift.String) - @objc deinit -} -public class FilepathImageProvider : Lottie.AnimationImageProvider { - public init(filepath: Swift.String) - public init(filepath: Foundation.URL) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -public protocol AnimationTextProvider : AnyObject { - func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String -} -final public class DictionaryTextProvider : Lottie.AnimationTextProvider { - public init(_ values: [Swift.String : Swift.String]) - final public func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -final public class DefaultTextProvider : Lottie.AnimationTextProvider { - public init() - final public func textFor(keypathName _: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -public enum CoordinateSpace : Swift.Int, Swift.Codable { - case type2d - case type3d - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class LottieAnimation : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - public init(dictionary: [Swift.String : Any]) throws - final public let startFrame: Lottie.AnimationFrameTime - final public let endFrame: Lottie.AnimationFrameTime - final public let framerate: Swift.Double - final public var markerNames: [Swift.String] { - get - } - @objc deinit - final public func encode(to encoder: Swift.Encoder) throws -} -extension UIKit.UIColor { - public var lottieColorValue: Lottie.LottieColor { - get - } -} -public protocol AnimationCacheProvider : AnyObject { - func animation(forKey: Swift.String) -> Lottie.LottieAnimation? - func setAnimation(_ animation: Lottie.LottieAnimation, forKey: Swift.String) - func clearCache() -} -public typealias LottieCompletionBlock = (Swift.Bool) -> Swift.Void -@_hasMissingDesignatedInitializers public class Asset : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - final public let id: Swift.String - @objc deinit - public func encode(to encoder: Swift.Encoder) throws -} -final public class SizeValueProvider { - public init(block: @escaping Lottie.SizeValueProvider.SizeValueBlock) - public init(_ size: CoreGraphics.CGSize) - public typealias SizeValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGSize - final public var size: CoreGraphics.CGSize { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -infix operator +| : DefaultPrecedence -infix operator +- : DefaultPrecedence -@objc final public class CompatibleAnimation : ObjectiveC.NSObject { - @objc public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main) - @objc deinit -} -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc @_Concurrency.MainActor(unsafe) final public class CompatibleAnimationView : UIKit.UIView { - @objc @_Concurrency.MainActor(unsafe) public init(compatibleAnimation: Lottie.CompatibleAnimation) - @objc @_Concurrency.MainActor(unsafe) override dynamic public init(frame: CoreGraphics.CGRect) - @objc @_Concurrency.MainActor(unsafe) final public var compatibleAnimation: Lottie.CompatibleAnimation? { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var loopAnimationCount: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) override final public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentProgress: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentFrame: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public func play() - @objc @_Concurrency.MainActor(unsafe) final public func play(completion: ((Swift.Bool) -> Swift.Void)?) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromProgress: CoreGraphics.CGFloat, toProgress: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromFrame: CoreGraphics.CGFloat, toFrame: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String, toMarker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func stop() - @objc @_Concurrency.MainActor(unsafe) final public func pause() - @objc @_Concurrency.MainActor(unsafe) final public func reloadImages() - @objc @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @objc @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> Any? - @objc @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @objc @_Concurrency.MainActor(unsafe) final public func setColorValue(_ color: UIKit.UIColor, forKeypath keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func getColorValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> UIKit.UIColor? - @objc @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func convert(rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGRect - @objc @_Concurrency.MainActor(unsafe) final public func convert(point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGPoint - @objc @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc deinit -} -public typealias AnimationFrameTime = CoreGraphics.CGFloat -public typealias AnimationProgressTime = CoreGraphics.CGFloat -public enum ColorFormatDenominator : Swift.Hashable { - case One - case OneHundred - case TwoFiftyFive - public static func == (a: Lottie.ColorFormatDenominator, b: Lottie.ColorFormatDenominator) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public struct LottieColor : Swift.Hashable { - public var r: Swift.Double - public var g: Swift.Double - public var b: Swift.Double - public var a: Swift.Double - public init(r: Swift.Double, g: Swift.Double, b: Swift.Double, a: Swift.Double, denominator: Lottie.ColorFormatDenominator = .One) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieColor, b: Lottie.LottieColor) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -final public class ColorValueProvider { - public init(block: @escaping Lottie.ColorValueProvider.ColorValueBlock) - public init(_ color: Lottie.LottieColor) - public init(_ keyframes: [Lottie.Keyframe]) - public typealias ColorValueBlock = (CoreGraphics.CGFloat) -> Lottie.LottieColor - final public var color: Lottie.LottieColor { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -public struct AnimationKeypath : Swift.Hashable, Swift.ExpressibleByStringLiteral { - public init(keypath: Swift.String) - public init(stringLiteral: Swift.String) - public init(keys: [Swift.String]) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.AnimationKeypath, b: Lottie.AnimationKeypath) -> Swift.Bool - public typealias ExtendedGraphemeClusterLiteralType = Swift.String - public typealias StringLiteralType = Swift.String - public typealias UnicodeScalarLiteralType = Swift.String - public var hashValue: Swift.Int { - get - } -} -public struct LottieConfiguration : Swift.Hashable { - public init(renderingEngine: Lottie.RenderingEngineOption = .automatic, decodingStrategy: Lottie.DecodingStrategy = .dictionaryBased) - public static var shared: Lottie.LottieConfiguration - public var renderingEngine: Lottie.RenderingEngineOption - public var decodingStrategy: Lottie.DecodingStrategy - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieConfiguration, b: Lottie.LottieConfiguration) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum RenderingEngineOption : Swift.Hashable { - case automatic - case specific(Lottie.RenderingEngine) - public static var mainThread: Lottie.RenderingEngineOption { - get - } - public static var coreAnimation: Lottie.RenderingEngineOption { - get - } -} -public enum RenderingEngine : Swift.Hashable { - case mainThread - case coreAnimation -} -extension Lottie.RenderingEngineOption : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -extension Lottie.RenderingEngine : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -public enum DecodingStrategy : Swift.Hashable { - case legacyCodable - case dictionaryBased - public static func == (a: Lottie.DecodingStrategy, b: Lottie.DecodingStrategy) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector1D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -public struct LottieVector2D : Swift.Codable, Swift.Hashable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector2D, b: Lottie.LottieVector2D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector3D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.LottieVector3D { - public var pointValue: CoreGraphics.CGPoint { - get - } - public var sizeValue: CoreGraphics.CGSize { - get - } -} -extension Lottie.LottieAnimationView { - @_Concurrency.MainActor(unsafe) convenience public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(filePath: Swift.String, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(url: Foundation.URL, imageProvider: Lottie.AnimationImageProvider? = nil, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimationView.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(asset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieName name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieFilePath filePath: Swift.String, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieUrl url: Foundation.URL, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, session: Foundation.URLSession = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieAsset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - public typealias DownloadClosure = (Swift.Error?) -> Swift.Void -} -public protocol AnyValueProvider { - var valueType: Any.Type { get } - var typeErasedStorage: Lottie.AnyValueProviderStorage { get } - func hasUpdate(frame: Lottie.AnimationFrameTime) -> Swift.Bool -} -extension Lottie.AnyValueProvider { - public func value(frame: Lottie.AnimationFrameTime) -> Any -} -public enum ValueProviderStorage where T : Lottie.AnyInterpolatable { - case singleValue(T) - case keyframes([Lottie.Keyframe]) - case closure((Lottie.AnimationFrameTime) -> T) -} -public enum AnyValueProviderStorage { - case singleValue(Any) - case keyframes([Lottie.Keyframe], interpolate: (Lottie.AnimationFrameTime) -> Any) - case closure((Lottie.AnimationFrameTime) -> Any) -} -public struct LottieVector1D : Swift.Hashable { - public init(_ value: Swift.Double) - public let value: Swift.Double - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector1D, b: Lottie.LottieVector1D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public struct LottieVector3D : Swift.Hashable { - public let x: Swift.Double - public let y: Swift.Double - public let z: Swift.Double - public init(x: Swift.Double, y: Swift.Double, z: Swift.Double) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector3D, b: Lottie.LottieVector3D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum LottieBackgroundBehavior { - case stop - case pause - case pauseAndRestore - case forceFinish - case continuePlaying - public static func `default`(for renderingEngine: Lottie.RenderingEngine) -> Lottie.LottieBackgroundBehavior - public static func == (a: Lottie.LottieBackgroundBehavior, b: Lottie.LottieBackgroundBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public enum LottieLoopMode { - case playOnce - case loop - case autoReverse - case `repeat`(Swift.Float) - case repeatBackwards(Swift.Float) -} -extension Lottie.LottieLoopMode : Swift.Equatable { - public static func == (lhs: Lottie.LottieLoopMode, rhs: Lottie.LottieLoopMode) -> Swift.Bool -} -@objc @_inheritsConvenienceInitializers @IBDesignable @_Concurrency.MainActor(unsafe) final public class LottieAnimationView : Lottie.LottieAnimationViewBase { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation?, imageProvider: Lottie.AnimationImageProvider? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(dotLottie: Lottie.DotLottieFile?, animationId: Swift.String? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) final public let configuration: Lottie.LottieConfiguration - @_Concurrency.MainActor(unsafe) final public var valueProviders: [Lottie.AnimationKeypath : Lottie.AnyValueProvider] { - get - } - @_Concurrency.MainActor(unsafe) final public var backgroundBehavior: Lottie.LottieBackgroundBehavior { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animationLoaded: ((_ animationView: Lottie.LottieAnimationView, _ animation: Lottie.LottieAnimation) -> Swift.Void)? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var imageProvider: Lottie.AnimationImageProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var textProvider: Lottie.AnimationTextProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var fontProvider: Lottie.AnimationFontProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var isAnimationQueued: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var loopMode: Lottie.LottieLoopMode { - get - set - } - @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentProgress: Lottie.AnimationProgressTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentFrame: Lottie.AnimationFrameTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: Lottie.AnimationFrameTime { - get - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: Lottie.AnimationProgressTime { - get - } - @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var viewportFrame: CoreGraphics.CGRect? { - get - set - } - @_Concurrency.MainActor(unsafe) @objc override final public var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) final public var currentRenderingEngine: Lottie.RenderingEngine? { - get - } - @_Concurrency.MainActor(unsafe) final public func loadAnimation(_ animationId: Swift.String? = nil, from dotLottieFile: Lottie.DotLottieFile) - @_Concurrency.MainActor(unsafe) final public func play(completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromProgress: Lottie.AnimationProgressTime? = nil, toProgress: Lottie.AnimationProgressTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromFrame: Lottie.AnimationFrameTime? = nil, toFrame: Lottie.AnimationFrameTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String? = nil, toMarker: Swift.String, playEndMarkerFrame: Swift.Bool = true, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func stop() - @_Concurrency.MainActor(unsafe) final public func pause() - @_Concurrency.MainActor(unsafe) final public func reloadImages() - @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @_Concurrency.MainActor(unsafe) final public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func getOriginalValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func convert(_ rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGRect? - @_Concurrency.MainActor(unsafe) final public func convert(_ point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGPoint? - @_Concurrency.MainActor(unsafe) final public func setNodeIsEnabled(isEnabled: Swift.Bool, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @objc deinit -} -@objc @_Concurrency.MainActor(unsafe) open class AnimatedControl : UIKit.UIControl { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isEnabled: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isSelected: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isHighlighted: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func continueTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func cancelTracking(with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) open func animationDidSet() - @_Concurrency.MainActor(unsafe) final public let animationView: Lottie.LottieAnimationView - @_Concurrency.MainActor(unsafe) public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) public func setLayer(named: Swift.String, forState: UIKit.UIControl.State) - @_Concurrency.MainActor(unsafe) public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @objc deinit -} -public enum DotLottieError : Swift.Error { - case invalidFileFormat - case invalidData - case animationNotAvailable - public static func == (a: Lottie.DotLottieError, b: Lottie.DotLottieError) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -final public class FloatValueProvider { - public init(block: @escaping Lottie.FloatValueProvider.CGFloatValueBlock) - public init(_ float: CoreGraphics.CGFloat) - public typealias CGFloatValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGFloat - final public var float: CoreGraphics.CGFloat { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc final public class CompatibleAnimationKeypath : ObjectiveC.NSObject { - @objc public init(keypath: Swift.String) - @objc public init(keys: [Swift.String]) - final public let animationKeypath: Lottie.AnimationKeypath - @objc deinit -} -extension Lottie.LottieColor : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.DotLottieFile { - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) -} -public protocol DotLottieCacheProvider { - func file(forKey: Swift.String) -> Lottie.DotLottieFile? - func setFile(_ lottie: Lottie.DotLottieFile, forKey: Swift.String) - func clearCache() -} -public class DotLottieCache : Lottie.DotLottieCacheProvider { - public init() - public static let sharedCache: Lottie.DotLottieCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func file(forKey key: Swift.String) -> Lottie.DotLottieFile? - public func setFile(_ lottie: Lottie.DotLottieFile, forKey key: Swift.String) - @objc deinit -} -extension Lottie.LottieAnimation { - public typealias DownloadClosure = (Lottie.LottieAnimation?) -> Swift.Void - final public var duration: Foundation.TimeInterval { - get - } - final public var bounds: CoreGraphics.CGRect { - get - } - final public var size: CoreGraphics.CGSize { - get - } - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func filepath(_ filepath: Swift.String, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func asset(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func from(data: Foundation.Data, strategy: Lottie.DecodingStrategy = LottieConfiguration.shared.decodingStrategy) throws -> Lottie.LottieAnimation - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) async -> Lottie.LottieAnimation? - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimation.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) - final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func progressTime(forFrame frameTime: Lottie.AnimationFrameTime, clamped: Swift.Bool = true) -> Lottie.AnimationProgressTime - final public func frameTime(forProgress progressTime: Lottie.AnimationProgressTime) -> Lottie.AnimationFrameTime - final public func time(forFrame frameTime: Lottie.AnimationFrameTime) -> Foundation.TimeInterval - final public func frameTime(forTime time: Foundation.TimeInterval) -> Lottie.AnimationFrameTime -} -extension Foundation.Bundle : @unchecked Swift.Sendable { -} -public enum LayerType : Swift.Int, Swift.Codable { - case precomp - case solid - case image - case null - case shape - case text - public init(from decoder: Swift.Decoder) throws - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum MatteType : Swift.Int, Swift.Codable { - case none - case add - case invert - case unknown - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum BlendMode : Swift.Int, Swift.Codable { - case normal - case multiply - case screen - case overlay - case darken - case lighten - case colorDodge - case colorBurn - case hardLight - case softLight - case difference - case exclusion - case hue - case saturation - case color - case luminosity - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class GradientValueProvider { - public init(block: @escaping Lottie.GradientValueProvider.ColorsValueBlock, locations: Lottie.GradientValueProvider.ColorLocationsBlock? = nil) - public init(_ colors: [Lottie.LottieColor], locations: [Swift.Double] = []) - public typealias ColorsValueBlock = (CoreGraphics.CGFloat) -> [Lottie.LottieColor] - public typealias ColorLocationsBlock = (CoreGraphics.CGFloat) -> [Swift.Double] - final public var colors: [Lottie.LottieColor] { - get - set - } - final public var locations: [Swift.Double] { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage<[Swift.Double]> { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedSwitch : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) override open func animationDidSet() - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - public enum CancelBehavior { - case reverse - case none - public static func == (a: Lottie.AnimatedSwitch.CancelBehavior, b: Lottie.AnimatedSwitch.CancelBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } - } - @_Concurrency.MainActor(unsafe) public var cancelBehavior: Lottie.AnimatedSwitch.CancelBehavior - @_Concurrency.MainActor(unsafe) public var animateUpdateWhenChangingAnimation: Swift.Bool - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public var isOn: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) public func setIsOn(_ isOn: Swift.Bool, animated: Swift.Bool, shouldFireHaptics: Swift.Bool = true) - @_Concurrency.MainActor(unsafe) public func setProgressForState(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, forOnState: Swift.Bool) - @objc deinit -} -public class BundleImageProvider : Lottie.AnimationImageProvider { - public init(bundle: Foundation.Bundle, searchPath: Swift.String?) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) final public class AnimationSubview : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LottieAnimationViewBase : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic public func didMoveToWindow() - @_Concurrency.MainActor(unsafe) @objc override dynamic public func layoutSubviews() - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@_hasMissingDesignatedInitializers final public class DotLottieFile { - @objc deinit -} -final public class LottieLogger { - public init(assert: @escaping Lottie.LottieLogger.Assert = { condition, message, file, line in - // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assert(condition(), message(), file: file, line: line) - }, assertionFailure: @escaping Lottie.LottieLogger.AssertionFailure = { message, file, line in - // If we default to `Swift.assertionFailure` directly with - // `assertionFailure: AssertionFailure = Swift.assertionFailure`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assertionFailure(message(), file: file, line: line) - }, warn: @escaping Lottie.LottieLogger.Warn = { message, _, _ in - }, info: @escaping Lottie.LottieLogger.Info = { message in - }) - public typealias Assert = (_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias AssertionFailure = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Warn = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Info = (_ message: @autoclosure () -> Swift.String) -> Swift.Void - public static var shared: Lottie.LottieLogger - final public func assert(_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func assertionFailure(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func warn(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func info(_ message: @autoclosure () -> Swift.String = String()) - @objc deinit -} -extension Lottie.LottieLogger { - public static var printToConsole: Lottie.LottieLogger { - get - } -} -@available(*, deprecated, message: "Use DefaultAnimationCache instead, which is thread-safe and automatically responds to memory pressure.") -public typealias LRUAnimationCache = Lottie.DefaultAnimationCache -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedButton : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, event: UIKit.UIControl.Event) - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromMarker fromName: Swift.String, toMarker toName: Swift.String, event: UIKit.UIControl.Event) - @objc deinit -} -final public class PointValueProvider { - public init(block: @escaping Lottie.PointValueProvider.PointValueBlock) - public init(_ point: CoreGraphics.CGPoint) - public typealias PointValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGPoint - final public var point: CoreGraphics.CGPoint { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -final public class Keyframe { - public init(_ value: T, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - public init(value: T, time: Lottie.AnimationFrameTime, isHold: Swift.Bool = false, inTangent: Lottie.LottieVector2D? = nil, outTangent: Lottie.LottieVector2D? = nil, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - final public let value: T - final public let time: Lottie.AnimationFrameTime - final public let isHold: Swift.Bool - final public let inTangent: Lottie.LottieVector2D? - final public let outTangent: Lottie.LottieVector2D? - final public let spatialInTangent: Lottie.LottieVector3D? - final public let spatialOutTangent: Lottie.LottieVector3D? - @objc deinit -} -extension Lottie.Keyframe : Swift.Equatable where T : Swift.Equatable { - public static func == (lhs: Lottie.Keyframe, rhs: Lottie.Keyframe) -> Swift.Bool -} -extension Lottie.Keyframe : Swift.Hashable where T : Swift.Hashable { - final public func hash(into hasher: inout Swift.Hasher) - final public var hashValue: Swift.Int { - get - } -} -public protocol AnimationImageProvider { - func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? -} -public protocol Interpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -public protocol SpatialInterpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -public protocol AnyInterpolatable { - func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.Interpolatable { - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent _: CoreGraphics.CGPoint?, spatialInTangent _: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.SpatialInterpolatable { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Swift.Double : Lottie.Interpolatable { -} -extension CoreGraphics.CGFloat : Lottie.Interpolatable { -} -extension Swift.Float : Lottie.Interpolatable { -} -extension Lottie.Interpolatable where Self : Swift.BinaryFloatingPoint { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -extension CoreGraphics.CGRect : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGRect, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGRect -} -extension CoreGraphics.CGSize : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGSize, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGSize -} -extension CoreGraphics.CGPoint : Lottie.SpatialInterpolatable { - public func interpolate(to: CoreGraphics.CGPoint, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> CoreGraphics.CGPoint -} -extension Lottie.LottieColor : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieColor, amount: CoreGraphics.CGFloat) -> Lottie.LottieColor -} -extension Lottie.LottieVector1D : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieVector1D, amount: CoreGraphics.CGFloat) -> Lottie.LottieVector1D -} -extension Lottie.LottieVector2D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector2D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector2D -} -extension Lottie.LottieVector3D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector3D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector3D -} -extension Swift.Array : Lottie.Interpolatable, Lottie.AnyInterpolatable where Element : Lottie.Interpolatable { - public func interpolate(to: [Element], amount: CoreGraphics.CGFloat) -> [Element] -} -extension Lottie.CoordinateSpace : Swift.Equatable {} -extension Lottie.CoordinateSpace : Swift.Hashable {} -extension Lottie.CoordinateSpace : Swift.RawRepresentable {} -extension Lottie.SizeValueProvider : Lottie.AnyValueProvider {} -extension Lottie.ColorValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LottieBackgroundBehavior : Swift.Equatable {} -extension Lottie.LottieBackgroundBehavior : Swift.Hashable {} -extension Lottie.DotLottieError : Swift.Equatable {} -extension Lottie.DotLottieError : Swift.Hashable {} -extension Lottie.FloatValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LayerType : Swift.Equatable {} -extension Lottie.LayerType : Swift.Hashable {} -extension Lottie.LayerType : Swift.RawRepresentable {} -extension Lottie.MatteType : Swift.Equatable {} -extension Lottie.MatteType : Swift.Hashable {} -extension Lottie.MatteType : Swift.RawRepresentable {} -extension Lottie.BlendMode : Swift.Equatable {} -extension Lottie.BlendMode : Swift.Hashable {} -extension Lottie.BlendMode : Swift.RawRepresentable {} -extension Lottie.GradientValueProvider : Lottie.AnyValueProvider {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Equatable {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Hashable {} -extension Lottie.PointValueProvider : Lottie.AnyValueProvider {} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftmodule b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftmodule deleted file mode 100644 index 29f2d01fac795fff9bc151d4036471a2ac3940a0..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/arm64.swiftmodule and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftdoc deleted file mode 100644 index 48710bbfe8674c96ec799991c01dd313d75b0dec..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftdoc and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftinterface b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftinterface deleted file mode 100644 index 38e219282a0c3f3391f332c4e09b65ce5f835658..0000000000000000000000000000000000000000 --- a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +++ /dev/null @@ -1,946 +0,0 @@ -// swift-interface-format-version: 1.0 -// swift-compiler-version: Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30) -// swift-module-flags: -target x86_64-apple-ios11.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name Lottie -import Compression -import CoreFoundation -import CoreGraphics -import CoreText -import Foundation -import QuartzCore -import Swift -import UIKit -import _Concurrency -import zlib -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers final public class ImageAsset : Lottie.Asset { - final public let name: Swift.String - final public let directory: Swift.String - final public let width: Swift.Double - final public let height: Swift.Double - override final public func encode(to encoder: Swift.Encoder) throws - @objc deinit -} -extension QuartzCore.CALayer { - @nonobjc public func logLayerTree(withIndent: Swift.Int = 0) -} -public protocol AnimationFontProvider { - func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? -} -final public class DefaultFontProvider : Lottie.AnimationFontProvider { - public init() - final public func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? - @objc deinit -} -public enum LottieAnimationCache { - public static var shared: Lottie.AnimationCacheProvider? -} -public class DefaultAnimationCache : Lottie.AnimationCacheProvider { - public init() - public static let sharedCache: Lottie.DefaultAnimationCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func animation(forKey key: Swift.String) -> Lottie.LottieAnimation? - public func setAnimation(_ animation: Lottie.LottieAnimation, forKey key: Swift.String) - @objc deinit -} -public class FilepathImageProvider : Lottie.AnimationImageProvider { - public init(filepath: Swift.String) - public init(filepath: Foundation.URL) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -public protocol AnimationTextProvider : AnyObject { - func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String -} -final public class DictionaryTextProvider : Lottie.AnimationTextProvider { - public init(_ values: [Swift.String : Swift.String]) - final public func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -final public class DefaultTextProvider : Lottie.AnimationTextProvider { - public init() - final public func textFor(keypathName _: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -public enum CoordinateSpace : Swift.Int, Swift.Codable { - case type2d - case type3d - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class LottieAnimation : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - public init(dictionary: [Swift.String : Any]) throws - final public let startFrame: Lottie.AnimationFrameTime - final public let endFrame: Lottie.AnimationFrameTime - final public let framerate: Swift.Double - final public var markerNames: [Swift.String] { - get - } - @objc deinit - final public func encode(to encoder: Swift.Encoder) throws -} -extension UIKit.UIColor { - public var lottieColorValue: Lottie.LottieColor { - get - } -} -public protocol AnimationCacheProvider : AnyObject { - func animation(forKey: Swift.String) -> Lottie.LottieAnimation? - func setAnimation(_ animation: Lottie.LottieAnimation, forKey: Swift.String) - func clearCache() -} -public typealias LottieCompletionBlock = (Swift.Bool) -> Swift.Void -@_hasMissingDesignatedInitializers public class Asset : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - final public let id: Swift.String - @objc deinit - public func encode(to encoder: Swift.Encoder) throws -} -final public class SizeValueProvider { - public init(block: @escaping Lottie.SizeValueProvider.SizeValueBlock) - public init(_ size: CoreGraphics.CGSize) - public typealias SizeValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGSize - final public var size: CoreGraphics.CGSize { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -infix operator +| : DefaultPrecedence -infix operator +- : DefaultPrecedence -@objc final public class CompatibleAnimation : ObjectiveC.NSObject { - @objc public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main) - @objc deinit -} -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc @_Concurrency.MainActor(unsafe) final public class CompatibleAnimationView : UIKit.UIView { - @objc @_Concurrency.MainActor(unsafe) public init(compatibleAnimation: Lottie.CompatibleAnimation) - @objc @_Concurrency.MainActor(unsafe) override dynamic public init(frame: CoreGraphics.CGRect) - @objc @_Concurrency.MainActor(unsafe) final public var compatibleAnimation: Lottie.CompatibleAnimation? { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var loopAnimationCount: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) override final public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentProgress: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentFrame: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public func play() - @objc @_Concurrency.MainActor(unsafe) final public func play(completion: ((Swift.Bool) -> Swift.Void)?) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromProgress: CoreGraphics.CGFloat, toProgress: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromFrame: CoreGraphics.CGFloat, toFrame: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String, toMarker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func stop() - @objc @_Concurrency.MainActor(unsafe) final public func pause() - @objc @_Concurrency.MainActor(unsafe) final public func reloadImages() - @objc @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @objc @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> Any? - @objc @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @objc @_Concurrency.MainActor(unsafe) final public func setColorValue(_ color: UIKit.UIColor, forKeypath keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func getColorValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> UIKit.UIColor? - @objc @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func convert(rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGRect - @objc @_Concurrency.MainActor(unsafe) final public func convert(point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGPoint - @objc @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc deinit -} -public typealias AnimationFrameTime = CoreGraphics.CGFloat -public typealias AnimationProgressTime = CoreGraphics.CGFloat -public enum ColorFormatDenominator : Swift.Hashable { - case One - case OneHundred - case TwoFiftyFive - public static func == (a: Lottie.ColorFormatDenominator, b: Lottie.ColorFormatDenominator) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public struct LottieColor : Swift.Hashable { - public var r: Swift.Double - public var g: Swift.Double - public var b: Swift.Double - public var a: Swift.Double - public init(r: Swift.Double, g: Swift.Double, b: Swift.Double, a: Swift.Double, denominator: Lottie.ColorFormatDenominator = .One) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieColor, b: Lottie.LottieColor) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -final public class ColorValueProvider { - public init(block: @escaping Lottie.ColorValueProvider.ColorValueBlock) - public init(_ color: Lottie.LottieColor) - public init(_ keyframes: [Lottie.Keyframe]) - public typealias ColorValueBlock = (CoreGraphics.CGFloat) -> Lottie.LottieColor - final public var color: Lottie.LottieColor { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -public struct AnimationKeypath : Swift.Hashable, Swift.ExpressibleByStringLiteral { - public init(keypath: Swift.String) - public init(stringLiteral: Swift.String) - public init(keys: [Swift.String]) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.AnimationKeypath, b: Lottie.AnimationKeypath) -> Swift.Bool - public typealias ExtendedGraphemeClusterLiteralType = Swift.String - public typealias StringLiteralType = Swift.String - public typealias UnicodeScalarLiteralType = Swift.String - public var hashValue: Swift.Int { - get - } -} -public struct LottieConfiguration : Swift.Hashable { - public init(renderingEngine: Lottie.RenderingEngineOption = .automatic, decodingStrategy: Lottie.DecodingStrategy = .dictionaryBased) - public static var shared: Lottie.LottieConfiguration - public var renderingEngine: Lottie.RenderingEngineOption - public var decodingStrategy: Lottie.DecodingStrategy - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieConfiguration, b: Lottie.LottieConfiguration) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum RenderingEngineOption : Swift.Hashable { - case automatic - case specific(Lottie.RenderingEngine) - public static var mainThread: Lottie.RenderingEngineOption { - get - } - public static var coreAnimation: Lottie.RenderingEngineOption { - get - } -} -public enum RenderingEngine : Swift.Hashable { - case mainThread - case coreAnimation -} -extension Lottie.RenderingEngineOption : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -extension Lottie.RenderingEngine : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -public enum DecodingStrategy : Swift.Hashable { - case legacyCodable - case dictionaryBased - public static func == (a: Lottie.DecodingStrategy, b: Lottie.DecodingStrategy) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector1D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -public struct LottieVector2D : Swift.Codable, Swift.Hashable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector2D, b: Lottie.LottieVector2D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector3D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.LottieVector3D { - public var pointValue: CoreGraphics.CGPoint { - get - } - public var sizeValue: CoreGraphics.CGSize { - get - } -} -extension Lottie.LottieAnimationView { - @_Concurrency.MainActor(unsafe) convenience public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(filePath: Swift.String, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(url: Foundation.URL, imageProvider: Lottie.AnimationImageProvider? = nil, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimationView.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(asset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieName name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieFilePath filePath: Swift.String, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieUrl url: Foundation.URL, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, session: Foundation.URLSession = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieAsset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - public typealias DownloadClosure = (Swift.Error?) -> Swift.Void -} -public protocol AnyValueProvider { - var valueType: Any.Type { get } - var typeErasedStorage: Lottie.AnyValueProviderStorage { get } - func hasUpdate(frame: Lottie.AnimationFrameTime) -> Swift.Bool -} -extension Lottie.AnyValueProvider { - public func value(frame: Lottie.AnimationFrameTime) -> Any -} -public enum ValueProviderStorage where T : Lottie.AnyInterpolatable { - case singleValue(T) - case keyframes([Lottie.Keyframe]) - case closure((Lottie.AnimationFrameTime) -> T) -} -public enum AnyValueProviderStorage { - case singleValue(Any) - case keyframes([Lottie.Keyframe], interpolate: (Lottie.AnimationFrameTime) -> Any) - case closure((Lottie.AnimationFrameTime) -> Any) -} -public struct LottieVector1D : Swift.Hashable { - public init(_ value: Swift.Double) - public let value: Swift.Double - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector1D, b: Lottie.LottieVector1D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public struct LottieVector3D : Swift.Hashable { - public let x: Swift.Double - public let y: Swift.Double - public let z: Swift.Double - public init(x: Swift.Double, y: Swift.Double, z: Swift.Double) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector3D, b: Lottie.LottieVector3D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum LottieBackgroundBehavior { - case stop - case pause - case pauseAndRestore - case forceFinish - case continuePlaying - public static func `default`(for renderingEngine: Lottie.RenderingEngine) -> Lottie.LottieBackgroundBehavior - public static func == (a: Lottie.LottieBackgroundBehavior, b: Lottie.LottieBackgroundBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public enum LottieLoopMode { - case playOnce - case loop - case autoReverse - case `repeat`(Swift.Float) - case repeatBackwards(Swift.Float) -} -extension Lottie.LottieLoopMode : Swift.Equatable { - public static func == (lhs: Lottie.LottieLoopMode, rhs: Lottie.LottieLoopMode) -> Swift.Bool -} -@objc @_inheritsConvenienceInitializers @IBDesignable @_Concurrency.MainActor(unsafe) final public class LottieAnimationView : Lottie.LottieAnimationViewBase { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation?, imageProvider: Lottie.AnimationImageProvider? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(dotLottie: Lottie.DotLottieFile?, animationId: Swift.String? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) final public let configuration: Lottie.LottieConfiguration - @_Concurrency.MainActor(unsafe) final public var valueProviders: [Lottie.AnimationKeypath : Lottie.AnyValueProvider] { - get - } - @_Concurrency.MainActor(unsafe) final public var backgroundBehavior: Lottie.LottieBackgroundBehavior { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animationLoaded: ((_ animationView: Lottie.LottieAnimationView, _ animation: Lottie.LottieAnimation) -> Swift.Void)? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var imageProvider: Lottie.AnimationImageProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var textProvider: Lottie.AnimationTextProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var fontProvider: Lottie.AnimationFontProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var isAnimationQueued: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var loopMode: Lottie.LottieLoopMode { - get - set - } - @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentProgress: Lottie.AnimationProgressTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentFrame: Lottie.AnimationFrameTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: Lottie.AnimationFrameTime { - get - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: Lottie.AnimationProgressTime { - get - } - @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var viewportFrame: CoreGraphics.CGRect? { - get - set - } - @_Concurrency.MainActor(unsafe) @objc override final public var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) final public var currentRenderingEngine: Lottie.RenderingEngine? { - get - } - @_Concurrency.MainActor(unsafe) final public func loadAnimation(_ animationId: Swift.String? = nil, from dotLottieFile: Lottie.DotLottieFile) - @_Concurrency.MainActor(unsafe) final public func play(completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromProgress: Lottie.AnimationProgressTime? = nil, toProgress: Lottie.AnimationProgressTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromFrame: Lottie.AnimationFrameTime? = nil, toFrame: Lottie.AnimationFrameTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String? = nil, toMarker: Swift.String, playEndMarkerFrame: Swift.Bool = true, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func stop() - @_Concurrency.MainActor(unsafe) final public func pause() - @_Concurrency.MainActor(unsafe) final public func reloadImages() - @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @_Concurrency.MainActor(unsafe) final public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func getOriginalValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func convert(_ rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGRect? - @_Concurrency.MainActor(unsafe) final public func convert(_ point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGPoint? - @_Concurrency.MainActor(unsafe) final public func setNodeIsEnabled(isEnabled: Swift.Bool, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @objc deinit -} -@objc @_Concurrency.MainActor(unsafe) open class AnimatedControl : UIKit.UIControl { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isEnabled: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isSelected: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isHighlighted: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func continueTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func cancelTracking(with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) open func animationDidSet() - @_Concurrency.MainActor(unsafe) final public let animationView: Lottie.LottieAnimationView - @_Concurrency.MainActor(unsafe) public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) public func setLayer(named: Swift.String, forState: UIKit.UIControl.State) - @_Concurrency.MainActor(unsafe) public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @objc deinit -} -public enum DotLottieError : Swift.Error { - case invalidFileFormat - case invalidData - case animationNotAvailable - public static func == (a: Lottie.DotLottieError, b: Lottie.DotLottieError) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -final public class FloatValueProvider { - public init(block: @escaping Lottie.FloatValueProvider.CGFloatValueBlock) - public init(_ float: CoreGraphics.CGFloat) - public typealias CGFloatValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGFloat - final public var float: CoreGraphics.CGFloat { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc final public class CompatibleAnimationKeypath : ObjectiveC.NSObject { - @objc public init(keypath: Swift.String) - @objc public init(keys: [Swift.String]) - final public let animationKeypath: Lottie.AnimationKeypath - @objc deinit -} -extension Lottie.LottieColor : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.DotLottieFile { - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) -} -public protocol DotLottieCacheProvider { - func file(forKey: Swift.String) -> Lottie.DotLottieFile? - func setFile(_ lottie: Lottie.DotLottieFile, forKey: Swift.String) - func clearCache() -} -public class DotLottieCache : Lottie.DotLottieCacheProvider { - public init() - public static let sharedCache: Lottie.DotLottieCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func file(forKey key: Swift.String) -> Lottie.DotLottieFile? - public func setFile(_ lottie: Lottie.DotLottieFile, forKey key: Swift.String) - @objc deinit -} -extension Lottie.LottieAnimation { - public typealias DownloadClosure = (Lottie.LottieAnimation?) -> Swift.Void - final public var duration: Foundation.TimeInterval { - get - } - final public var bounds: CoreGraphics.CGRect { - get - } - final public var size: CoreGraphics.CGSize { - get - } - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func filepath(_ filepath: Swift.String, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func asset(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func from(data: Foundation.Data, strategy: Lottie.DecodingStrategy = LottieConfiguration.shared.decodingStrategy) throws -> Lottie.LottieAnimation - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) async -> Lottie.LottieAnimation? - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimation.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) - final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func progressTime(forFrame frameTime: Lottie.AnimationFrameTime, clamped: Swift.Bool = true) -> Lottie.AnimationProgressTime - final public func frameTime(forProgress progressTime: Lottie.AnimationProgressTime) -> Lottie.AnimationFrameTime - final public func time(forFrame frameTime: Lottie.AnimationFrameTime) -> Foundation.TimeInterval - final public func frameTime(forTime time: Foundation.TimeInterval) -> Lottie.AnimationFrameTime -} -extension Foundation.Bundle : @unchecked Swift.Sendable { -} -public enum LayerType : Swift.Int, Swift.Codable { - case precomp - case solid - case image - case null - case shape - case text - public init(from decoder: Swift.Decoder) throws - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum MatteType : Swift.Int, Swift.Codable { - case none - case add - case invert - case unknown - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum BlendMode : Swift.Int, Swift.Codable { - case normal - case multiply - case screen - case overlay - case darken - case lighten - case colorDodge - case colorBurn - case hardLight - case softLight - case difference - case exclusion - case hue - case saturation - case color - case luminosity - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class GradientValueProvider { - public init(block: @escaping Lottie.GradientValueProvider.ColorsValueBlock, locations: Lottie.GradientValueProvider.ColorLocationsBlock? = nil) - public init(_ colors: [Lottie.LottieColor], locations: [Swift.Double] = []) - public typealias ColorsValueBlock = (CoreGraphics.CGFloat) -> [Lottie.LottieColor] - public typealias ColorLocationsBlock = (CoreGraphics.CGFloat) -> [Swift.Double] - final public var colors: [Lottie.LottieColor] { - get - set - } - final public var locations: [Swift.Double] { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage<[Swift.Double]> { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedSwitch : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) override open func animationDidSet() - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - public enum CancelBehavior { - case reverse - case none - public static func == (a: Lottie.AnimatedSwitch.CancelBehavior, b: Lottie.AnimatedSwitch.CancelBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } - } - @_Concurrency.MainActor(unsafe) public var cancelBehavior: Lottie.AnimatedSwitch.CancelBehavior - @_Concurrency.MainActor(unsafe) public var animateUpdateWhenChangingAnimation: Swift.Bool - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public var isOn: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) public func setIsOn(_ isOn: Swift.Bool, animated: Swift.Bool, shouldFireHaptics: Swift.Bool = true) - @_Concurrency.MainActor(unsafe) public func setProgressForState(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, forOnState: Swift.Bool) - @objc deinit -} -public class BundleImageProvider : Lottie.AnimationImageProvider { - public init(bundle: Foundation.Bundle, searchPath: Swift.String?) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) final public class AnimationSubview : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LottieAnimationViewBase : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic public func didMoveToWindow() - @_Concurrency.MainActor(unsafe) @objc override dynamic public func layoutSubviews() - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@_hasMissingDesignatedInitializers final public class DotLottieFile { - @objc deinit -} -final public class LottieLogger { - public init(assert: @escaping Lottie.LottieLogger.Assert = { condition, message, file, line in - // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assert(condition(), message(), file: file, line: line) - }, assertionFailure: @escaping Lottie.LottieLogger.AssertionFailure = { message, file, line in - // If we default to `Swift.assertionFailure` directly with - // `assertionFailure: AssertionFailure = Swift.assertionFailure`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assertionFailure(message(), file: file, line: line) - }, warn: @escaping Lottie.LottieLogger.Warn = { message, _, _ in - }, info: @escaping Lottie.LottieLogger.Info = { message in - }) - public typealias Assert = (_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias AssertionFailure = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Warn = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Info = (_ message: @autoclosure () -> Swift.String) -> Swift.Void - public static var shared: Lottie.LottieLogger - final public func assert(_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func assertionFailure(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func warn(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func info(_ message: @autoclosure () -> Swift.String = String()) - @objc deinit -} -extension Lottie.LottieLogger { - public static var printToConsole: Lottie.LottieLogger { - get - } -} -@available(*, deprecated, message: "Use DefaultAnimationCache instead, which is thread-safe and automatically responds to memory pressure.") -public typealias LRUAnimationCache = Lottie.DefaultAnimationCache -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedButton : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, event: UIKit.UIControl.Event) - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromMarker fromName: Swift.String, toMarker toName: Swift.String, event: UIKit.UIControl.Event) - @objc deinit -} -final public class PointValueProvider { - public init(block: @escaping Lottie.PointValueProvider.PointValueBlock) - public init(_ point: CoreGraphics.CGPoint) - public typealias PointValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGPoint - final public var point: CoreGraphics.CGPoint { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -final public class Keyframe { - public init(_ value: T, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - public init(value: T, time: Lottie.AnimationFrameTime, isHold: Swift.Bool = false, inTangent: Lottie.LottieVector2D? = nil, outTangent: Lottie.LottieVector2D? = nil, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - final public let value: T - final public let time: Lottie.AnimationFrameTime - final public let isHold: Swift.Bool - final public let inTangent: Lottie.LottieVector2D? - final public let outTangent: Lottie.LottieVector2D? - final public let spatialInTangent: Lottie.LottieVector3D? - final public let spatialOutTangent: Lottie.LottieVector3D? - @objc deinit -} -extension Lottie.Keyframe : Swift.Equatable where T : Swift.Equatable { - public static func == (lhs: Lottie.Keyframe, rhs: Lottie.Keyframe) -> Swift.Bool -} -extension Lottie.Keyframe : Swift.Hashable where T : Swift.Hashable { - final public func hash(into hasher: inout Swift.Hasher) - final public var hashValue: Swift.Int { - get - } -} -public protocol AnimationImageProvider { - func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? -} -public protocol Interpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -public protocol SpatialInterpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -public protocol AnyInterpolatable { - func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.Interpolatable { - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent _: CoreGraphics.CGPoint?, spatialInTangent _: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.SpatialInterpolatable { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Swift.Double : Lottie.Interpolatable { -} -extension CoreGraphics.CGFloat : Lottie.Interpolatable { -} -extension Swift.Float : Lottie.Interpolatable { -} -extension Lottie.Interpolatable where Self : Swift.BinaryFloatingPoint { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -extension CoreGraphics.CGRect : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGRect, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGRect -} -extension CoreGraphics.CGSize : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGSize, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGSize -} -extension CoreGraphics.CGPoint : Lottie.SpatialInterpolatable { - public func interpolate(to: CoreGraphics.CGPoint, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> CoreGraphics.CGPoint -} -extension Lottie.LottieColor : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieColor, amount: CoreGraphics.CGFloat) -> Lottie.LottieColor -} -extension Lottie.LottieVector1D : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieVector1D, amount: CoreGraphics.CGFloat) -> Lottie.LottieVector1D -} -extension Lottie.LottieVector2D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector2D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector2D -} -extension Lottie.LottieVector3D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector3D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector3D -} -extension Swift.Array : Lottie.Interpolatable, Lottie.AnyInterpolatable where Element : Lottie.Interpolatable { - public func interpolate(to: [Element], amount: CoreGraphics.CGFloat) -> [Element] -} -extension Lottie.CoordinateSpace : Swift.Equatable {} -extension Lottie.CoordinateSpace : Swift.Hashable {} -extension Lottie.CoordinateSpace : Swift.RawRepresentable {} -extension Lottie.SizeValueProvider : Lottie.AnyValueProvider {} -extension Lottie.ColorValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LottieBackgroundBehavior : Swift.Equatable {} -extension Lottie.LottieBackgroundBehavior : Swift.Hashable {} -extension Lottie.DotLottieError : Swift.Equatable {} -extension Lottie.DotLottieError : Swift.Hashable {} -extension Lottie.FloatValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LayerType : Swift.Equatable {} -extension Lottie.LayerType : Swift.Hashable {} -extension Lottie.LayerType : Swift.RawRepresentable {} -extension Lottie.MatteType : Swift.Equatable {} -extension Lottie.MatteType : Swift.Hashable {} -extension Lottie.MatteType : Swift.RawRepresentable {} -extension Lottie.BlendMode : Swift.Equatable {} -extension Lottie.BlendMode : Swift.Hashable {} -extension Lottie.BlendMode : Swift.RawRepresentable {} -extension Lottie.GradientValueProvider : Lottie.AnyValueProvider {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Equatable {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Hashable {} -extension Lottie.PointValueProvider : Lottie.AnyValueProvider {} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftmodule b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftmodule deleted file mode 100644 index 06f7dce4e0f83b0304d6e21ced3653d1959a23ac..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64-apple-ios-simulator.swiftmodule and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftdoc b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftdoc deleted file mode 100644 index 48710bbfe8674c96ec799991c01dd313d75b0dec..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftdoc and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftinterface b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftinterface deleted file mode 100644 index 38e219282a0c3f3391f332c4e09b65ce5f835658..0000000000000000000000000000000000000000 --- a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftinterface +++ /dev/null @@ -1,946 +0,0 @@ -// swift-interface-format-version: 1.0 -// swift-compiler-version: Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30) -// swift-module-flags: -target x86_64-apple-ios11.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name Lottie -import Compression -import CoreFoundation -import CoreGraphics -import CoreText -import Foundation -import QuartzCore -import Swift -import UIKit -import _Concurrency -import zlib -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers final public class ImageAsset : Lottie.Asset { - final public let name: Swift.String - final public let directory: Swift.String - final public let width: Swift.Double - final public let height: Swift.Double - override final public func encode(to encoder: Swift.Encoder) throws - @objc deinit -} -extension QuartzCore.CALayer { - @nonobjc public func logLayerTree(withIndent: Swift.Int = 0) -} -public protocol AnimationFontProvider { - func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? -} -final public class DefaultFontProvider : Lottie.AnimationFontProvider { - public init() - final public func fontFor(family: Swift.String, size: CoreGraphics.CGFloat) -> CoreText.CTFont? - @objc deinit -} -public enum LottieAnimationCache { - public static var shared: Lottie.AnimationCacheProvider? -} -public class DefaultAnimationCache : Lottie.AnimationCacheProvider { - public init() - public static let sharedCache: Lottie.DefaultAnimationCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func animation(forKey key: Swift.String) -> Lottie.LottieAnimation? - public func setAnimation(_ animation: Lottie.LottieAnimation, forKey key: Swift.String) - @objc deinit -} -public class FilepathImageProvider : Lottie.AnimationImageProvider { - public init(filepath: Swift.String) - public init(filepath: Foundation.URL) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -public protocol AnimationTextProvider : AnyObject { - func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String -} -final public class DictionaryTextProvider : Lottie.AnimationTextProvider { - public init(_ values: [Swift.String : Swift.String]) - final public func textFor(keypathName: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -final public class DefaultTextProvider : Lottie.AnimationTextProvider { - public init() - final public func textFor(keypathName _: Swift.String, sourceText: Swift.String) -> Swift.String - @objc deinit -} -public enum CoordinateSpace : Swift.Int, Swift.Codable { - case type2d - case type3d - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class LottieAnimation : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - public init(dictionary: [Swift.String : Any]) throws - final public let startFrame: Lottie.AnimationFrameTime - final public let endFrame: Lottie.AnimationFrameTime - final public let framerate: Swift.Double - final public var markerNames: [Swift.String] { - get - } - @objc deinit - final public func encode(to encoder: Swift.Encoder) throws -} -extension UIKit.UIColor { - public var lottieColorValue: Lottie.LottieColor { - get - } -} -public protocol AnimationCacheProvider : AnyObject { - func animation(forKey: Swift.String) -> Lottie.LottieAnimation? - func setAnimation(_ animation: Lottie.LottieAnimation, forKey: Swift.String) - func clearCache() -} -public typealias LottieCompletionBlock = (Swift.Bool) -> Swift.Void -@_hasMissingDesignatedInitializers public class Asset : Swift.Codable { - required public init(from decoder: Swift.Decoder) throws - final public let id: Swift.String - @objc deinit - public func encode(to encoder: Swift.Encoder) throws -} -final public class SizeValueProvider { - public init(block: @escaping Lottie.SizeValueProvider.SizeValueBlock) - public init(_ size: CoreGraphics.CGSize) - public typealias SizeValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGSize - final public var size: CoreGraphics.CGSize { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -infix operator +| : DefaultPrecedence -infix operator +- : DefaultPrecedence -@objc final public class CompatibleAnimation : ObjectiveC.NSObject { - @objc public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main) - @objc deinit -} -@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc @_Concurrency.MainActor(unsafe) final public class CompatibleAnimationView : UIKit.UIView { - @objc @_Concurrency.MainActor(unsafe) public init(compatibleAnimation: Lottie.CompatibleAnimation) - @objc @_Concurrency.MainActor(unsafe) override dynamic public init(frame: CoreGraphics.CGRect) - @objc @_Concurrency.MainActor(unsafe) final public var compatibleAnimation: Lottie.CompatibleAnimation? { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var loopAnimationCount: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) override final public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentProgress: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var currentFrame: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: CoreGraphics.CGFloat { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - @objc get - @objc set - } - @objc @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - @objc get - } - @objc @_Concurrency.MainActor(unsafe) final public func play() - @objc @_Concurrency.MainActor(unsafe) final public func play(completion: ((Swift.Bool) -> Swift.Void)?) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromProgress: CoreGraphics.CGFloat, toProgress: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromFrame: CoreGraphics.CGFloat, toFrame: CoreGraphics.CGFloat, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String, toMarker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, completion: ((Swift.Bool) -> Swift.Void)? = nil) - @objc @_Concurrency.MainActor(unsafe) final public func stop() - @objc @_Concurrency.MainActor(unsafe) final public func pause() - @objc @_Concurrency.MainActor(unsafe) final public func reloadImages() - @objc @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @objc @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> Any? - @objc @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @objc @_Concurrency.MainActor(unsafe) final public func setColorValue(_ color: UIKit.UIColor, forKeypath keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func getColorValue(for keypath: Lottie.CompatibleAnimationKeypath, atFrame: CoreGraphics.CGFloat) -> UIKit.UIColor? - @objc @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.CompatibleAnimationKeypath) - @objc @_Concurrency.MainActor(unsafe) final public func convert(rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGRect - @objc @_Concurrency.MainActor(unsafe) final public func convert(point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.CompatibleAnimationKeypath?) -> CoreGraphics.CGPoint - @objc @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> CoreGraphics.CGFloat - @objc deinit -} -public typealias AnimationFrameTime = CoreGraphics.CGFloat -public typealias AnimationProgressTime = CoreGraphics.CGFloat -public enum ColorFormatDenominator : Swift.Hashable { - case One - case OneHundred - case TwoFiftyFive - public static func == (a: Lottie.ColorFormatDenominator, b: Lottie.ColorFormatDenominator) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public struct LottieColor : Swift.Hashable { - public var r: Swift.Double - public var g: Swift.Double - public var b: Swift.Double - public var a: Swift.Double - public init(r: Swift.Double, g: Swift.Double, b: Swift.Double, a: Swift.Double, denominator: Lottie.ColorFormatDenominator = .One) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieColor, b: Lottie.LottieColor) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -final public class ColorValueProvider { - public init(block: @escaping Lottie.ColorValueProvider.ColorValueBlock) - public init(_ color: Lottie.LottieColor) - public init(_ keyframes: [Lottie.Keyframe]) - public typealias ColorValueBlock = (CoreGraphics.CGFloat) -> Lottie.LottieColor - final public var color: Lottie.LottieColor { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -public struct AnimationKeypath : Swift.Hashable, Swift.ExpressibleByStringLiteral { - public init(keypath: Swift.String) - public init(stringLiteral: Swift.String) - public init(keys: [Swift.String]) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.AnimationKeypath, b: Lottie.AnimationKeypath) -> Swift.Bool - public typealias ExtendedGraphemeClusterLiteralType = Swift.String - public typealias StringLiteralType = Swift.String - public typealias UnicodeScalarLiteralType = Swift.String - public var hashValue: Swift.Int { - get - } -} -public struct LottieConfiguration : Swift.Hashable { - public init(renderingEngine: Lottie.RenderingEngineOption = .automatic, decodingStrategy: Lottie.DecodingStrategy = .dictionaryBased) - public static var shared: Lottie.LottieConfiguration - public var renderingEngine: Lottie.RenderingEngineOption - public var decodingStrategy: Lottie.DecodingStrategy - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieConfiguration, b: Lottie.LottieConfiguration) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum RenderingEngineOption : Swift.Hashable { - case automatic - case specific(Lottie.RenderingEngine) - public static var mainThread: Lottie.RenderingEngineOption { - get - } - public static var coreAnimation: Lottie.RenderingEngineOption { - get - } -} -public enum RenderingEngine : Swift.Hashable { - case mainThread - case coreAnimation -} -extension Lottie.RenderingEngineOption : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -extension Lottie.RenderingEngine : Swift.RawRepresentable, Swift.CustomStringConvertible { - public init?(rawValue: Swift.String) - public var rawValue: Swift.String { - get - } - public var description: Swift.String { - get - } - public typealias RawValue = Swift.String -} -public enum DecodingStrategy : Swift.Hashable { - case legacyCodable - case dictionaryBased - public static func == (a: Lottie.DecodingStrategy, b: Lottie.DecodingStrategy) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector1D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -public struct LottieVector2D : Swift.Codable, Swift.Hashable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector2D, b: Lottie.LottieVector2D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -extension Lottie.LottieVector3D : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.LottieVector3D { - public var pointValue: CoreGraphics.CGPoint { - get - } - public var sizeValue: CoreGraphics.CGSize { - get - } -} -extension Lottie.LottieAnimationView { - @_Concurrency.MainActor(unsafe) convenience public init(name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(filePath: Swift.String, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(url: Foundation.URL, imageProvider: Lottie.AnimationImageProvider? = nil, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimationView.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(asset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, imageProvider: Lottie.AnimationImageProvider? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared, configuration: Lottie.LottieConfiguration = .shared) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieName name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieFilePath filePath: Swift.String, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieUrl url: Foundation.URL, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, session: Foundation.URLSession = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - @_Concurrency.MainActor(unsafe) convenience public init(dotLottieAsset name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationId: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, configuration: Lottie.LottieConfiguration = .shared, completion: ((Lottie.LottieAnimationView, Swift.Error?) -> Swift.Void)? = nil) - public typealias DownloadClosure = (Swift.Error?) -> Swift.Void -} -public protocol AnyValueProvider { - var valueType: Any.Type { get } - var typeErasedStorage: Lottie.AnyValueProviderStorage { get } - func hasUpdate(frame: Lottie.AnimationFrameTime) -> Swift.Bool -} -extension Lottie.AnyValueProvider { - public func value(frame: Lottie.AnimationFrameTime) -> Any -} -public enum ValueProviderStorage where T : Lottie.AnyInterpolatable { - case singleValue(T) - case keyframes([Lottie.Keyframe]) - case closure((Lottie.AnimationFrameTime) -> T) -} -public enum AnyValueProviderStorage { - case singleValue(Any) - case keyframes([Lottie.Keyframe], interpolate: (Lottie.AnimationFrameTime) -> Any) - case closure((Lottie.AnimationFrameTime) -> Any) -} -public struct LottieVector1D : Swift.Hashable { - public init(_ value: Swift.Double) - public let value: Swift.Double - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector1D, b: Lottie.LottieVector1D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public struct LottieVector3D : Swift.Hashable { - public let x: Swift.Double - public let y: Swift.Double - public let z: Swift.Double - public init(x: Swift.Double, y: Swift.Double, z: Swift.Double) - public func hash(into hasher: inout Swift.Hasher) - public static func == (a: Lottie.LottieVector3D, b: Lottie.LottieVector3D) -> Swift.Bool - public var hashValue: Swift.Int { - get - } -} -public enum LottieBackgroundBehavior { - case stop - case pause - case pauseAndRestore - case forceFinish - case continuePlaying - public static func `default`(for renderingEngine: Lottie.RenderingEngine) -> Lottie.LottieBackgroundBehavior - public static func == (a: Lottie.LottieBackgroundBehavior, b: Lottie.LottieBackgroundBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -public enum LottieLoopMode { - case playOnce - case loop - case autoReverse - case `repeat`(Swift.Float) - case repeatBackwards(Swift.Float) -} -extension Lottie.LottieLoopMode : Swift.Equatable { - public static func == (lhs: Lottie.LottieLoopMode, rhs: Lottie.LottieLoopMode) -> Swift.Bool -} -@objc @_inheritsConvenienceInitializers @IBDesignable @_Concurrency.MainActor(unsafe) final public class LottieAnimationView : Lottie.LottieAnimationViewBase { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation?, imageProvider: Lottie.AnimationImageProvider? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(dotLottie: Lottie.DotLottieFile?, animationId: Swift.String? = nil, textProvider: Lottie.AnimationTextProvider = DefaultTextProvider(), fontProvider: Lottie.AnimationFontProvider = DefaultFontProvider(), configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) public init(configuration: Lottie.LottieConfiguration = .shared, logger: Lottie.LottieLogger = .shared) - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) final public let configuration: Lottie.LottieConfiguration - @_Concurrency.MainActor(unsafe) final public var valueProviders: [Lottie.AnimationKeypath : Lottie.AnyValueProvider] { - get - } - @_Concurrency.MainActor(unsafe) final public var backgroundBehavior: Lottie.LottieBackgroundBehavior { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var animationLoaded: ((_ animationView: Lottie.LottieAnimationView, _ animation: Lottie.LottieAnimation) -> Swift.Void)? { - get - set - } - @_Concurrency.MainActor(unsafe) final public var imageProvider: Lottie.AnimationImageProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var textProvider: Lottie.AnimationTextProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var fontProvider: Lottie.AnimationFontProvider { - get - set - } - @_Concurrency.MainActor(unsafe) final public var isAnimationPlaying: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var isAnimationQueued: Swift.Bool { - get - } - @_Concurrency.MainActor(unsafe) final public var loopMode: Lottie.LottieLoopMode { - get - set - } - @_Concurrency.MainActor(unsafe) final public var shouldRasterizeWhenIdle: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentProgress: Lottie.AnimationProgressTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentTime: Foundation.TimeInterval { - get - set - } - @_Concurrency.MainActor(unsafe) final public var currentFrame: Lottie.AnimationFrameTime { - get - set - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationFrame: Lottie.AnimationFrameTime { - get - } - @_Concurrency.MainActor(unsafe) final public var realtimeAnimationProgress: Lottie.AnimationProgressTime { - get - } - @_Concurrency.MainActor(unsafe) final public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) final public var respectAnimationFrameRate: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) final public var viewportFrame: CoreGraphics.CGRect? { - get - set - } - @_Concurrency.MainActor(unsafe) @objc override final public var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) final public var currentRenderingEngine: Lottie.RenderingEngine? { - get - } - @_Concurrency.MainActor(unsafe) final public func loadAnimation(_ animationId: Swift.String? = nil, from dotLottieFile: Lottie.DotLottieFile) - @_Concurrency.MainActor(unsafe) final public func play(completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromProgress: Lottie.AnimationProgressTime? = nil, toProgress: Lottie.AnimationProgressTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromFrame: Lottie.AnimationFrameTime? = nil, toFrame: Lottie.AnimationFrameTime, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(fromMarker: Swift.String? = nil, toMarker: Swift.String, playEndMarkerFrame: Swift.Bool = true, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func play(marker: Swift.String, loopMode: Lottie.LottieLoopMode? = nil, completion: Lottie.LottieCompletionBlock? = nil) - @_Concurrency.MainActor(unsafe) final public func stop() - @_Concurrency.MainActor(unsafe) final public func pause() - @_Concurrency.MainActor(unsafe) final public func reloadImages() - @_Concurrency.MainActor(unsafe) final public func forceDisplayUpdate() - @_Concurrency.MainActor(unsafe) final public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func getValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func getOriginalValue(for keypath: Lottie.AnimationKeypath, atFrame: Lottie.AnimationFrameTime?) -> Any? - @_Concurrency.MainActor(unsafe) final public func logHierarchyKeypaths() - @_Concurrency.MainActor(unsafe) final public func addSubview(_ subview: Lottie.AnimationSubview, forLayerAt keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func convert(_ rect: CoreGraphics.CGRect, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGRect? - @_Concurrency.MainActor(unsafe) final public func convert(_ point: CoreGraphics.CGPoint, toLayerAt keypath: Lottie.AnimationKeypath?) -> CoreGraphics.CGPoint? - @_Concurrency.MainActor(unsafe) final public func setNodeIsEnabled(isEnabled: Swift.Bool, keypath: Lottie.AnimationKeypath) - @_Concurrency.MainActor(unsafe) final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - @_Concurrency.MainActor(unsafe) final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @_Concurrency.MainActor(unsafe) final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - @objc deinit -} -@objc @_Concurrency.MainActor(unsafe) open class AnimatedControl : UIKit.UIControl { - @_Concurrency.MainActor(unsafe) public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isEnabled: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isSelected: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var isHighlighted: Swift.Bool { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open var intrinsicContentSize: CoreGraphics.CGSize { - @_Concurrency.MainActor(unsafe) @objc get - } - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func continueTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func cancelTracking(with event: UIKit.UIEvent?) - @_Concurrency.MainActor(unsafe) open func animationDidSet() - @_Concurrency.MainActor(unsafe) final public let animationView: Lottie.LottieAnimationView - @_Concurrency.MainActor(unsafe) public var animation: Lottie.LottieAnimation? { - get - set - } - @_Concurrency.MainActor(unsafe) public var animationSpeed: CoreGraphics.CGFloat { - get - set - } - @_Concurrency.MainActor(unsafe) public func setLayer(named: Swift.String, forState: UIKit.UIControl.State) - @_Concurrency.MainActor(unsafe) public func setValueProvider(_ valueProvider: Lottie.AnyValueProvider, keypath: Lottie.AnimationKeypath) - @objc deinit -} -public enum DotLottieError : Swift.Error { - case invalidFileFormat - case invalidData - case animationNotAvailable - public static func == (a: Lottie.DotLottieError, b: Lottie.DotLottieError) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } -} -final public class FloatValueProvider { - public init(block: @escaping Lottie.FloatValueProvider.CGFloatValueBlock) - public init(_ float: CoreGraphics.CGFloat) - public typealias CGFloatValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGFloat - final public var float: CoreGraphics.CGFloat { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc final public class CompatibleAnimationKeypath : ObjectiveC.NSObject { - @objc public init(keypath: Swift.String) - @objc public init(keys: [Swift.String]) - final public let animationKeypath: Lottie.AnimationKeypath - @objc deinit -} -extension Lottie.LottieColor : Swift.Codable { - public init(from decoder: Swift.Decoder) throws - public func encode(to encoder: Swift.Encoder) throws -} -extension Lottie.DotLottieFile { - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(filepath: Swift.String, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func asset(named name: Swift.String, bundle: Foundation.Bundle = Bundle.main, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache) async throws -> Lottie.DotLottieFile - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, dotLottieCache: Lottie.DotLottieCacheProvider? = DotLottieCache.sharedCache, handleResult: @escaping (Swift.Result) -> Swift.Void) -} -public protocol DotLottieCacheProvider { - func file(forKey: Swift.String) -> Lottie.DotLottieFile? - func setFile(_ lottie: Lottie.DotLottieFile, forKey: Swift.String) - func clearCache() -} -public class DotLottieCache : Lottie.DotLottieCacheProvider { - public init() - public static let sharedCache: Lottie.DotLottieCache - public var cacheSize: Swift.Int { - get - set - } - public func clearCache() - public func file(forKey key: Swift.String) -> Lottie.DotLottieFile? - public func setFile(_ lottie: Lottie.DotLottieFile, forKey key: Swift.String) - @objc deinit -} -extension Lottie.LottieAnimation { - public typealias DownloadClosure = (Lottie.LottieAnimation?) -> Swift.Void - final public var duration: Foundation.TimeInterval { - get - } - final public var bounds: CoreGraphics.CGRect { - get - } - final public var size: CoreGraphics.CGSize { - get - } - public static func named(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, subdirectory: Swift.String? = nil, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func filepath(_ filepath: Swift.String, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func asset(_ name: Swift.String, bundle: Foundation.Bundle = Bundle.main, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) -> Lottie.LottieAnimation? - public static func from(data: Foundation.Data, strategy: Lottie.DecodingStrategy = LottieConfiguration.shared.decodingStrategy) throws -> Lottie.LottieAnimation - - #if compiler(>=5.3) && $AsyncAwait - @available(iOS 13.0, macOS 10.15, tvOS 13.0, *) - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) async -> Lottie.LottieAnimation? - #endif - - public static func loadedFrom(url: Foundation.URL, session: Foundation.URLSession = .shared, closure: @escaping Lottie.LottieAnimation.DownloadClosure, animationCache: Lottie.AnimationCacheProvider? = LottieAnimationCache.shared) - final public func progressTime(forMarker named: Swift.String) -> Lottie.AnimationProgressTime? - final public func frameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func durationFrameTime(forMarker named: Swift.String) -> Lottie.AnimationFrameTime? - final public func progressTime(forFrame frameTime: Lottie.AnimationFrameTime, clamped: Swift.Bool = true) -> Lottie.AnimationProgressTime - final public func frameTime(forProgress progressTime: Lottie.AnimationProgressTime) -> Lottie.AnimationFrameTime - final public func time(forFrame frameTime: Lottie.AnimationFrameTime) -> Foundation.TimeInterval - final public func frameTime(forTime time: Foundation.TimeInterval) -> Lottie.AnimationFrameTime -} -extension Foundation.Bundle : @unchecked Swift.Sendable { -} -public enum LayerType : Swift.Int, Swift.Codable { - case precomp - case solid - case image - case null - case shape - case text - public init(from decoder: Swift.Decoder) throws - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum MatteType : Swift.Int, Swift.Codable { - case none - case add - case invert - case unknown - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -public enum BlendMode : Swift.Int, Swift.Codable { - case normal - case multiply - case screen - case overlay - case darken - case lighten - case colorDodge - case colorBurn - case hardLight - case softLight - case difference - case exclusion - case hue - case saturation - case color - case luminosity - public init?(rawValue: Swift.Int) - public typealias RawValue = Swift.Int - public var rawValue: Swift.Int { - get - } -} -final public class GradientValueProvider { - public init(block: @escaping Lottie.GradientValueProvider.ColorsValueBlock, locations: Lottie.GradientValueProvider.ColorLocationsBlock? = nil) - public init(_ colors: [Lottie.LottieColor], locations: [Swift.Double] = []) - public typealias ColorsValueBlock = (CoreGraphics.CGFloat) -> [Lottie.LottieColor] - public typealias ColorLocationsBlock = (CoreGraphics.CGFloat) -> [Swift.Double] - final public var colors: [Lottie.LottieColor] { - get - set - } - final public var locations: [Swift.Double] { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage<[Swift.Double]> { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedSwitch : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) override open func animationDidSet() - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - public enum CancelBehavior { - case reverse - case none - public static func == (a: Lottie.AnimatedSwitch.CancelBehavior, b: Lottie.AnimatedSwitch.CancelBehavior) -> Swift.Bool - public func hash(into hasher: inout Swift.Hasher) - public var hashValue: Swift.Int { - get - } - } - @_Concurrency.MainActor(unsafe) public var cancelBehavior: Lottie.AnimatedSwitch.CancelBehavior - @_Concurrency.MainActor(unsafe) public var animateUpdateWhenChangingAnimation: Swift.Bool - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public var isOn: Swift.Bool { - get - set - } - @_Concurrency.MainActor(unsafe) public func setIsOn(_ isOn: Swift.Bool, animated: Swift.Bool, shouldFireHaptics: Swift.Bool = true) - @_Concurrency.MainActor(unsafe) public func setProgressForState(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, forOnState: Swift.Bool) - @objc deinit -} -public class BundleImageProvider : Lottie.AnimationImageProvider { - public init(bundle: Foundation.Bundle, searchPath: Swift.String?) - public func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) final public class AnimationSubview : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) public class LottieAnimationViewBase : UIKit.UIView { - @_Concurrency.MainActor(unsafe) @objc override dynamic public var contentMode: UIKit.UIView.ContentMode { - @_Concurrency.MainActor(unsafe) @objc get - @_Concurrency.MainActor(unsafe) @objc set - } - @_Concurrency.MainActor(unsafe) @objc override dynamic public func didMoveToWindow() - @_Concurrency.MainActor(unsafe) @objc override dynamic public func layoutSubviews() - @_Concurrency.MainActor(unsafe) @objc override dynamic public init(frame: CoreGraphics.CGRect) - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder: Foundation.NSCoder) - @objc deinit -} -@_hasMissingDesignatedInitializers final public class DotLottieFile { - @objc deinit -} -final public class LottieLogger { - public init(assert: @escaping Lottie.LottieLogger.Assert = { condition, message, file, line in - // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assert(condition(), message(), file: file, line: line) - }, assertionFailure: @escaping Lottie.LottieLogger.AssertionFailure = { message, file, line in - // If we default to `Swift.assertionFailure` directly with - // `assertionFailure: AssertionFailure = Swift.assertionFailure`, - // the call will unexpectedly not respect the -O flag and will crash in release - // https://github.com/apple/swift/issues/60249 - Swift.assertionFailure(message(), file: file, line: line) - }, warn: @escaping Lottie.LottieLogger.Warn = { message, _, _ in - }, info: @escaping Lottie.LottieLogger.Info = { message in - }) - public typealias Assert = (_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias AssertionFailure = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Warn = (_ message: @autoclosure () -> Swift.String, _ fileID: Swift.StaticString, _ line: Swift.UInt) -> Swift.Void - public typealias Info = (_ message: @autoclosure () -> Swift.String) -> Swift.Void - public static var shared: Lottie.LottieLogger - final public func assert(_ condition: @autoclosure () -> Swift.Bool, _ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func assertionFailure(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func warn(_ message: @autoclosure () -> Swift.String = String(), fileID: Swift.StaticString = #fileID, line: Swift.UInt = #line) - final public func info(_ message: @autoclosure () -> Swift.String = String()) - @objc deinit -} -extension Lottie.LottieLogger { - public static var printToConsole: Lottie.LottieLogger { - get - } -} -@available(*, deprecated, message: "Use DefaultAnimationCache instead, which is thread-safe and automatically responds to memory pressure.") -public typealias LRUAnimationCache = Lottie.DefaultAnimationCache -@objc @_inheritsConvenienceInitializers @_Concurrency.MainActor(unsafe) open class AnimatedButton : Lottie.AnimatedControl { - @_Concurrency.MainActor(unsafe) override public init(animation: Lottie.LottieAnimation, configuration: Lottie.LottieConfiguration = .shared) - @objc override dynamic public init() - @_Concurrency.MainActor(unsafe) @objc required dynamic public init?(coder aDecoder: Foundation.NSCoder) - @_Concurrency.MainActor(unsafe) @objc override dynamic open func beginTracking(_ touch: UIKit.UITouch, with event: UIKit.UIEvent?) -> Swift.Bool - @_Concurrency.MainActor(unsafe) @objc override dynamic open func endTracking(_ touch: UIKit.UITouch?, with event: UIKit.UIEvent?) - @objc override dynamic public var accessibilityTraits: UIKit.UIAccessibilityTraits { - @objc get - @objc set - } - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromProgress: Lottie.AnimationProgressTime, toProgress: Lottie.AnimationProgressTime, event: UIKit.UIControl.Event) - @_Concurrency.MainActor(unsafe) public func setPlayRange(fromMarker fromName: Swift.String, toMarker toName: Swift.String, event: UIKit.UIControl.Event) - @objc deinit -} -final public class PointValueProvider { - public init(block: @escaping Lottie.PointValueProvider.PointValueBlock) - public init(_ point: CoreGraphics.CGPoint) - public typealias PointValueBlock = (CoreGraphics.CGFloat) -> CoreGraphics.CGPoint - final public var point: CoreGraphics.CGPoint { - get - set - } - final public var valueType: Any.Type { - get - } - final public var storage: Lottie.ValueProviderStorage { - get - } - final public func hasUpdate(frame _: CoreGraphics.CGFloat) -> Swift.Bool - @objc deinit -} -final public class Keyframe { - public init(_ value: T, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - public init(value: T, time: Lottie.AnimationFrameTime, isHold: Swift.Bool = false, inTangent: Lottie.LottieVector2D? = nil, outTangent: Lottie.LottieVector2D? = nil, spatialInTangent: Lottie.LottieVector3D? = nil, spatialOutTangent: Lottie.LottieVector3D? = nil) - final public let value: T - final public let time: Lottie.AnimationFrameTime - final public let isHold: Swift.Bool - final public let inTangent: Lottie.LottieVector2D? - final public let outTangent: Lottie.LottieVector2D? - final public let spatialInTangent: Lottie.LottieVector3D? - final public let spatialOutTangent: Lottie.LottieVector3D? - @objc deinit -} -extension Lottie.Keyframe : Swift.Equatable where T : Swift.Equatable { - public static func == (lhs: Lottie.Keyframe, rhs: Lottie.Keyframe) -> Swift.Bool -} -extension Lottie.Keyframe : Swift.Hashable where T : Swift.Hashable { - final public func hash(into hasher: inout Swift.Hasher) - final public var hashValue: Swift.Int { - get - } -} -public protocol AnimationImageProvider { - func imageForAsset(asset: Lottie.ImageAsset) -> CoreGraphics.CGImage? -} -public protocol Interpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -public protocol SpatialInterpolatable : Lottie.AnyInterpolatable { - func interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -public protocol AnyInterpolatable { - func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.Interpolatable { - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent _: CoreGraphics.CGPoint?, spatialInTangent _: CoreGraphics.CGPoint?) -> Self -} -extension Lottie.SpatialInterpolatable { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self - public func _interpolate(to: Self, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Self -} -extension Swift.Double : Lottie.Interpolatable { -} -extension CoreGraphics.CGFloat : Lottie.Interpolatable { -} -extension Swift.Float : Lottie.Interpolatable { -} -extension Lottie.Interpolatable where Self : Swift.BinaryFloatingPoint { - public func interpolate(to: Self, amount: CoreGraphics.CGFloat) -> Self -} -extension CoreGraphics.CGRect : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGRect, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGRect -} -extension CoreGraphics.CGSize : Lottie.Interpolatable { - public func interpolate(to: CoreGraphics.CGSize, amount: CoreGraphics.CGFloat) -> CoreGraphics.CGSize -} -extension CoreGraphics.CGPoint : Lottie.SpatialInterpolatable { - public func interpolate(to: CoreGraphics.CGPoint, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> CoreGraphics.CGPoint -} -extension Lottie.LottieColor : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieColor, amount: CoreGraphics.CGFloat) -> Lottie.LottieColor -} -extension Lottie.LottieVector1D : Lottie.Interpolatable { - public func interpolate(to: Lottie.LottieVector1D, amount: CoreGraphics.CGFloat) -> Lottie.LottieVector1D -} -extension Lottie.LottieVector2D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector2D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector2D -} -extension Lottie.LottieVector3D : Lottie.SpatialInterpolatable { - public func interpolate(to: Lottie.LottieVector3D, amount: CoreGraphics.CGFloat, spatialOutTangent: CoreGraphics.CGPoint?, spatialInTangent: CoreGraphics.CGPoint?) -> Lottie.LottieVector3D -} -extension Swift.Array : Lottie.Interpolatable, Lottie.AnyInterpolatable where Element : Lottie.Interpolatable { - public func interpolate(to: [Element], amount: CoreGraphics.CGFloat) -> [Element] -} -extension Lottie.CoordinateSpace : Swift.Equatable {} -extension Lottie.CoordinateSpace : Swift.Hashable {} -extension Lottie.CoordinateSpace : Swift.RawRepresentable {} -extension Lottie.SizeValueProvider : Lottie.AnyValueProvider {} -extension Lottie.ColorValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LottieBackgroundBehavior : Swift.Equatable {} -extension Lottie.LottieBackgroundBehavior : Swift.Hashable {} -extension Lottie.DotLottieError : Swift.Equatable {} -extension Lottie.DotLottieError : Swift.Hashable {} -extension Lottie.FloatValueProvider : Lottie.AnyValueProvider {} -extension Lottie.LayerType : Swift.Equatable {} -extension Lottie.LayerType : Swift.Hashable {} -extension Lottie.LayerType : Swift.RawRepresentable {} -extension Lottie.MatteType : Swift.Equatable {} -extension Lottie.MatteType : Swift.Hashable {} -extension Lottie.MatteType : Swift.RawRepresentable {} -extension Lottie.BlendMode : Swift.Equatable {} -extension Lottie.BlendMode : Swift.Hashable {} -extension Lottie.BlendMode : Swift.RawRepresentable {} -extension Lottie.GradientValueProvider : Lottie.AnyValueProvider {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Equatable {} -extension Lottie.AnimatedSwitch.CancelBehavior : Swift.Hashable {} -extension Lottie.PointValueProvider : Lottie.AnyValueProvider {} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftmodule b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftmodule deleted file mode 100644 index 06f7dce4e0f83b0304d6e21ced3653d1959a23ac..0000000000000000000000000000000000000000 Binary files a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/Lottie.swiftmodule/x86_64.swiftmodule and /dev/null differ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/module.modulemap b/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/module.modulemap deleted file mode 100644 index 1f3f6721a62735fee2d38dc65d0556ba38d5845f..0000000000000000000000000000000000000000 --- a/uni_modules/uni-animation-view/utssdk/app-ios/Frameworks/Lottie.framework/Modules/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -framework module Lottie { - header "Lottie-Swift.h" - requires objc -} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/Resources/PrivacyInfo.xcprivacy b/uni_modules/uni-animation-view/utssdk/app-ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000000000000000000000000000000000..64e785c852a27dbb621edfd61615dcffa2af5861 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,23 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + + diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/config.json b/uni_modules/uni-animation-view/utssdk/app-ios/config.json index 4628e157e82284be282cf07749d8e12103958627..a7eb81c50c5215422e37a0c7f6bbdaa16716b8df 100644 --- a/uni_modules/uni-animation-view/utssdk/app-ios/config.json +++ b/uni_modules/uni-animation-view/utssdk/app-ios/config.json @@ -1,5 +1,5 @@ { - "deploymentTarget": "11", + "deploymentTarget": "13.0", "validArchitectures": [ "arm64", "x86_64" diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/index.vue b/uni_modules/uni-animation-view/utssdk/app-ios/index.vue index 537d78705ea565be8325233cddf4361e950b0eef..95f5c940102d30a858d7e4c16966e8a8d3912271 100644 --- a/uni_modules/uni-animation-view/utssdk/app-ios/index.vue +++ b/uni_modules/uni-animation-view/utssdk/app-ios/index.vue @@ -4,11 +4,11 @@ diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/PrivacyInfo.xcprivacy b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000000000000000000000000000000000..64e785c852a27dbb621edfd61615dcffa2af5861 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/PrivacyInfo.xcprivacy @@ -0,0 +1,23 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + + diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift new file mode 100644 index 0000000000000000000000000000000000000000..f62b7760db139a91f828bb4ac0adf5cd35af988e --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift @@ -0,0 +1,81 @@ +// Created by Cal Stephens on 1/6/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAAnimation { + /// Creates a `CAAnimation` that wraps this animation, + /// applying timing-related configuration from the given `LayerAnimationContext`. + /// - This animation should start at the beginning of the animation and + /// last the entire duration of the animation. It will be trimmed and retimed + /// to match the current playback state / looping configuration of the animation view. + @nonobjc + func timed(with context: LayerAnimationContext, for layer: CALayer) -> CAAnimation { + // The base animation always has the duration of the full animation, + // since that's the time space where keyframing and interpolating happens. + // So we start with a simple animation timeline from 0% to 100%: + // + // ┌──────────────────────────────────┐ + // │ baseAnimation │ + // └──────────────────────────────────┘ + // 0% 100% + // + let baseAnimation = self + baseAnimation.duration = context.animationDuration + baseAnimation.speed = (context.endFrame < context.startFrame) ? -1 : 1 + + // To select the subrange of the `baseAnimation` that should be played, + // we create a parent animation with the duration of that subrange + // to clip the `baseAnimation`. This parent animation can then loop + // and/or autoreverse over the clipped subrange. + // + // ┌────────────────────┬───────► + // │ clippingParent │ ... + // └────────────────────┴───────► + // 25% 75% + // ┌──────────────────────────────────┐ + // │ baseAnimation │ + // └──────────────────────────────────┘ + // 0% 100% + // + let clippingParent = CAAnimationGroup() + clippingParent.animations = [baseAnimation] + + clippingParent.duration = Double(abs(context.endFrame - context.startFrame)) / context.animation.framerate + baseAnimation.timeOffset = context.animation.time(forFrame: context.startFrame) + + clippingParent.autoreverses = context.timingConfiguration.autoreverses + clippingParent.repeatCount = context.timingConfiguration.repeatCount + clippingParent.timeOffset = context.timingConfiguration.timeOffset + + // Once the animation ends, it should pause on the final frame + clippingParent.fillMode = .both + clippingParent.isRemovedOnCompletion = false + + // We can pause the animation on a specific frame by setting the root layer's + // `speed` to 0, and then setting the `timeOffset` for the given frame. + // - For that setup to work properly, we have to set the `beginTime` + // of this animation to a time slightly before the current time. + // - It's not really clear why this is necessary, but `timeOffset` + // is not applied correctly without this configuration. + // - We can't do this when playing the animation in real time, + // because it can cause keyframe timings to be incorrect. + if context.timingConfiguration.speed == 0 { + let currentTime = layer.convertTime(CACurrentMediaTime(), from: nil) + clippingParent.beginTime = currentTime - .leastNonzeroMagnitude + } + + return clippingParent + } +} + +extension CALayer { + /// Adds the given animation to this layer, timed with the given timing configuration + /// - The given animation should start at the beginning of the animation and + /// last the entire duration of the animation. It will be trimmed and retimed + /// to match the current playback state / looping configuration of the animation view. + @nonobjc + func add(_ animation: CAPropertyAnimation, timedWith context: LayerAnimationContext) { + add(animation.timed(with: context, for: self), forKey: animation.keyPath) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CALayer+addAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CALayer+addAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..1447419e92a17c459179eb2c5cf8b806f0238bdd --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CALayer+addAnimation.swift @@ -0,0 +1,469 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CALayer { + + // MARK: Internal + + /// Constructs a `CAKeyframeAnimation` that reflects the given keyframes, + /// and adds it to this `CALayer`. + @nonobjc + func addAnimation( + for property: LayerProperty, + keyframes: KeyframeGroup, + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + context: LayerAnimationContext) + throws + { + if let customAnimation = try customizedAnimation(for: property, context: context) { + add(customAnimation, timedWith: context) + } + + else if + let defaultAnimation = try defaultAnimation( + for: property, + keyframes: keyframes, + value: keyframeValueMapping, + context: context) + { + let timedAnimation = defaultAnimation.timed(with: context, for: self) + add(timedAnimation, forKey: property.caLayerKeypath) + } + } + + // MARK: Private + + /// Constructs a `CAAnimation` that reflects the given keyframes + /// - If the value can be applied directly to the CALayer using KVC, + /// then no `CAAnimation` will be created and the value will be applied directly. + @nonobjc + private func defaultAnimation( + for property: LayerProperty, + keyframes keyframeGroup: KeyframeGroup, + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + context: LayerAnimationContext) + throws -> CAAnimation? + { + let keyframes = keyframeGroup.keyframes + guard !keyframes.isEmpty else { return nil } + + // Check if this set of keyframes uses After Effects expressions, which aren't supported. + // - We only log this once per `CoreAnimationLayer` instance. + if keyframeGroup.unsupportedAfterEffectsExpression != nil, !context.loggingState.hasLoggedAfterEffectsExpressionsWarning { + context.loggingState.hasLoggedAfterEffectsExpressionsWarning = true + context.logger.info(""" + `\(property.caLayerKeypath)` animation for "\(context.currentKeypath.fullPath)" \ + includes an After Effects expression (https://helpx.adobe.com/after-effects/using/expression-language.html), \ + which is not supported by lottie-ios (expressions are only supported by lottie-web). \ + This animation may not play correctly. + """) + } + + // If there is exactly one keyframe value that doesn't animate, + // we can improve performance by applying that value directly to the layer + // instead of creating a relatively expensive `CAKeyframeAnimation`. + if keyframes.count == 1 { + return singleKeyframeAnimation( + for: property, + keyframeValue: try keyframeValueMapping(keyframes[0].value), + writeDirectlyToPropertyIfPossible: true) + } + + /// If we're required to use the `complexTimeRemapping` from some parent `PreCompLayer`, + /// we have to manually interpolate the keyframes with the time remapping applied. + if context.mustUseComplexTimeRemapping { + return try defaultAnimation( + for: property, + keyframes: Keyframes.manuallyInterpolatedWithTimeRemapping(keyframeGroup, context: context), + value: keyframeValueMapping, + context: context.withoutTimeRemapping()) + } + + // Split the keyframes into segments with the same `CAAnimationCalculationMode` value + // - Each of these segments will become their own `CAKeyframeAnimation` + let animationSegments = keyframes.segmentsSplitByCalculationMode() + + // If we only have a single segment, we can just create a single `CAKeyframeAnimation` + // instead of wrapping it in a `CAAnimationGroup` -- this reduces allocation overhead a bit. + if animationSegments.count == 1 { + return try keyframeAnimation( + for: property, + keyframes: animationSegments[0], + value: keyframeValueMapping, + context: context) + } else { + return try animationGroup( + for: property, + animationSegments: animationSegments, + value: keyframeValueMapping, + context: context) + } + } + + /// A `CAAnimation` that applies the custom value from the `AnyValueProvider` + /// registered for this specific property's `AnimationKeypath`, + /// if one has been registered using `LottieAnimationView.setValueProvider(_:keypath:)`. + @nonobjc + private func customizedAnimation( + for property: LayerProperty, + context: LayerAnimationContext) + throws -> CAPropertyAnimation? + { + guard + let customizableProperty = property.customizableProperty, + let customKeyframes = try context.valueProviderStore.customKeyframes( + of: customizableProperty, + for: AnimationKeypath(keys: context.currentKeypath.keys + customizableProperty.name.map { $0.rawValue }), + context: context) + else { return nil } + + // Since custom animations are overriding an existing animation, + // we always have to create a CAAnimation and can't write directly + // to the layer property + if + customKeyframes.keyframes.count == 1, + let singleKeyframeAnimation = singleKeyframeAnimation( + for: property, + keyframeValue: customKeyframes.keyframes[0].value, + writeDirectlyToPropertyIfPossible: false) + { + return singleKeyframeAnimation + } + + return try keyframeAnimation( + for: property, + keyframes: Array(customKeyframes.keyframes), + value: { $0 }, + context: context) + } + + /// Creates an animation that applies a single keyframe to this layer property + /// - In many cases this animation can be omitted entirely, and the underlying + /// property can be set directly. In that case, no animation will be created. + private func singleKeyframeAnimation( + for property: LayerProperty, + keyframeValue: ValueRepresentation, + writeDirectlyToPropertyIfPossible: Bool) + -> CABasicAnimation? + { + if writeDirectlyToPropertyIfPossible { + // If the keyframe value is the same as the layer's default value for this property, + // then we can just ignore this set of keyframes. + if property.isDefaultValue(keyframeValue) { + return nil + } + + // If the property on the CALayer being animated hasn't been modified from the default yet, + // then we can apply the keyframe value directly to the layer using KVC instead + // of creating a `CAAnimation`. + let currentValue = value(forKey: property.caLayerKeypath) as? ValueRepresentation + if property.isDefaultValue(currentValue) { + setValue(keyframeValue, forKeyPath: property.caLayerKeypath) + return nil + } + } + + // Otherwise, we still need to create a `CAAnimation`, but we can + // create a simple `CABasicAnimation` that is still less expensive + // than computing a `CAKeyframeAnimation`. + let animation = CABasicAnimation(keyPath: property.caLayerKeypath) + animation.fromValue = keyframeValue + animation.toValue = keyframeValue + return animation + } + + /// Creates a `CAAnimationGroup` that wraps a `CAKeyframeAnimation` for each + /// of the given `animationSegments` + private func animationGroup( + for property: LayerProperty, + animationSegments: [[Keyframe]], + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + context: LayerAnimationContext) + throws -> CAAnimationGroup + { + // Build the `CAKeyframeAnimation` for each segment of keyframes + // with the same `CAAnimationCalculationMode`. + // - Here we have a non-zero number of animation segments, + // all of which have a non-zero number of keyframes. + let segmentAnimations: [CAKeyframeAnimation] = try animationSegments.indices.map { index in + let animationSegment = animationSegments[index] + var segmentStartTime = try context.time(forFrame: animationSegment.first!.time) + var segmentEndTime = try context.time(forFrame: animationSegment.last!.time) + + // Every portion of the animation timeline has to be covered by a `CAKeyframeAnimation`, + // so if this is the first or last segment then the start/end time should be exactly + // the start/end time of the animation itself. + let isFirstSegment = (index == animationSegments.indices.first!) + let isLastSegment = (index == animationSegments.indices.last!) + + if isFirstSegment { + segmentStartTime = min( + try context.time(forFrame: context.animation.startFrame), + segmentStartTime) + } + + if isLastSegment { + segmentEndTime = max( + try context.time(forFrame: context.animation.endFrame), + segmentEndTime) + } + + let segmentDuration = segmentEndTime - segmentStartTime + + // We're building `CAKeyframeAnimation`s, so the `keyTimes` are expressed + // relative to 0 (`segmentStartTime`) and 1 (`segmentEndTime`). This is different + // from the default behavior of the `keyframeAnimation` method, where times + // are expressed relative to the entire animation duration. + let customKeyTimes = try animationSegment.map { keyframeModel -> NSNumber in + let keyframeTime = try context.time(forFrame: keyframeModel.time) + let segmentProgressTime = ((keyframeTime - segmentStartTime) / segmentDuration) + return segmentProgressTime as NSNumber + } + + let animation = try keyframeAnimation( + for: property, + keyframes: animationSegment, + value: keyframeValueMapping, + customKeyTimes: customKeyTimes, + context: context) + + animation.duration = segmentDuration + animation.beginTime = segmentStartTime + return animation + } + + let fullAnimation = CAAnimationGroup() + fullAnimation.animations = segmentAnimations + return fullAnimation + } + + /// Creates and validates a `CAKeyframeAnimation` for the given keyframes + private func keyframeAnimation( + for property: LayerProperty, + keyframes: [Keyframe], + value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation, + customKeyTimes: [NSNumber]? = nil, + context: LayerAnimationContext) + throws + -> CAKeyframeAnimation + { + // Convert the list of `Keyframe` into + // the representation used by `CAKeyframeAnimation` + var keyTimes = try customKeyTimes ?? keyframes.map { keyframeModel -> NSNumber in + NSNumber(value: Float(try context.progressTime(for: keyframeModel.time))) + } + + var timingFunctions = timingFunctions(for: keyframes) + let calculationMode = calculationMode(for: keyframes) + + let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath) + + // Position animations define a `CGPath` curve that should be followed, + // instead of animating directly between keyframe point values. + if property.caLayerKeypath == LayerProperty.position.caLayerKeypath { + animation.path = try path(keyframes: keyframes, value: { value in + guard let point = try keyframeValueMapping(value) as? CGPoint else { + context.logger.assertionFailure("Cannot create point from keyframe with value \(value)") + return .zero + } + + return point + }) + } + + // All other types of keyframes provide individual values that are interpolated by Core Animation + else { + var values = try keyframes.map { keyframeModel in + try keyframeValueMapping(keyframeModel.value) + } + + validate( + values: &values, + keyTimes: &keyTimes, + timingFunctions: &timingFunctions, + for: calculationMode, + context: context) + + animation.values = values + } + + animation.calculationMode = calculationMode + animation.keyTimes = keyTimes + animation.timingFunctions = timingFunctions + return animation + } + + /// The `CAAnimationCalculationMode` that should be used for a `CAKeyframeAnimation` + /// animating the given keyframes + private func calculationMode( + for keyframes: [Keyframe]) + -> CAAnimationCalculationMode + { + // At this point we expect all of the animations to have been split in + // to segments based on the `CAAnimationCalculationMode`, so we can just + // check the first keyframe. + if keyframes[0].isHold { + .discrete + } else { + .linear + } + } + + /// `timingFunctions` to apply to a `CAKeyframeAnimation` animating the given keyframes + private func timingFunctions( + for keyframes: [Keyframe]) + -> [CAMediaTimingFunction] + { + // Compute the timing function between each keyframe and the subsequent keyframe + var timingFunctions: [CAMediaTimingFunction] = [] + + for (index, keyframe) in keyframes.enumerated() + where index != keyframes.indices.last + { + let nextKeyframe = keyframes[index + 1] + + let controlPoint1 = keyframe.outTangent?.pointValue ?? .zero + let controlPoint2 = nextKeyframe.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) + + timingFunctions.append(CAMediaTimingFunction( + controlPoints: + Float(controlPoint1.x), + Float(controlPoint1.y), + Float(controlPoint2.x), + Float(controlPoint2.y))) + } + + return timingFunctions + } + + /// Creates a `CGPath` for the given `position` keyframes, + /// which accounts for `spatialInTangent`s and `spatialOutTangents` + private func path( + keyframes positionKeyframes: [Keyframe], + value keyframeValueMapping: (KeyframeValue) throws -> CGPoint) rethrows + -> CGPath + { + let path = CGMutablePath() + + for (index, keyframe) in positionKeyframes.enumerated() { + if index == positionKeyframes.indices.first { + path.move(to: try keyframeValueMapping(keyframe.value)) + } + + if index != positionKeyframes.indices.last { + let nextKeyframe = positionKeyframes[index + 1] + + if + let controlPoint1 = keyframe.spatialOutTangent?.pointValue, + let controlPoint2 = nextKeyframe.spatialInTangent?.pointValue, + !(controlPoint1 == .zero && controlPoint2 == .zero) + { + path.addCurve( + to: try keyframeValueMapping(nextKeyframe.value), + control1: try keyframeValueMapping(keyframe.value) + controlPoint1, + control2: try keyframeValueMapping(nextKeyframe.value) + controlPoint2) + } + + else { + path.addLine(to: try keyframeValueMapping(nextKeyframe.value)) + } + } + } + + path.closeSubpath() + return path + } + + /// Validates that the requirements of the `CAKeyframeAnimation` API are met correctly + private func validate( + values: inout [ValueRepresentation], + keyTimes: inout [NSNumber], + timingFunctions: inout [CAMediaTimingFunction], + for calculationMode: CAAnimationCalculationMode, + context: LayerAnimationContext) + { + // Validate that we have correct start (0.0) and end (1.0) keyframes. + // From the documentation of `CAKeyframeAnimation.keyTimes`: + // - The first value in the `keyTimes` array must be 0.0 and the last value must be 1.0. + if keyTimes.first != 0.0 { + keyTimes.insert(0.0, at: 0) + values.insert(values[0], at: 0) + timingFunctions.insert(CAMediaTimingFunction(name: .linear), at: 0) + } + + if keyTimes.last != 1.0 { + keyTimes.append(1.0) + values.append(values.last!) + timingFunctions.append(CAMediaTimingFunction(name: .linear)) + } + + switch calculationMode { + case .linear, .cubic: + // From the documentation of `CAKeyframeAnimation.keyTimes`: + // - The number of elements in the keyTimes array + // should match the number of elements in the values property + context.logger.assert( + values.count == keyTimes.count, + "`values.count` must exactly equal `keyTimes.count`") + + context.logger.assert( + timingFunctions.count == (values.count - 1), + "`timingFunctions.count` must exactly equal `values.count - 1`") + + case .discrete: + // From the documentation of `CAKeyframeAnimation.keyTimes`: + // - If the calculationMode is set to discrete... the keyTimes array + // should have one more entry than appears in the values array. + values.removeLast() + + context.logger.assert( + keyTimes.count == values.count + 1, + "`keyTimes.count` must exactly equal `values.count + 1`") + + default: + context.logger.assertionFailure(""" + Unexpected keyframe calculation mode \(calculationMode) + """) + } + } + +} + +extension RandomAccessCollection { + /// Splits this array of `Keyframe`s into segments with the same `CAAnimationCalculationMode` + /// - Keyframes with `isHold=true` become `discrete`, and keyframes with `isHold=false` + /// become linear. Each `CAKeyframeAnimation` can only be one or the other, so each + /// `calculationModeSegment` becomes its own `CAKeyframeAnimation`. + func segmentsSplitByCalculationMode() -> [[Element]] + where Element == Keyframe, Index == Int + { + var segments: [[Element]] = [] + var currentSegment: [Element] = [] + + for keyframe in self { + guard let mostRecentKeyframe = currentSegment.last else { + currentSegment.append(keyframe) + continue + } + + // When `isHold` changes between any two given keyframes, we have to create a new segment + if keyframe.isHold != mostRecentKeyframe.isHold { + // Add this keyframe to both the existing segment that is ending, + // so we know how long that segment is, and the new segment, + // so we know when that segment starts. + currentSegment.append(keyframe) + segments.append(currentSegment) + currentSegment = [keyframe] + } + + else { + currentSegment.append(keyframe) + } + } + + segments.append(currentSegment) + return segments + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..e5b888f45e8173801dc856ab8ef65662484ef1ad --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift @@ -0,0 +1,84 @@ +// Created by Cal Stephens on 1/28/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `CombinedShapeItem` to this `CALayer` + @nonobjc + func addAnimations( + for combinedShapes: CombinedShapeItem, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier) + throws + { + try addAnimation( + for: .path, + keyframes: combinedShapes.shapes, + value: { paths in + let combinedPath = CGMutablePath() + for path in paths { + combinedPath.addPath(path.cgPath().duplicated(times: pathMultiplier)) + } + return combinedPath + }, + context: context) + } +} + +// MARK: - CombinedShapeItem + +/// A custom `ShapeItem` subclass that combines multiple `Shape`s into a single `KeyframeGroup` +final class CombinedShapeItem: ShapeItem { + + // MARK: Lifecycle + + init(shapes: KeyframeGroup<[BezierPath]>, name: String) { + self.shapes = shapes + super.init(name: name, type: .shape, hidden: false) + } + + required init(from _: Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + required init(dictionary _: [String: Any]) throws { + fatalError("init(dictionary:) has not been implemented") + } + + // MARK: Internal + + let shapes: KeyframeGroup<[BezierPath]> + +} + +extension CombinedShapeItem { + /// Manually combines the given shape keyframes by manually interpolating at each frame + static func manuallyInterpolating( + shapes: [KeyframeGroup], + name: String) + -> CombinedShapeItem + { + let interpolators = shapes.map { shape in + KeyframeInterpolator(keyframes: shape.keyframes) + } + + let times = shapes.flatMap { $0.keyframes.map { $0.time } } + + let minimumTime = times.min() ?? 0 + let maximumTime = times.max() ?? 0 + let animationLocalTimeRange = Int(minimumTime)...Int(maximumTime) + + let interpolatedKeyframes = animationLocalTimeRange.map { localTime in + Keyframe( + value: interpolators.compactMap { interpolator in + interpolator.value(frame: AnimationFrameTime(localTime)) as? BezierPath + }, + time: AnimationFrameTime(localTime)) + } + + return CombinedShapeItem( + shapes: KeyframeGroup(keyframes: ContiguousArray(interpolatedKeyframes)), + name: name) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CustomPathAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CustomPathAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..2171eda5172a4d6118c33cc6743507861d96bcd8 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/CustomPathAnimation.swift @@ -0,0 +1,86 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `BezierPath` keyframes to this `CALayer` + @nonobjc + func addAnimations( + for customPath: KeyframeGroup, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier = 1, + transformPath: (CGPath) -> CGPath = { $0 }, + roundedCorners: RoundedCorners? = nil) + throws + { + let combinedKeyframes = try BezierPathKeyframe.combining( + path: customPath, + cornerRadius: roundedCorners?.radius) + + try addAnimation( + for: .path, + keyframes: combinedKeyframes, + value: { pathKeyframe in + var path = pathKeyframe.path + if let cornerRadius = pathKeyframe.cornerRadius { + path = path.roundCorners(radius: cornerRadius.cgFloatValue) + } + + return transformPath(path.cgPath().duplicated(times: pathMultiplier)) + }, + context: context) + } +} + +extension CGPath { + /// Duplicates this `CGPath` so that it is repeated the given number of times + func duplicated(times: Int) -> CGPath { + if times <= 1 { + return self + } + + let cgPath = CGMutablePath() + + for _ in 0.., + cornerRadius: KeyframeGroup?) throws + -> KeyframeGroup + { + guard + let cornerRadius, + cornerRadius.keyframes.contains(where: { $0.value.cgFloatValue > 0 }) + else { + return path.map { path in + BezierPathKeyframe(path: path, cornerRadius: nil) + } + } + + return Keyframes.combined( + path, cornerRadius, + makeCombinedResult: BezierPathKeyframe.init) + } + + func interpolate(to: BezierPathKeyframe, amount: CGFloat) -> BezierPathKeyframe { + BezierPathKeyframe( + path: path.interpolate(to: to.path, amount: amount), + cornerRadius: cornerRadius.interpolate(to: to.cornerRadius, amount: amount)) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/DropShadowAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/DropShadowAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..92296390bbe07ddcb262e6e13de8ce70efca35df --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/DropShadowAnimation.swift @@ -0,0 +1,160 @@ +// Created by Cal Stephens on 8/15/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - DropShadowModel + +protocol DropShadowModel { + /// The opacity of the drop shadow, from 0 to 100. + var _opacity: KeyframeGroup? { get } + + /// The shadow radius of the blur + var _radius: KeyframeGroup? { get } + + /// The color of the drop shadow + var _color: KeyframeGroup? { get } + + /// The angle of the drop shadow, in degrees, + /// with "90" resulting in a shadow directly beneath the layer. + /// Combines with the `distance` to form the `shadowOffset`. + var _angle: KeyframeGroup? { get } + + /// The distance of the drop shadow offset. + /// Combines with the `angle` to form the `shadowOffset`. + var _distance: KeyframeGroup? { get } +} + +// MARK: - DropShadowStyle + DropShadowModel + +extension DropShadowStyle: DropShadowModel { + var _opacity: KeyframeGroup? { opacity } + var _color: KeyframeGroup? { color } + var _angle: KeyframeGroup? { angle } + var _distance: KeyframeGroup? { distance } + + var _radius: KeyframeGroup? { + size.map { sizeValue in + // After Effects shadow softness uses a different range of values than CALayer.shadowRadius, + // so shadows render too softly if we directly use the value from After Effects. We find that + // dividing this value from After Effects by 2 produces results that are visually similar. + LottieVector1D(sizeValue.cgFloatValue / 2) + } + } +} + +// MARK: - DropShadowEffect + DropShadowModel + +extension DropShadowEffect: DropShadowModel { + var _color: KeyframeGroup? { color?.value } + var _distance: KeyframeGroup? { distance?.value } + + var _radius: KeyframeGroup? { + softness?.value?.map { softnessValue in + // After Effects shadow softness uses a different range of values than CALayer.shadowRadius, + // so shadows render too softly if we directly use the value from After Effects. We find that + // dividing this value from After Effects by 5 produces results that are visually similar. + LottieVector1D(softnessValue.cgFloatValue / 5) + } + } + + var _opacity: KeyframeGroup? { + opacity?.value?.map { originalOpacityValue in + // `DropShadowEffect.opacity` is a value between 0 and 255, + // but `DropShadowModel._opacity` expects a value between 0 and 100. + LottieVector1D((originalOpacityValue.value / 255.0) * 100) + } + } + + var _angle: KeyframeGroup? { + direction?.value?.map { originalAngleValue in + // `DropShadowEffect.distance` is rotated 90º from the + // angle value representation expected by `DropShadowModel._angle` + LottieVector1D(originalAngleValue.value - 90) + } + } +} + +// MARK: - CALayer + DropShadowModel + +extension CALayer { + + // MARK: Internal + + /// Adds drop shadow animations from the given `DropShadowModel` to this layer + @nonobjc + func addDropShadowAnimations( + for dropShadowModel: DropShadowModel, + context: LayerAnimationContext) + throws + { + try addShadowOpacityAnimation(from: dropShadowModel, context: context) + try addShadowColorAnimation(from: dropShadowModel, context: context) + try addShadowRadiusAnimation(from: dropShadowModel, context: context) + try addShadowOffsetAnimation(from: dropShadowModel, context: context) + } + + // MARK: Private + + private func addShadowOpacityAnimation(from model: DropShadowModel, context: LayerAnimationContext) throws { + guard let opacityKeyframes = model._opacity else { return } + + try addAnimation( + for: .shadowOpacity, + keyframes: opacityKeyframes, + value: { + // Lottie animation files express opacity as a numerical percentage value + // (e.g. 0%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.0, 0.5, 1.0). + $0.cgFloatValue / 100 + }, + context: context) + } + + private func addShadowColorAnimation(from model: DropShadowModel, context: LayerAnimationContext) throws { + guard let shadowColorKeyframes = model._color else { return } + + try addAnimation( + for: .shadowColor, + keyframes: shadowColorKeyframes, + value: \.cgColorValue, + context: context) + } + + private func addShadowRadiusAnimation(from model: DropShadowModel, context: LayerAnimationContext) throws { + guard let shadowSizeKeyframes = model._radius else { return } + + try addAnimation( + for: .shadowRadius, + keyframes: shadowSizeKeyframes, + value: \.cgFloatValue, + context: context) + } + + private func addShadowOffsetAnimation(from model: DropShadowModel, context: LayerAnimationContext) throws { + guard + let angleKeyframes = model._angle, + let distanceKeyframes = model._distance + else { return } + + let offsetKeyframes = Keyframes.combined(angleKeyframes, distanceKeyframes) { angleDegrees, distance -> CGSize in + // Lottie animation files express rotation in degrees + // (e.g. 90º, 180º, 360º) so we convert to radians to get the + // values expected by Core Animation (e.g. π/2, π, 2π) + let angleRadians = (angleDegrees.cgFloatValue * .pi) / 180 + + // Lottie animation files express the `shadowOffset` as (angle, distance) pair, + // which we convert to the expected x / y offset values: + let offsetX = distance.cgFloatValue * cos(angleRadians) + let offsetY = distance.cgFloatValue * sin(angleRadians) + return CGSize(width: offsetX, height: offsetY) + } + + try addAnimation( + for: .shadowOffset, + keyframes: offsetKeyframes, + value: { $0 }, + context: context) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/EllipseAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/EllipseAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..df3d148d485ce36e6449bc7b18379159794b5ac9 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/EllipseAnimation.swift @@ -0,0 +1,49 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `Ellipse` to this `CALayer` + @nonobjc + func addAnimations( + for ellipse: Ellipse, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier) + throws + { + try addAnimation( + for: .path, + keyframes: ellipse.combinedKeyframes(), + value: { keyframe in + BezierPath.ellipse( + size: keyframe.size.sizeValue, + center: keyframe.position.pointValue, + direction: ellipse.direction) + .cgPath() + .duplicated(times: pathMultiplier) + }, + context: context) + } +} + +extension Ellipse { + /// Data that represents how to render an ellipse at a specific point in time + struct Keyframe: Interpolatable { + let size: LottieVector3D + let position: LottieVector3D + + func interpolate(to: Ellipse.Keyframe, amount: CGFloat) -> Ellipse.Keyframe { + Keyframe( + size: size.interpolate(to: to.size, amount: amount), + position: position.interpolate(to: to.position, amount: amount)) + } + } + + /// Creates a single array of animatable keyframes from the separate arrays of keyframes in this Ellipse + func combinedKeyframes() throws -> KeyframeGroup { + Keyframes.combined( + size, position, + makeCombinedResult: Ellipse.Keyframe.init) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/GradientAnimations.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/GradientAnimations.swift new file mode 100644 index 0000000000000000000000000000000000000000..17ebf274d00aefe31a0ff2b9d6cc9e1d97d014ff --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/GradientAnimations.swift @@ -0,0 +1,244 @@ +// Created by Cal Stephens on 1/7/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - GradientShapeItem + +/// A `ShapeItem` that represents a gradient +protocol GradientShapeItem: OpacityAnimationModel { + var startPoint: KeyframeGroup { get } + var endPoint: KeyframeGroup { get } + var gradientType: GradientType { get } + var numberOfColors: Int { get } + var colors: KeyframeGroup<[Double]> { get } +} + +// MARK: - GradientFill + GradientShapeItem + +extension GradientFill: GradientShapeItem { } + +// MARK: - GradientStroke + GradientShapeItem + +extension GradientStroke: GradientShapeItem { } + +// MARK: - GradientRenderLayer + GradientShapeItem + +extension GradientRenderLayer { + + // MARK: Internal + + /// Adds gradient-related animations to this layer, from the given `GradientFill` + /// - The RGB components and alpha components can have different color stops / locations, + /// so have to be rendered in separate `CAGradientLayer`s. + func addGradientAnimations( + for gradient: GradientShapeItem, + type: GradientContentType, + context: LayerAnimationContext) + throws + { + // We have to set `colors` and `locations` to non-nil values + // for the animations below to actually take effect + locations = [] + + // The initial value for `colors` must be an array with the exact same number of colors + // as the gradient that will be applied in the `CAAnimation` + switch type { + case .rgb: + colors = .init( + repeating: CGColor.rgb(0, 0, 0), + count: gradient.numberOfColors) + + case .alpha: + colors = .init( + repeating: CGColor.rgb(0, 0, 0), + count: gradient.colorConfiguration(from: gradient.colors.keyframes[0].value, type: .alpha).count) + } + + try addAnimation( + for: .colors, + keyframes: gradient.colors, + value: { colorComponents in + gradient.colorConfiguration(from: colorComponents, type: type).map { $0.color } + }, + context: context) + + try addAnimation( + for: .locations, + keyframes: gradient.colors, + value: { colorComponents in + gradient.colorConfiguration(from: colorComponents, type: type).map { $0.location } + }, + context: context) + + try addOpacityAnimation(for: gradient, context: context) + + switch gradient.gradientType { + case .linear: + try addLinearGradientAnimations(for: gradient, context: context) + case .radial: + try addRadialGradientAnimations(for: gradient, context: context) + case .none: + break + } + } + + // MARK: Private + + private func addLinearGradientAnimations( + for gradient: GradientShapeItem, + context: LayerAnimationContext) + throws + { + type = .axial + + try addAnimation( + for: .startPoint, + keyframes: gradient.startPoint, + value: { absoluteStartPoint in + percentBasedPointInBounds(from: absoluteStartPoint.pointValue) + }, + context: context) + + try addAnimation( + for: .endPoint, + keyframes: gradient.endPoint, + value: { absoluteEndPoint in + percentBasedPointInBounds(from: absoluteEndPoint.pointValue) + }, + context: context) + } + + private func addRadialGradientAnimations(for gradient: GradientShapeItem, context: LayerAnimationContext) throws { + type = .radial + + let combinedKeyframes = Keyframes.combined( + gradient.startPoint, gradient.endPoint, + makeCombinedResult: { absoluteStartPoint, absoluteEndPoint -> RadialGradientKeyframes in + // Convert the absolute start / end points to the relative structure used by Core Animation + let relativeStartPoint = percentBasedPointInBounds(from: absoluteStartPoint.pointValue) + let radius = absoluteStartPoint.pointValue.distanceTo(absoluteEndPoint.pointValue) + let relativeEndPoint = percentBasedPointInBounds( + from: CGPoint( + x: absoluteStartPoint.x + radius, + y: absoluteStartPoint.y + radius)) + + return RadialGradientKeyframes(startPoint: relativeStartPoint, endPoint: relativeEndPoint) + }) + + try addAnimation( + for: .startPoint, + keyframes: combinedKeyframes, + value: \.startPoint, + context: context) + + try addAnimation( + for: .endPoint, + keyframes: combinedKeyframes, + value: \.endPoint, + context: context) + } +} + +// MARK: - RadialGradientKeyframes + +private struct RadialGradientKeyframes: Interpolatable { + let startPoint: CGPoint + let endPoint: CGPoint + + func interpolate(to: RadialGradientKeyframes, amount: CGFloat) -> RadialGradientKeyframes { + RadialGradientKeyframes( + startPoint: startPoint.interpolate(to: to.startPoint, amount: amount), + endPoint: endPoint.interpolate(to: to.endPoint, amount: amount)) + } +} + +// MARK: - GradientContentType + +/// Each type of gradient that can be constructed from a `GradientShapeItem` +enum GradientContentType { + case rgb + case alpha +} + +/// `colors` and `locations` configuration for a `CAGradientLayer` +typealias GradientColorConfiguration = [(color: CGColor, location: CGFloat)] + +extension GradientShapeItem { + + // MARK: Internal + + /// Whether or not this `GradientShapeItem` includes an alpha component + /// that has to be rendered as a separate `CAGradientLayer` from the + /// layer that renders the rgb color components + var hasAlphaComponent: Bool { + for colorComponentsKeyframe in colors.keyframes { + let colorComponents = colorComponentsKeyframe.value + let alphaConfiguration = colorConfiguration(from: colorComponents, type: .alpha) + + let notFullyOpaque = alphaConfiguration.contains(where: { color, _ in + color.alpha < 0.999 + }) + + if notFullyOpaque { + return true + } + } + + return false + } + + // MARK: Fileprivate + + /// Converts the compact `[Double]` color components representation + /// into an array of `CGColor`s and the location of those colors within the gradient. + /// - The color components array is a repeating list of `[location, red, green, blue]` values + /// for each color in the gradient, followed by an optional repeating list of + /// `[location, alpha]` values that control the colors' alpha values. + /// - The RGB and alpha values can have different color stops / locations, + /// so each has to be rendered in a separate `CAGradientLayer`. + fileprivate func colorConfiguration( + from colorComponents: [Double], + type: GradientContentType) + -> GradientColorConfiguration + { + switch type { + case .rgb: + precondition( + colorComponents.count >= numberOfColors * 4, + "Each color must have RGB components and a location component") + + // Each group of four `Double` values represents a single `CGColor`, + // and its relative location within the gradient. + var colors = GradientColorConfiguration() + + for colorIndex in 0.. { + /// The `CALayer` KVC key path that this value should be assigned to + let caLayerKeypath: String + + /// Whether or not the given value is the default value for this property + /// - If the keyframe values are just equal to the default value, + /// then we can improve performance a bit by just not creating + /// a CAAnimation (since it would be redundant). + let isDefaultValue: (ValueRepresentation?) -> Bool + + /// A description of how this property can be customized dynamically + /// at runtime using `AnimationView.setValueProvider(_:keypath:)` + let customizableProperty: CustomizableProperty? +} + +extension LayerProperty where ValueRepresentation: Equatable { + /// Initializes a `LayerProperty` that corresponds to a property on `CALayer` + /// or some other `CALayer` subclass like `CAShapeLayer`. + /// - Parameters: + /// - caLayerKeypath: The Objective-C `#keyPath` to the `CALayer` property, + /// e.g. `#keyPath(CALayer.opacity)` or `#keyPath(CAShapeLayer.path)`. + /// - defaultValue: The default value of the property (e.g. the value of the + /// property immediately after calling `CALayer.init()`). Knowing this value + /// lets us perform some optimizations in `CALayer+addAnimation`. + /// - customizableProperty: A description of how this property can be customized + /// dynamically at runtime using `AnimationView.setValueProvider(_:keypath:)`. + init( + caLayerKeypath: String, + defaultValue: ValueRepresentation?, + customizableProperty: CustomizableProperty?) + { + self.init( + caLayerKeypath: caLayerKeypath, + isDefaultValue: { $0 == defaultValue }, + customizableProperty: customizableProperty) + } +} + +// MARK: - CustomizableProperty + +/// A description of how a `CALayer` property can be customized dynamically +/// at runtime using `LottieAnimationView.setValueProvider(_:keypath:)` +struct CustomizableProperty { + /// The name that `AnimationKeypath`s can use to refer to this property + /// - When building an animation for this property that will be applied + /// to a specific layer, this `name` is appended to the end of that + /// layer's `AnimationKeypath`. The combined keypath is used to query + /// the `ValueProviderStore`. + let name: [PropertyName] + + /// A closure that coverts the type-erased value of an `AnyValueProvider` + /// to the strongly-typed representation used by this property, if possible. + /// - `value` is the value for the current frame that should be converted, + /// as returned by `AnyValueProvider.typeErasedStorage`. + /// - `valueProvider` is the `AnyValueProvider` that returned the type-erased value. + let conversion: (_ value: Any, _ valueProvider: AnyValueProvider) -> ValueRepresentation? +} + +// MARK: - PropertyName + +/// The name of a customizable property that can be used in an `AnimationKeypath` +/// - These values should be shared between the two rendering engines, +/// since they form the public API of the `AnimationKeypath` system. +enum PropertyName: String, CaseIterable { + case color = "Color" + case opacity = "Opacity" + case scale = "Scale" + case position = "Position" + case rotation = "Rotation" + case strokeWidth = "Stroke Width" + case gradientColors = "Colors" +} + +// MARK: CALayer properties + +extension LayerProperty { + static var position: LayerProperty { + .init( + caLayerKeypath: "transform.translation", + defaultValue: CGPoint(x: 0, y: 0), + customizableProperty: .position) + } + + static var positionX: LayerProperty { + .init( + caLayerKeypath: "transform.translation.x", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var positionY: LayerProperty { + .init( + caLayerKeypath: "transform.translation.y", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var scale: LayerProperty { + .init( + caLayerKeypath: "transform.scale", + defaultValue: 1, + customizableProperty: nil /* currently unsupported */ ) + } + + static var scaleX: LayerProperty { + .init( + caLayerKeypath: "transform.scale.x", + defaultValue: 1, + customizableProperty: .scaleX) + } + + static var scaleY: LayerProperty { + .init( + caLayerKeypath: "transform.scale.y", + defaultValue: 1, + customizableProperty: .scaleY) + } + + static var rotationX: LayerProperty { + .init( + caLayerKeypath: "transform.rotation.x", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var rotationY: LayerProperty { + .init( + caLayerKeypath: "transform.rotation.y", + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var rotationZ: LayerProperty { + .init( + caLayerKeypath: "transform.rotation.z", + defaultValue: 0, + customizableProperty: .rotation) + } + + static var anchorPoint: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.anchorPoint), + // This is intentionally not `GGPoint(x: 0.5, y: 0.5)` (the actual default) + // to opt `anchorPoint` out of the KVC `setValue` flow, which causes issues. + defaultValue: nil, + customizableProperty: nil /* currently unsupported */ ) + } + + static var opacity: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.opacity), + defaultValue: 1, + customizableProperty: .opacity) + } + + static var isHidden: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.isHidden), + defaultValue: false, + customizableProperty: nil /* unsupported */ ) + } + + static var transform: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.transform), + isDefaultValue: { transform in + guard let transform else { return false } + return CATransform3DIsIdentity(transform) + }, + customizableProperty: nil /* currently unsupported */ ) + } + + static var shadowOpacity: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.shadowOpacity), + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var shadowColor: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.shadowColor), + defaultValue: .rgb(0, 0, 0), + customizableProperty: nil /* currently unsupported */ ) + } + + static var shadowRadius: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.shadowRadius), + defaultValue: 3.0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var shadowOffset: LayerProperty { + .init( + caLayerKeypath: #keyPath(CALayer.shadowOffset), + defaultValue: CGSize(width: 0, height: -3.0), + customizableProperty: nil /* currently unsupported */ ) + } +} + +// MARK: CAShapeLayer properties + +extension LayerProperty { + static var path: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.path), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */ ) + } + + static var fillColor: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.fillColor), + defaultValue: nil, + customizableProperty: .color) + } + + static var lineWidth: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.lineWidth), + defaultValue: 1, + customizableProperty: .floatValue(.strokeWidth)) + } + + static var lineDashPhase: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.lineDashPhase), + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var strokeColor: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.strokeColor), + defaultValue: nil, + customizableProperty: .color) + } + + static var strokeStart: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.strokeStart), + defaultValue: 0, + customizableProperty: nil /* currently unsupported */ ) + } + + static var strokeEnd: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAShapeLayer.strokeEnd), + defaultValue: 1, + customizableProperty: nil /* currently unsupported */ ) + } +} + +// MARK: CAGradientLayer properties + +extension LayerProperty { + static var colors: LayerProperty<[CGColor]> { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.colors), + defaultValue: nil, + customizableProperty: .gradientColors) + } + + static var locations: LayerProperty<[CGFloat]> { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.locations), + defaultValue: nil, + customizableProperty: .gradientLocations) + } + + static var startPoint: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.startPoint), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */ ) + } + + static var endPoint: LayerProperty { + .init( + caLayerKeypath: #keyPath(CAGradientLayer.endPoint), + defaultValue: nil, + customizableProperty: nil /* currently unsupported */ ) + } +} + +// MARK: - CustomizableProperty types + +extension CustomizableProperty { + static var color: CustomizableProperty { + .init( + name: [.color], + conversion: { typeErasedValue, _ in + guard let color = typeErasedValue as? LottieColor else { + return nil + } + + return .rgba(CGFloat(color.r), CGFloat(color.g), CGFloat(color.b), CGFloat(color.a)) + }) + } + + static var opacity: CustomizableProperty { + .init( + name: [.opacity], + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector1D else { return nil } + + // Lottie animation files express opacity as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + return vector.cgFloatValue / 100 + }) + } + + static var scaleX: CustomizableProperty { + .init( + name: [.scale], + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector3D else { return nil } + + // Lottie animation files express scale as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + return vector.pointValue.x / 100 + }) + } + + static var scaleY: CustomizableProperty { + .init( + name: [.scale], + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector3D else { return nil } + + // Lottie animation files express scale as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + return vector.pointValue.y / 100 + }) + } + + static var rotation: CustomizableProperty { + .init( + name: [.rotation], + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector1D else { return nil } + + // Lottie animation files express rotation in degrees + // (e.g. 90º, 180º, 360º) so we covert to radians to get the + // values expected by Core Animation (e.g. π/2, π, 2π) + return vector.cgFloatValue * .pi / 180 + }) + } + + static var position: CustomizableProperty { + .init( + name: [.position], + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector3D else { return nil } + return vector.pointValue + }) + } + + static var gradientColors: CustomizableProperty<[CGColor]> { + .init( + name: [.gradientColors], + conversion: { _, typeErasedValueProvider in + guard let gradientValueProvider = typeErasedValueProvider as? GradientValueProvider else { return nil } + return gradientValueProvider.colors.map { $0.cgColorValue } + }) + } + + static var gradientLocations: CustomizableProperty<[CGFloat]> { + .init( + name: [.gradientColors], + conversion: { _, typeErasedValueProvider in + guard let gradientValueProvider = typeErasedValueProvider as? GradientValueProvider else { return nil } + return gradientValueProvider.locations.map { CGFloat($0) } + }) + } + + static func floatValue(_ name: PropertyName...) -> CustomizableProperty { + .init( + name: name, + conversion: { typeErasedValue, _ in + guard let vector = typeErasedValue as? LottieVector1D else { return nil } + return vector.cgFloatValue + }) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/OpacityAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/OpacityAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..b7bf2200eb3acd540cb8139550067b35d3fe18f3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/OpacityAnimation.swift @@ -0,0 +1,52 @@ +// Created by Cal Stephens on 5/17/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - OpacityAnimationModel + +protocol OpacityAnimationModel { + /// The opacity animation to apply to a `CALayer` + var opacity: KeyframeGroup { get } +} + +// MARK: - Transform + OpacityAnimationModel + +extension Transform: OpacityAnimationModel { } + +// MARK: - ShapeTransform + OpacityAnimationModel + +extension ShapeTransform: OpacityAnimationModel { } + +// MARK: - Fill + OpacityAnimationModel + +extension Fill: OpacityAnimationModel { } + +// MARK: - GradientFill + OpacityAnimationModel + +extension GradientFill: OpacityAnimationModel { } + +// MARK: - Stroke + OpacityAnimationModel + +extension Stroke: OpacityAnimationModel { } + +// MARK: - GradientStroke + OpacityAnimationModel + +extension GradientStroke: OpacityAnimationModel { } + +extension CALayer { + /// Adds the opacity animation from the given `OpacityAnimationModel` to this layer + @nonobjc + func addOpacityAnimation(for opacity: OpacityAnimationModel, context: LayerAnimationContext) throws { + try addAnimation( + for: .opacity, + keyframes: opacity.opacity, + value: { + // Lottie animation files express opacity as a numerical percentage value + // (e.g. 0%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.0, 0.5, 1.0). + $0.cgFloatValue / 100 + }, + context: context) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/RectangleAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/RectangleAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..8c617a67eb40c34f14374966178070c8d9054eab --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/RectangleAnimation.swift @@ -0,0 +1,54 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds animations for the given `Rectangle` to this `CALayer` + @nonobjc + func addAnimations( + for rectangle: Rectangle, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier, + roundedCorners: RoundedCorners?) + throws + { + try addAnimation( + for: .path, + keyframes: try rectangle.combinedKeyframes(roundedCorners: roundedCorners), + value: { keyframe in + BezierPath.rectangle( + position: keyframe.position.pointValue, + size: keyframe.size.sizeValue, + cornerRadius: keyframe.cornerRadius.cgFloatValue, + direction: rectangle.direction) + .cgPath() + .duplicated(times: pathMultiplier) + }, + context: context) + } +} + +extension Rectangle { + /// Data that represents how to render a rectangle at a specific point in time + struct Keyframe: Interpolatable { + let size: LottieVector3D + let position: LottieVector3D + let cornerRadius: LottieVector1D + + func interpolate(to: Rectangle.Keyframe, amount: CGFloat) -> Rectangle.Keyframe { + Rectangle.Keyframe( + size: size.interpolate(to: to.size, amount: amount), + position: position.interpolate(to: to.position, amount: amount), + cornerRadius: cornerRadius.interpolate(to: to.cornerRadius, amount: amount)) + } + } + + /// Creates a single array of animatable keyframes from the separate arrays of keyframes in this Rectangle + func combinedKeyframes(roundedCorners: RoundedCorners?) throws -> KeyframeGroup { + let cornerRadius = roundedCorners?.radius ?? cornerRadius + return Keyframes.combined( + size, position, cornerRadius, + makeCombinedResult: Rectangle.Keyframe.init) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/ShapeAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/ShapeAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..0559f29a2f8f1c7af41896d1452f01ec7d49636d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/ShapeAnimation.swift @@ -0,0 +1,236 @@ +// Created by Cal Stephens on 1/7/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + /// Adds a `path` animation for the given `ShapeItem` + @nonobjc + func addAnimations( + for shape: ShapeItem, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier, + roundedCorners: RoundedCorners?) + throws + { + switch shape { + case let customShape as Shape: + try addAnimations( + for: customShape.path, + context: context, + pathMultiplier: pathMultiplier, + roundedCorners: roundedCorners) + + case let combinedShape as CombinedShapeItem: + try addAnimations(for: combinedShape, context: context, pathMultiplier: pathMultiplier) + try context.compatibilityAssert(roundedCorners == nil, """ + Rounded corners support is not currently implemented for combined shape items + """) + + case let ellipse as Ellipse: + try addAnimations(for: ellipse, context: context, pathMultiplier: pathMultiplier) + + case let rectangle as Rectangle: + try addAnimations( + for: rectangle, + context: context, + pathMultiplier: pathMultiplier, + roundedCorners: roundedCorners) + + case let star as Star: + try addAnimations(for: star, context: context, pathMultiplier: pathMultiplier) + try context.compatibilityAssert(roundedCorners == nil, """ + Rounded corners support is currently not implemented for polygon items + """) + + default: + // None of the other `ShapeItem` subclasses draw a `path` + try context.logCompatibilityIssue("Unexpected shape type \(type(of: shape))") + return + } + } + + /// Adds a `fillColor` animation for the given `Fill` object + @nonobjc + func addAnimations(for fill: Fill, context: LayerAnimationContext) throws { + fillRule = fill.fillRule.caFillRule + + try addAnimation( + for: .fillColor, + keyframes: fill.color, + value: \.cgColorValue, + context: context) + + try addOpacityAnimation(for: fill, context: context) + } + + /// Adds animations for `strokeStart` and `strokeEnd` from the given `Trim` object + @nonobjc + func addAnimations(for trim: Trim, context: LayerAnimationContext) throws -> PathMultiplier { + let (strokeStartKeyframes, strokeEndKeyframes, pathMultiplier) = try trim.caShapeLayerKeyframes() + + try addAnimation( + for: .strokeStart, + keyframes: strokeStartKeyframes, + value: { strokeStart in + // Lottie animation files express stoke trims as a numerical percentage value + // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.25, 0.5, 1.0). + CGFloat(strokeStart.cgFloatValue) / CGFloat(pathMultiplier) / 100 + }, context: context) + + try addAnimation( + for: .strokeEnd, + keyframes: strokeEndKeyframes, + value: { strokeEnd in + // Lottie animation files express stoke trims as a numerical percentage value + // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.25, 0.5, 1.0). + CGFloat(strokeEnd.cgFloatValue) / CGFloat(pathMultiplier) / 100 + }, context: context) + + return pathMultiplier + } +} + +/// The number of times that a `CGPath` needs to be duplicated in order to support the animation's `Trim` keyframes +typealias PathMultiplier = Int + +extension Trim { + + // MARK: Fileprivate + + /// The `strokeStart` and `strokeEnd` keyframes to apply to a `CAShapeLayer`, + /// plus a `pathMultiplier` that should be applied to the layer's `path` so that + /// trim values larger than 100% can be displayed properly. + fileprivate func caShapeLayerKeyframes() + throws + -> (strokeStart: KeyframeGroup, strokeEnd: KeyframeGroup, pathMultiplier: PathMultiplier) + { + let strokeStart: KeyframeGroup + let strokeEnd: KeyframeGroup + + // CAShapeLayer requires strokeStart to be less than strokeEnd. This + // isn't required by the Lottie schema, so some animations may have + // strokeStart and strokeEnd flipped. + if startValueIsAlwaysLessOrEqualToThanEndValue() { + // If the start value is always _less than_ or equal to the end value + // then we can use the given values without any modifications + strokeStart = start + strokeEnd = end + } else if startValueIsAlwaysGreaterThanOrEqualToEndValue() { + // If the start value is always _greater than_ or equal to the end value, + // then we can just swap the start / end keyframes. This lets us avoid + // manually interpolating the keyframes values at each frame, which + // would be more expensive. + strokeStart = end + strokeEnd = start + } else { + // Otherwise if the start / end values ever swap places we have to + // fix the order on a per-keyframe basis, which may require manually + // interpolating the keyframe values at each frame. + (strokeStart, strokeEnd) = interpolatedAtEachFrame() + } + + // If there are no offsets, then the stroke values can be used as-is + guard + !offset.keyframes.isEmpty, + offset.keyframes.contains(where: { $0.value.cgFloatValue != 0 }) + else { + return (strokeStart, strokeEnd, 1) + } + + // Apply the offset to the start / end values at each frame + let offsetStrokeKeyframes = Keyframes.combined( + strokeStart, + strokeEnd, + offset, + makeCombinedResult: { start, end, offset -> (start: LottieVector1D, end: LottieVector1D) in + // Compute the adjusted value by converting the offset value to a stroke value + let offsetStart = start.cgFloatValue + (offset.cgFloatValue / 360 * 100) + let offsetEnd = end.cgFloatValue + (offset.cgFloatValue / 360 * 100) + return (start: LottieVector1D(offsetStart), end: LottieVector1D(offsetEnd)) + }) + + var adjustedStrokeStart = offsetStrokeKeyframes.map { $0.start } + var adjustedStrokeEnd = offsetStrokeKeyframes.map { $0.end } + + // If maximum stroke value is larger than 100%, then we have to create copies of the path + // so the total path length includes the maximum stroke + let startStrokes = adjustedStrokeStart.keyframes.map { $0.value.cgFloatValue } + let endStrokes = adjustedStrokeEnd.keyframes.map { $0.value.cgFloatValue } + let minimumStrokeMultiplier = Double(floor((startStrokes.min() ?? 0) / 100.0)) + let maximumStrokeMultiplier = Double(ceil((endStrokes.max() ?? 100) / 100.0)) + + if minimumStrokeMultiplier < 0 { + // Core Animation doesn't support negative stroke offsets, so we have to + // shift all of the offset values up by the minimum + adjustedStrokeStart = adjustedStrokeStart.map { LottieVector1D($0.value + (abs(minimumStrokeMultiplier) * 100.0)) } + adjustedStrokeEnd = adjustedStrokeEnd.map { LottieVector1D($0.value + (abs(minimumStrokeMultiplier) * 100.0)) } + } + + return ( + strokeStart: adjustedStrokeStart, + strokeEnd: adjustedStrokeEnd, + pathMultiplier: Int(abs(maximumStrokeMultiplier) + abs(minimumStrokeMultiplier))) + } + + // MARK: Private + + /// Checks whether or not the value for `trim.start` is less than + /// or equal to the value for every `trim.end` at every frame. + private func startValueIsAlwaysLessOrEqualToThanEndValue() -> Bool { + startAndEndValuesAllSatisfy { startValue, endValue in + startValue <= endValue + } + } + + /// Checks whether or not the value for `trim.start` is greater than + /// or equal to the value for every `trim.end` at every frame. + private func startValueIsAlwaysGreaterThanOrEqualToEndValue() -> Bool { + startAndEndValuesAllSatisfy { startValue, endValue in + startValue >= endValue + } + } + + private func startAndEndValuesAllSatisfy(_ condition: (_ start: CGFloat, _ end: CGFloat) -> Bool) -> Bool { + let keyframeTimes = Set(start.keyframes.map { $0.time } + end.keyframes.map { $0.time }) + + let startInterpolator = KeyframeInterpolator(keyframes: start.keyframes) + let endInterpolator = KeyframeInterpolator(keyframes: end.keyframes) + + for keyframeTime in keyframeTimes { + guard + let startAtTime = startInterpolator.value(frame: keyframeTime) as? LottieVector1D, + let endAtTime = endInterpolator.value(frame: keyframeTime) as? LottieVector1D + else { continue } + + if !condition(startAtTime.cgFloatValue, endAtTime.cgFloatValue) { + return false + } + } + + return true + } + + /// Interpolates the start and end keyframes, at each frame if necessary, + /// so that the value of `strokeStart` is always less than `strokeEnd`. + private func interpolatedAtEachFrame() + -> (strokeStart: KeyframeGroup, strokeEnd: KeyframeGroup) + { + let combinedKeyframes = Keyframes.combined( + start, + end, + makeCombinedResult: { startValue, endValue -> (start: LottieVector1D, end: LottieVector1D) in + if startValue.cgFloatValue < endValue.cgFloatValue { + return (start: startValue, end: endValue) + } else { + return (start: endValue, end: startValue) + } + }) + + return ( + strokeStart: combinedKeyframes.map { $0.start }, + strokeEnd: combinedKeyframes.map { $0.end }) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/StarAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/StarAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..62a762111be7c4f55e9a19da76cb26e716129ddf --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/StarAnimation.swift @@ -0,0 +1,116 @@ +// Created by Cal Stephens on 1/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CAShapeLayer { + + // MARK: Internal + + /// Adds animations for the given `Rectangle` to this `CALayer` + @nonobjc + func addAnimations( + for star: Star, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier) + throws + { + switch star.starType { + case .star: + try addStarAnimation(for: star, context: context, pathMultiplier: pathMultiplier) + case .polygon: + try addPolygonAnimation(for: star, context: context, pathMultiplier: pathMultiplier) + case .none: + break + } + } + + // MARK: Private + + @nonobjc + private func addStarAnimation( + for star: Star, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier) + throws + { + try addAnimation( + for: .path, + keyframes: try star.combinedKeyframes(), + value: { keyframe in + BezierPath.star( + position: keyframe.position.pointValue, + outerRadius: keyframe.outerRadius.cgFloatValue, + innerRadius: keyframe.innerRadius.cgFloatValue, + outerRoundedness: keyframe.outerRoundness.cgFloatValue, + innerRoundedness: keyframe.innerRoundness.cgFloatValue, + numberOfPoints: keyframe.points.cgFloatValue, + rotation: keyframe.rotation.cgFloatValue, + direction: star.direction) + .cgPath() + .duplicated(times: pathMultiplier) + }, + context: context) + } + + @nonobjc + private func addPolygonAnimation( + for star: Star, + context: LayerAnimationContext, + pathMultiplier: PathMultiplier) + throws + { + try addAnimation( + for: .path, + keyframes: try star.combinedKeyframes(), + value: { keyframe in + BezierPath.polygon( + position: keyframe.position.pointValue, + numberOfPoints: keyframe.points.cgFloatValue, + outerRadius: keyframe.outerRadius.cgFloatValue, + outerRoundedness: keyframe.outerRoundness.cgFloatValue, + rotation: keyframe.rotation.cgFloatValue, + direction: star.direction) + .cgPath() + .duplicated(times: pathMultiplier) + }, + context: context) + } +} + +extension Star { + /// Data that represents how to render a star at a specific point in time + struct Keyframe: Interpolatable { + let position: LottieVector3D + let outerRadius: LottieVector1D + let innerRadius: LottieVector1D + let outerRoundness: LottieVector1D + let innerRoundness: LottieVector1D + let points: LottieVector1D + let rotation: LottieVector1D + + func interpolate(to: Star.Keyframe, amount: CGFloat) -> Star.Keyframe { + Star.Keyframe( + position: position.interpolate(to: to.position, amount: amount), + outerRadius: outerRadius.interpolate(to: to.outerRadius, amount: amount), + innerRadius: innerRadius.interpolate(to: to.innerRadius, amount: amount), + outerRoundness: outerRoundness.interpolate(to: to.outerRoundness, amount: amount), + innerRoundness: innerRoundness.interpolate(to: to.innerRoundness, amount: amount), + points: points.interpolate(to: to.points, amount: amount), + rotation: rotation.interpolate(to: to.rotation, amount: amount)) + } + } + + /// Creates a single array of animatable keyframes from the separate arrays of keyframes in this star/polygon + func combinedKeyframes() throws -> KeyframeGroup { + Keyframes.combined( + position, + outerRadius, + innerRadius ?? KeyframeGroup(LottieVector1D(0)), + outerRoundness, + innerRoundness ?? KeyframeGroup(LottieVector1D(0)), + points, + rotation, + makeCombinedResult: Star.Keyframe.init) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/StrokeAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/StrokeAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..92b5e4a18b776bce70e85cbd8e0813f27dfc72bb --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/StrokeAnimation.swift @@ -0,0 +1,86 @@ +// Created by Cal Stephens on 2/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - StrokeShapeItem + +/// A `ShapeItem` that represents a stroke +protocol StrokeShapeItem: ShapeItem, OpacityAnimationModel { + var strokeColor: KeyframeGroup? { get } + var width: KeyframeGroup { get } + var lineCap: LineCap { get } + var lineJoin: LineJoin { get } + var miterLimit: Double { get } + var dashPattern: [DashElement]? { get } + func copy(width: KeyframeGroup) -> StrokeShapeItem +} + +// MARK: - Stroke + StrokeShapeItem + +extension Stroke: StrokeShapeItem { + var strokeColor: KeyframeGroup? { color } + + func copy(width: KeyframeGroup) -> StrokeShapeItem { + // Type-erase the copy from `Stroke` to `StrokeShapeItem` + let copy: Stroke = copy(width: width) + return copy + } +} + +// MARK: - GradientStroke + StrokeShapeItem + +extension GradientStroke: StrokeShapeItem { + var strokeColor: KeyframeGroup? { nil } + + func copy(width: KeyframeGroup) -> StrokeShapeItem { + // Type-erase the copy from `GradientStroke` to `StrokeShapeItem` + let copy: GradientStroke = copy(width: width) + return copy + } +} + +// MARK: - CAShapeLayer + StrokeShapeItem + +extension CAShapeLayer { + /// Adds animations for properties related to the given `Stroke` object (`strokeColor`, `lineWidth`, etc) + @nonobjc + func addStrokeAnimations(for stroke: StrokeShapeItem, context: LayerAnimationContext) throws { + lineJoin = stroke.lineJoin.caLineJoin + lineCap = stroke.lineCap.caLineCap + miterLimit = CGFloat(stroke.miterLimit) + + if let strokeColor = stroke.strokeColor { + try addAnimation( + for: .strokeColor, + keyframes: strokeColor, + value: \.cgColorValue, + context: context) + } + + try addAnimation( + for: .lineWidth, + keyframes: stroke.width, + value: \.cgFloatValue, + context: context) + + try addOpacityAnimation(for: stroke, context: context) + + if let (dashPattern, dashPhase) = stroke.dashPattern?.shapeLayerConfiguration { + let lineDashPattern = try dashPattern.map { + try KeyframeGroup(keyframes: $0) + .exactlyOneKeyframe(context: context, description: "stroke dashPattern").cgFloatValue + } + + if lineDashPattern.isSupportedLayerDashPattern { + self.lineDashPattern = lineDashPattern as [NSNumber] + } + + try addAnimation( + for: .lineDashPhase, + keyframes: KeyframeGroup(keyframes: dashPhase), + value: \.cgFloatValue, + context: context) + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/TransformAnimations.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/TransformAnimations.swift new file mode 100644 index 0000000000000000000000000000000000000000..806ff135d52768ca414df858eaa84e64d3459e92 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/TransformAnimations.swift @@ -0,0 +1,343 @@ +// Created by Cal Stephens on 12/17/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - TransformModel + +/// This protocol mirrors the interface of `Transform`, +/// but is also implemented by `ShapeTransform` to allow +/// both transform types to share the same animation implementation. +protocol TransformModel { + /// The anchor point of the transform. + var anchorPoint: KeyframeGroup { get } + + /// The position of the transform. This is nil if the position data was split. + var _position: KeyframeGroup? { get } + + /// The positionX of the transform. This is nil if the position property is set. + var _positionX: KeyframeGroup? { get } + + /// The positionY of the transform. This is nil if the position property is set. + var _positionY: KeyframeGroup? { get } + + /// The scale of the transform + var scale: KeyframeGroup { get } + + /// The rotation of the transform on X axis. + var rotationX: KeyframeGroup { get } + + /// The rotation of the transform on Y axis. + var rotationY: KeyframeGroup { get } + + /// The rotation of the transform on Z axis. + var rotationZ: KeyframeGroup { get } + + /// The skew of the transform (only present on `ShapeTransform`s) + var _skew: KeyframeGroup? { get } + + /// The skew axis of the transform (only present on `ShapeTransform`s) + var _skewAxis: KeyframeGroup? { get } +} + +// MARK: - Transform + TransformModel + +extension Transform: TransformModel { + var _position: KeyframeGroup? { position } + var _positionX: KeyframeGroup? { positionX } + var _positionY: KeyframeGroup? { positionY } + var _skew: KeyframeGroup? { nil } + var _skewAxis: KeyframeGroup? { nil } +} + +// MARK: - ShapeTransform + TransformModel + +extension ShapeTransform: TransformModel { + var anchorPoint: KeyframeGroup { anchor } + var _position: KeyframeGroup? { position } + var _positionX: KeyframeGroup? { nil } + var _positionY: KeyframeGroup? { nil } + var _skew: KeyframeGroup? { skew } + var _skewAxis: KeyframeGroup? { skewAxis } +} + +// MARK: - CALayer + TransformModel + +extension CALayer { + + // MARK: Internal + + /// Adds transform-related animations from the given `TransformModel` to this layer + /// - This _doesn't_ apply `transform.opacity`, which has to be handled separately + /// since child layers don't inherit the `opacity` of their parent. + @nonobjc + func addTransformAnimations( + for transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + if + // CALayers don't support animating skew with its own set of keyframes. + // If the transform includes a skew, we have to combine all of the transform + // components into a single set of keyframes. + transformModel.hasSkew + // Negative `scale.x` values aren't applied correctly by Core Animation when animating + // `transform.scale.x` and `transform.scale.y` using separate `CAKeyframeAnimation`s + // (https://openradar.appspot.com/FB9862872). If the transform includes negative `scale.x` + // values, we have to combine all of the transform components into a single set of keyframes. + || transformModel.hasNegativeXScaleValues + { + try addCombinedTransformAnimation(for: transformModel, context: context) + } + + else { + try addPositionAnimations(from: transformModel, context: context) + try addAnchorPointAnimation(from: transformModel, context: context) + try addScaleAnimations(from: transformModel, context: context) + try addRotationAnimations(from: transformModel, context: context) + } + } + + // MARK: Private + + @nonobjc + private func addPositionAnimations( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + if let positionKeyframes = transformModel._position { + try addAnimation( + for: .position, + keyframes: positionKeyframes, + value: \.pointValue, + context: context) + } else if + let xKeyframes = transformModel._positionX, + let yKeyframes = transformModel._positionY + { + try addAnimation( + for: .positionX, + keyframes: xKeyframes, + value: \.cgFloatValue, + context: context) + + try addAnimation( + for: .positionY, + keyframes: yKeyframes, + value: \.cgFloatValue, + context: context) + } else { + try context.logCompatibilityIssue(""" + `Transform` values must provide either `position` or `positionX` / `positionY` keyframes + """) + } + } + + @nonobjc + private func addAnchorPointAnimation( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .anchorPoint, + keyframes: transformModel.anchorPoint, + value: { absoluteAnchorPoint in + guard bounds.width > 0, bounds.height > 0 else { + context.logger.assertionFailure("Size must be non-zero before an animation can be played") + return .zero + } + + // Lottie animation files express anchorPoint as an absolute point value, + // so we have to divide by the width/height of this layer to get the + // relative decimal values expected by Core Animation. + return CGPoint( + x: CGFloat(absoluteAnchorPoint.x) / bounds.width, + y: CGFloat(absoluteAnchorPoint.y) / bounds.height) + }, + context: context) + } + + @nonobjc + private func addScaleAnimations( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + try addAnimation( + for: .scaleX, + keyframes: transformModel.scale, + value: { scale in + // Lottie animation files express scale as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + CGFloat(scale.x) / 100 + }, + context: context) + + try addAnimation( + for: .scaleY, + keyframes: transformModel.scale, + value: { scale in + // Lottie animation files express scale as a numerical percentage value + // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.5, 1.0, 2.0). + CGFloat(scale.y) / 100 + }, + context: context) + } + + private func addRotationAnimations( + from transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + let containsXRotationValues = transformModel.rotationX.keyframes.contains(where: { $0.value.cgFloatValue != 0 }) + let containsYRotationValues = transformModel.rotationY.keyframes.contains(where: { $0.value.cgFloatValue != 0 }) + + // When `rotation.x` or `rotation.y` is used, it doesn't render property in test snapshots + // but do renders correctly on the simulator / device + if TestHelpers.snapshotTestsAreRunning { + if containsXRotationValues { + context.logger.warn(""" + `rotation.x` values are not displayed correctly in snapshot tests + """) + } + + if containsYRotationValues { + context.logger.warn(""" + `rotation.y` values are not displayed correctly in snapshot tests + """) + } + } + + // Lottie animation files express rotation in degrees + // (e.g. 90º, 180º, 360º) so we convert to radians to get the + // values expected by Core Animation (e.g. π/2, π, 2π) + + try addAnimation( + for: .rotationX, + keyframes: transformModel.rotationX, + value: { rotationDegrees in + rotationDegrees.cgFloatValue * .pi / 180 + }, + context: context) + + try addAnimation( + for: .rotationY, + keyframes: transformModel.rotationY, + value: { rotationDegrees in + rotationDegrees.cgFloatValue * .pi / 180 + }, + context: context) + + try addAnimation( + for: .rotationZ, + keyframes: transformModel.rotationZ, + value: { rotationDegrees in + // Lottie animation files express rotation in degrees + // (e.g. 90º, 180º, 360º) so we convert to radians to get the + // values expected by Core Animation (e.g. π/2, π, 2π) + rotationDegrees.cgFloatValue * .pi / 180 + }, + context: context) + } + + /// Adds an animation for the entire `transform` key by combining all of the + /// position / size / rotation / skew animations into a single set of keyframes. + /// This is more expensive that animating each component separately, since + /// it may require manually interpolating the keyframes at each frame. + private func addCombinedTransformAnimation( + for transformModel: TransformModel, + context: LayerAnimationContext) + throws + { + let requiresManualInterpolation = + // Core Animation doesn't animate skew changes properly. If the skew value + // changes over the course of the animation then we have to manually + // compute the `CATransform3D` for each frame individually. + transformModel.hasSkewAnimation + // `addAnimation` requires that we use an `Interpolatable` value, but we can't interpolate a `CATransform3D`. + // Since this is only necessary when using `complexTimeRemapping`, we can avoid this by manually interpolating + // when `context.mustUseComplexTimeRemapping` is true and just returning a `Hold` container. + // Since our keyframes are already manually interpolated, they won't need to be interpolated again. + || context.mustUseComplexTimeRemapping + + let combinedTransformKeyframes = Keyframes.combined( + transformModel.anchorPoint, + transformModel._position ?? KeyframeGroup(LottieVector3D(x: 0.0, y: 0.0, z: 0.0)), + transformModel._positionX ?? KeyframeGroup(LottieVector1D(0)), + transformModel._positionY ?? KeyframeGroup(LottieVector1D(0)), + transformModel.scale, + transformModel.rotationX, + transformModel.rotationY, + transformModel.rotationZ, + transformModel._skew ?? KeyframeGroup(LottieVector1D(0)), + transformModel._skewAxis ?? KeyframeGroup(LottieVector1D(0)), + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { + anchor, position, positionX, positionY, scale, rotationX, rotationY, rotationZ, skew, skewAxis + -> Hold in + + let transformPosition: CGPoint = + if transformModel._positionX != nil, transformModel._positionY != nil { + CGPoint(x: positionX.cgFloatValue, y: positionY.cgFloatValue) + } else { + position.pointValue + } + + let transform = CATransform3D.makeTransform( + anchor: anchor.pointValue, + position: transformPosition, + scale: scale.sizeValue, + rotationX: rotationX.cgFloatValue, + rotationY: rotationY.cgFloatValue, + rotationZ: rotationZ.cgFloatValue, + skew: skew.cgFloatValue, + skewAxis: skewAxis.cgFloatValue) + + return Hold(value: transform) + }) + + try addAnimation( + for: .transform, + keyframes: combinedTransformKeyframes, + value: { $0.value }, + context: context) + } + +} + +extension TransformModel { + /// Whether or not this transform has a non-zero skew value + var hasSkew: Bool { + guard + let _skew, + let _skewAxis, + !_skew.keyframes.isEmpty, + !_skewAxis.keyframes.isEmpty + else { + return false + } + + return _skew.keyframes.contains(where: { $0.value.cgFloatValue != 0 }) + } + + /// Whether or not this transform has a non-zero skew value which animates + var hasSkewAnimation: Bool { + guard + hasSkew, + let _skew, + let _skewAxis + else { return false } + + return _skew.keyframes.count > 1 + || _skewAxis.keyframes.count > 1 + } + + /// Whether or not this `TransformModel` has any negative X scale values + var hasNegativeXScaleValues: Bool { + scale.keyframes.contains(where: { $0.value.x < 0 }) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/VisibilityAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/VisibilityAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..7d6c4b71ea1ac5b7c396c7dac7a69f7094124c73 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Animations/VisibilityAnimation.swift @@ -0,0 +1,63 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CALayer { + /// Adds an animation for the given `inTime` and `outTime` to this `CALayer` + @nonobjc + func addVisibilityAnimation( + inFrame: AnimationFrameTime, + outFrame: AnimationFrameTime, + context: LayerAnimationContext) + throws + { + /// If this layer uses `complexTimeRemapping`, use the `addAnimation` codepath + /// which uses `Keyframes.manuallyInterpolatedWithTimeRemapping`. + if context.mustUseComplexTimeRemapping { + let isHiddenKeyframes = KeyframeGroup(keyframes: [ + Keyframe(value: true, time: 0, isHold: true), // hidden, before `inFrame` + Keyframe(value: false, time: inFrame, isHold: true), // visible + Keyframe(value: true, time: outFrame, isHold: true), // hidden, after `outFrame` + ]) + + try addAnimation( + for: .isHidden, + keyframes: isHiddenKeyframes.map { Hold(value: $0) }, + value: { $0.value }, + context: context) + } + + /// Otherwise continue using the legacy codepath that doesn't support complex time remapping. + /// - TODO: We could remove this codepath in favor of always using the simpler codepath above, + /// but would have to solve https://github.com/airbnb/lottie-ios/pull/2254 for that codepath. + else { + let animation = CAKeyframeAnimation(keyPath: #keyPath(isHidden)) + animation.calculationMode = .discrete + + animation.values = [ + true, // hidden, before `inFrame` + false, // visible + true, // hidden, after `outFrame` + ] + + // From the documentation of `keyTimes`: + // - If the calculationMode is set to discrete, the first value in the array + // must be 0.0 and the last value must be 1.0. The array should have one more + // entry than appears in the values array. For example, if there are two values, + // there should be three key times. + animation.keyTimes = [ + NSNumber(value: 0.0), + NSNumber(value: max(Double(try context.progressTime(for: inFrame)), 0)), + // Anything visible during the last frame should stay visible until the absolute end of the animation. + // - This matches the behavior of the main thread rendering engine. + context.simpleTimeRemapping(outFrame) == context.animation.endFrame + ? NSNumber(value: Double(1.0)) + : NSNumber(value: min(Double(try context.progressTime(for: outFrame)), 1)), + NSNumber(value: 1.0), + ] + + add(animation, timedWith: context) + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/CompatibilityTracker.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/CompatibilityTracker.swift new file mode 100644 index 0000000000000000000000000000000000000000..ce7e09793ee8e8314e1565808edc7d8ec14be40d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/CompatibilityTracker.swift @@ -0,0 +1,130 @@ +// Created by Cal Stephens on 5/4/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - CompatibilityIssue + +/// A compatibility issue that was encountered while setting up an animation with the Core Animation engine +struct CompatibilityIssue: CustomStringConvertible { + let message: String + let context: String + + var description: String { + "[\(context)] \(message)" + } +} + +// MARK: - CompatibilityTracker + +/// A type that tracks whether or not an animation is compatible with the Core Animation engine +final class CompatibilityTracker { + + // MARK: Lifecycle + + init(mode: Mode, logger: LottieLogger) { + self.mode = mode + self.logger = logger + } + + // MARK: Internal + + /// How compatibility issues should be handled + enum Mode { + /// When a compatibility issue is encountered, an error will be thrown immediately, + /// aborting the animation setup process as soon as possible. + case abort + + /// When a compatibility issue is encountered, it is stored in `CompatibilityTracker.issues` + case track + } + + enum Error: Swift.Error { + case encounteredCompatibilityIssue(CompatibilityIssue) + } + + /// Records a compatibility issue that will be reported according to `CompatibilityTracker.Mode` + func logIssue(message: String, context: String) throws { + logger.assert(!context.isEmpty, "Compatibility issue context is unexpectedly empty") + + let issue = CompatibilityIssue( + // Compatibility messages are usually written in source files using multi-line strings, + // but converting them to be a single line makes it easier to read the ultimate log output. + message: message.replacingOccurrences(of: "\n", with: " "), + context: context) + + switch mode { + case .abort: + throw CompatibilityTracker.Error.encounteredCompatibilityIssue(issue) + case .track: + issues.append(issue) + } + } + + /// Asserts that a condition is true, otherwise logs a compatibility issue that will be reported + /// according to `CompatibilityTracker.Mode` + func assert( + _ condition: Bool, + _ message: @autoclosure () -> String, + context: @autoclosure () -> String) + throws + { + if !condition { + try logIssue(message: message(), context: context()) + } + } + + /// Reports the compatibility issues that were recorded when setting up the animation, + /// and clears the set of tracked issues. + func reportCompatibilityIssues(_ handler: ([CompatibilityIssue]) -> Void) { + handler(issues) + issues = [] + } + + // MARK: Private + + private let mode: Mode + private let logger: LottieLogger + + /// Compatibility issues encountered while setting up the animation + private var issues = [CompatibilityIssue]() + +} + +// MARK: - CompatibilityTrackerProviding + +protocol CompatibilityTrackerProviding { + var compatibilityTracker: CompatibilityTracker { get } + var compatibilityIssueContext: String { get } +} + +extension CompatibilityTrackerProviding { + /// Records a compatibility issue that will be reported according to `CompatibilityTracker.Mode` + func logCompatibilityIssue(_ message: String) throws { + try compatibilityTracker.logIssue(message: message, context: compatibilityIssueContext) + } + + /// Asserts that a condition is true, otherwise logs a compatibility issue that will be reported + /// according to `CompatibilityTracker.Mode` + func compatibilityAssert( + _ condition: Bool, + _ message: @autoclosure () -> String) + throws + { + try compatibilityTracker.assert(condition, message(), context: compatibilityIssueContext) + } +} + +// MARK: - LayerContext + CompatibilityTrackerProviding + +extension LayerContext: CompatibilityTrackerProviding { + var compatibilityIssueContext: String { + layerName + } +} + +// MARK: - LayerAnimationContext + CompatibilityTrackerProviding + +extension LayerAnimationContext: CompatibilityTrackerProviding { + var compatibilityIssueContext: String { + currentKeypath.fullPath + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/CoreAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/CoreAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..594368dc510f3605430d7b84094d0519d179582b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/CoreAnimationLayer.swift @@ -0,0 +1,599 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - CoreAnimationLayer + +/// The root `CALayer` of the Core Animation rendering engine +final class CoreAnimationLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + /// Initializes a `CALayer` that renders the given animation using `CAAnimation`s. + /// - This initializer is throwing, but will only throw when using + /// `CompatibilityTracker.Mode.abort`. + init( + animation: LottieAnimation, + imageProvider: AnimationImageProvider, + textProvider: AnimationKeypathTextProvider, + fontProvider: AnimationFontProvider, + maskAnimationToBounds: Bool, + compatibilityTrackerMode: CompatibilityTracker.Mode, + logger: LottieLogger) + throws + { + self.animation = animation + self.imageProvider = imageProvider + self.textProvider = textProvider + self.fontProvider = fontProvider + self.logger = logger + compatibilityTracker = CompatibilityTracker(mode: compatibilityTrackerMode, logger: logger) + valueProviderStore = ValueProviderStore(logger: logger) + super.init() + masksToBounds = maskAnimationToBounds + setup() + try setupChildLayers() + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("init(layer:) incorrectly called with \(type(of: layer))") + } + + animation = typedLayer.animation + currentAnimationConfiguration = typedLayer.currentAnimationConfiguration + imageProvider = typedLayer.imageProvider + textProvider = typedLayer.textProvider + fontProvider = typedLayer.fontProvider + didSetUpAnimation = typedLayer.didSetUpAnimation + compatibilityTracker = typedLayer.compatibilityTracker + logger = typedLayer.logger + valueProviderStore = typedLayer.valueProviderStore + super.init(layer: typedLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + /// Timing-related configuration to apply to this layer's child `CAAnimation`s + /// - This is effectively a configurable subset of `CAMediaTiming` + struct CAMediaTimingConfiguration: Equatable { + var autoreverses = false + var repeatCount: Float = 0 + var speed: Float = 1 + var timeOffset: TimeInterval = 0 + } + + enum PlaybackState: Equatable { + /// The animation is has started playing, and may still be playing. + /// - When animating with a finite duration (e.g. `playOnce`), playback + /// state will still be `playing` when the animation completes. + /// To check if the animation is currently playing, prefer `isAnimationPlaying`. + case playing + /// The animation is statically displaying a specific frame + case paused(frame: AnimationFrameTime) + } + + /// Configuration used by the `playAnimation` method + struct AnimationConfiguration: Equatable { + var animationContext: AnimationContext + var timingConfiguration: CAMediaTimingConfiguration + var recordHierarchyKeypath: ((String) -> Void)? + + static func ==(_ lhs: AnimationConfiguration, _ rhs: AnimationConfiguration) -> Bool { + lhs.animationContext == rhs.animationContext + && lhs.timingConfiguration == rhs.timingConfiguration + && ((lhs.recordHierarchyKeypath == nil) == (rhs.recordHierarchyKeypath == nil)) + } + } + + /// The parent `LottieAnimationLayer` that manages this layer + weak var lottieAnimationLayer: LottieAnimationLayer? + + /// A closure that is called after this layer sets up its animation. + /// If the animation setup was unsuccessful and encountered compatibility issues, + /// those issues are included in this call. + var didSetUpAnimation: (([CompatibilityIssue]) -> Void)? + + /// The `AnimationImageProvider` that `ImageLayer`s use to retrieve images, + /// referenced by name in the animation json. + var imageProvider: AnimationImageProvider { + didSet { reloadImages() } + } + + /// The `AnimationKeypathTextProvider` that `TextLayer`'s use to retrieve texts, + /// that they should use to render their text context + var textProvider: AnimationKeypathTextProvider { + didSet { + // We need to rebuild the current animation after updating the text provider, + // since this is used in `TextLayer.setupAnimations(context:)` + rebuildCurrentAnimation() + } + } + + /// The `FontProvider` that `TextLayer`s use to retrieve the `CTFont` + /// that they should use to render their text content + var fontProvider: AnimationFontProvider { + didSet { reloadFonts() } + } + + /// Queues the animation with the given timing configuration + /// to begin playing at the next `display()` call. + /// - This batches together animations so that even if `playAnimation` + /// is called multiple times in the same run loop cycle, the animation + /// will only be set up a single time. + func playAnimation( + configuration: AnimationConfiguration, + playbackState: PlaybackState = .playing) + { + pendingAnimationConfiguration = ( + animationConfiguration: configuration, + playbackState: playbackState) + + setNeedsDisplay() + } + + override func layoutSublayers() { + super.layoutSublayers() + + // If no animation has been set up yet, display the first frame + // now that the layer hierarchy has been setup and laid out + if + pendingAnimationConfiguration == nil, + currentAnimationConfiguration == nil, + bounds.size != .zero + { + currentFrame = animation.frameTime(forProgress: animationProgress) + } + } + + override func display() { + // We intentionally don't call `super.display()`, since this layer + // doesn't directly render any content. + // - This fixes an issue where certain animations would unexpectedly + // allocate a very large amount of memory (400mb+). + // - Alternatively this layer could subclass `CATransformLayer`, + // but this causes Core Animation to emit unnecessary logs. + if var pendingAnimationConfiguration { + pendingAnimationConfigurationModification?(&pendingAnimationConfiguration.animationConfiguration) + pendingAnimationConfigurationModification = nil + self.pendingAnimationConfiguration = nil + + do { + try setupAnimation(for: pendingAnimationConfiguration.animationConfiguration) + } catch { + if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error { + // Even though the animation setup failed, we still update the layer's playback state + // so it can be read by the parent `LottieAnimationView` when handling this error + currentPlaybackState = pendingAnimationConfiguration.playbackState + + didSetUpAnimation?([compatibilityIssue]) + return + } + } + + currentPlaybackState = pendingAnimationConfiguration.playbackState + + compatibilityTracker.reportCompatibilityIssues { compatibilityIssues in + didSetUpAnimation?(compatibilityIssues) + } + } + } + + // MARK: Private + + /// The configuration for the most recent animation which has been + /// queued by calling `playAnimation` but not yet actually set up + private var pendingAnimationConfiguration: ( + animationConfiguration: AnimationConfiguration, + playbackState: PlaybackState)? + + /// A modification that should be applied to the next animation configuration + private var pendingAnimationConfigurationModification: ((inout AnimationConfiguration) -> Void)? + + /// Configuration for the animation that is currently setup in this layer + private var currentAnimationConfiguration: AnimationConfiguration? + + /// The current progress of the placeholder `CAAnimation`, + /// which is also the realtime animation progress of this layer's animation + @objc private var animationProgress: CGFloat = 0 + + private let animation: LottieAnimation + private let valueProviderStore: ValueProviderStore + private let compatibilityTracker: CompatibilityTracker + private let logger: LottieLogger + private let loggingState = LoggingState() + + /// The current playback state of the animation that is displayed in this layer + private var currentPlaybackState: PlaybackState? { + didSet { + guard playbackState != oldValue else { return } + + switch playbackState { + case .playing, nil: + timeOffset = 0 + case .paused(let frame): + timeOffset = animation.time(forFrame: frame) + } + } + } + + /// The current or pending playback state of the animation displayed in this layer + private var playbackState: PlaybackState? { + pendingAnimationConfiguration?.playbackState ?? currentPlaybackState + } + + /// Context used when setting up and configuring sublayers + private var layerContext: LayerContext { + LayerContext( + animation: animation, + imageProvider: imageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + compatibilityTracker: compatibilityTracker, + layerName: "root layer") + } + + private func setup() { + bounds = animation.bounds + } + + private func setupChildLayers() throws { + try setupLayerHierarchy( + for: animation.layers, + context: layerContext) + + try validateReasonableNumberOfTimeRemappingLayers() + } + + /// Immediately builds and begins playing `CAAnimation`s for each sublayer + private func setupAnimation(for configuration: AnimationConfiguration) throws { + // Remove any existing animations from the layer hierarchy + removeAnimations() + + currentAnimationConfiguration = configuration + + let layerContext = LayerAnimationContext( + animation: animation, + timingConfiguration: configuration.timingConfiguration, + startFrame: configuration.animationContext.playFrom, + endFrame: configuration.animationContext.playTo, + valueProviderStore: valueProviderStore, + compatibilityTracker: compatibilityTracker, + logger: logger, + loggingState: loggingState, + currentKeypath: AnimationKeypath(keys: []), + textProvider: textProvider, + recordHierarchyKeypath: configuration.recordHierarchyKeypath) + + // Perform a layout pass if necessary so all of the sublayers + // have the most up-to-date sizing information + layoutIfNeeded() + + // Set the speed of this layer, which will be inherited + // by all sublayers and their animations. + // - This is required to support scrubbing with a speed of 0 + speed = configuration.timingConfiguration.speed + + // Setup a placeholder animation to let us track the realtime animation progress + setupPlaceholderAnimation(context: layerContext) + + // Set up the new animations with the current `TimingConfiguration` + for animationLayer in sublayers ?? [] { + try (animationLayer as? AnimationLayer)?.setupAnimations(context: layerContext) + } + } + + /// Sets up a placeholder `CABasicAnimation` that tracks the current + /// progress of this animation (between 0 and 1). This lets us provide + /// realtime animation progress via `self.currentFrame`. + private func setupPlaceholderAnimation(context: LayerAnimationContext) { + let animationProgressTracker = CABasicAnimation(keyPath: #keyPath(animationProgress)) + animationProgressTracker.fromValue = 0 + animationProgressTracker.toValue = 1 + + let timedProgressAnimation = animationProgressTracker.timed(with: context, for: self) + timedProgressAnimation.delegate = currentAnimationConfiguration?.animationContext.closure + + // Remove the progress animation once complete so we know when the animation + // has finished playing (if it doesn't loop infinitely) + timedProgressAnimation.isRemovedOnCompletion = true + + add(timedProgressAnimation, forKey: #keyPath(animationProgress)) + } + + /// Removes the current `CAAnimation`s, and rebuilds new animations + /// using the same configuration as the previous animations. + private func rebuildCurrentAnimation() { + guard + // Don't replace any pending animations that are queued to begin + // on the next run loop cycle, since an existing pending animation + // will cause the animation to be rebuilt anyway. + pendingAnimationConfiguration == nil + else { return } + + if isAnimationPlaying == true { + lottieAnimationLayer?.updateInFlightAnimation() + } else { + let currentFrame = currentFrame + removeAnimations() + self.currentFrame = currentFrame + } + } + +} + +// MARK: RootAnimationLayer + +extension CoreAnimationLayer: RootAnimationLayer { + + var primaryAnimationKey: AnimationKey { + .specific(#keyPath(animationProgress)) + } + + /// Whether or not the animation is currently playing. + /// - Handles case where CAAnimations with a finite duration animation (e.g. `playOnce`) + /// have finished playing but still present on this layer. + var isAnimationPlaying: Bool? { + switch pendingAnimationConfiguration?.playbackState { + case .playing: + true + case .paused: + false + case nil: + switch playbackState { + case .playing: + animation(forKey: #keyPath(animationProgress)) != nil + case nil, .paused: + false + } + } + } + + /// The current frame of the animation being displayed, + /// accounting for the realtime progress of any active CAAnimations. + var currentFrame: AnimationFrameTime { + get { + switch playbackState { + case .paused(let frame): + return frame + + case .playing, nil: + // When in the `playing` state, the animation is either actively playing + // or is completed on the final frame of a non-repeating animation. + // When a non-repeating animation is complete, `animation(forKey: #keyPath(animationProgress))` + // is no longer present and the Core-Animation-managed `animationProgress` value is just 0. + // In that case, since the animation is complete, we just return the final frame that was played to. + let animationCurrentlyPlaying = animation(forKey: #keyPath(animationProgress)) != nil + + if !animationCurrentlyPlaying, let configuration = currentAnimationConfiguration { + return configuration.animationContext.playTo + } else { + return animation.frameTime(forProgress: (presentation() ?? self).animationProgress) + } + } + } + set { + // We can display a specific frame of the animation by setting + // `timeOffset` of this layer. This requires setting up the layer hierarchy + // with a specific configuration (speed=0, etc) at least once. But if + // the layer hierarchy is already set up correctly, we can update the + // `timeOffset` very cheaply. + let requiredAnimationConfiguration = AnimationConfiguration( + animationContext: AnimationContext( + playFrom: animation.startFrame, + // Normal animation playback (like when looping) skips the last frame. + // However when the animation is paused, we need to be able to render the final frame. + // To allow this we have to extend the length of the animation by one frame. + playTo: animation.endFrame + 1, + closure: nil), + timingConfiguration: CAMediaTimingConfiguration(speed: 0)) + + if + pendingAnimationConfiguration == nil, + currentAnimationConfiguration == requiredAnimationConfiguration + { + currentPlaybackState = .paused(frame: newValue) + } + + else { + playAnimation( + configuration: requiredAnimationConfiguration, + playbackState: .paused(frame: newValue)) + } + } + } + + var renderScale: CGFloat { + get { contentsScale } + set { + contentsScale = newValue + + for sublayer in allSublayers { + sublayer.contentsScale = newValue + } + } + } + + var respectAnimationFrameRate: Bool { + get { false } + set { + logger.assertionFailure(""" + The Core Animation rendering engine currently doesn't support `respectAnimationFrameRate`) + """) + } + } + + var _animationLayers: [CALayer] { + (sublayers ?? []).filter { $0 is AnimationLayer } + } + + func reloadImages() { + // When the image provider changes, we have to update all `ImageLayer`s + // so they can query the most up-to-date image from the new image provider. + for sublayer in allSublayers { + if let imageLayer = sublayer as? ImageLayer { + imageLayer.setupImage(context: layerContext) + } + } + } + + func reloadFonts() { + // When the text provider changes, we have to update all `TextLayer`s + // so they can query the most up-to-date font from the new font provider. + for sublayer in allSublayers { + if let textLayer = sublayer as? TextLayer { + try? textLayer.configureRenderLayer(with: layerContext) + } + } + } + + func forceDisplayUpdate() { + // Unimplemented + // - We can't call `display()` here, because it would cause unexpected frame animations: + // https://github.com/airbnb/lottie-ios/issues/2193 + } + + func logHierarchyKeypaths() { + for keypath in allHierarchyKeypaths() { + logger.info(keypath) + } + } + + func allHierarchyKeypaths() -> [String] { + guard pendingAnimationConfiguration?.animationConfiguration ?? currentAnimationConfiguration != nil else { + logger.info("Cannot log hierarchy keypaths until animation has been set up at least once") + return [] + } + + logger.info("Lottie: Rebuilding animation with hierarchy keypath logging enabled") + + var allAnimationKeypaths = [String]() + pendingAnimationConfigurationModification = { configuration in + configuration.recordHierarchyKeypath = { keypath in + allAnimationKeypaths.append(keypath) + } + } + + rebuildCurrentAnimation() + displayIfNeeded() + + return allAnimationKeypaths + } + + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + valueProviderStore.setValueProvider(valueProvider, keypath: keypath) + + // We need to rebuild the current animation after registering a value provider, + // since any existing `CAAnimation`s could now be out of date. + rebuildCurrentAnimation() + } + + func getValue(for _: AnimationKeypath, atFrame _: AnimationFrameTime?) -> Any? { + logger.assertionFailure(""" + The Core Animation rendering engine doesn't support querying values for individual frames + """) + return nil + } + + func getOriginalValue(for _: AnimationKeypath, atFrame _: AnimationFrameTime?) -> Any? { + logger.assertionFailure(""" + The Core Animation rendering engine doesn't support querying values for individual frames + """) + return nil + } + + func layer(for _: AnimationKeypath) -> CALayer? { + logger.assertionFailure(""" + The Core Animation rendering engine doesn't support retrieving `CALayer`s by keypath + """) + return nil + } + + func animatorNodes(for _: AnimationKeypath) -> [AnimatorNode]? { + logger.assertionFailure(""" + The Core Animation rendering engine does not use `AnimatorNode`s + """) + return nil + } + + func removeAnimations() { + currentAnimationConfiguration = nil + currentPlaybackState = nil + removeAllAnimations() + + for sublayer in allSublayers { + sublayer.removeAllAnimations() + } + } + + /// Time remapping in the Core Animation rendering engine requires manually interpolating + /// every frame of every animation. For very large animations with a huge number of layers, + /// this can be prohibitively expensive. + func validateReasonableNumberOfTimeRemappingLayers() throws { + let numberOfLayersWithTimeRemapping = numberOfLayersWithTimeRemapping + let numberOfFrames = Int(animation.framerate * animation.duration) + let totalCost = numberOfLayersWithTimeRemapping * numberOfFrames + + /// Cap the cost / complexity of animations that use Core Animation time remapping. + /// - Short, simple animations perform well, but long and complex animations perform poorly. + /// - We count the total number of frames that will need to be manually interpolated, which is + /// the number of layers with time remapping enabled times the total number of frames. + /// - The cap is arbitrary, and is currently: + /// - 1000 layers for a one second animation at 60fp + /// - 500 layers for a two second animation at 60fps, etc + /// - All of the sample animations in the lottie-ios repo below this cap perform well. + /// If users report animations below this cap that perform poorly, we can lower the cap. + let maximumAllowedCost = 1000 * 60 + + try layerContext.compatibilityAssert( + totalCost < maximumAllowedCost, + """ + This animation has a very large number of layers with time remapping (\(numberOfLayersWithTimeRemapping) \ + layers over \(numberOfFrames) frames) so will perform poorly with the Core Animation rendering engine. + """) + } + +} + +// MARK: - CALayer + allSublayers + +extension CALayer { + /// All of the layers in the layer tree that are descendants from this later + @nonobjc + var allSublayers: [CALayer] { + var allSublayers: [CALayer] = [] + + for sublayer in sublayers ?? [] { + allSublayers.append(sublayer) + allSublayers.append(contentsOf: sublayer.allSublayers) + } + + return allSublayers + } + + /// The number of layers in this layer hierarchy that have a time remapping applied + @nonobjc + var numberOfLayersWithTimeRemapping: Int { + var numberOfSublayersWithTimeRemapping = 0 + + for sublayer in sublayers ?? [] { + if + let preCompLayer = sublayer as? PreCompLayer, + preCompLayer.preCompLayer.timeRemapping != nil + { + numberOfSublayersWithTimeRemapping += preCompLayer.allSublayers.count + } else { + numberOfSublayersWithTimeRemapping += sublayer.numberOfLayersWithTimeRemapping + } + } + + return numberOfSublayersWithTimeRemapping + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift new file mode 100644 index 0000000000000000000000000000000000000000..fe9e9e02683032eb456b3dcb4221f73dd393aa4d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift @@ -0,0 +1,35 @@ +// Created by Cal Stephens on 12/15/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - CALayer + fillBoundsOfSuperlayer + +extension CALayer { + /// Updates the `bounds` of this layer to fill the bounds of its `superlayer` + /// without setting `frame` (which is not permitted if the layer can rotate) + @nonobjc + func fillBoundsOfSuperlayer() { + guard let superlayer else { return } + + if let customLayerLayer = self as? CustomLayoutLayer { + customLayerLayer.layout(superlayerBounds: superlayer.bounds) + } + + else { + // By default the `anchorPoint` of a layer is `CGPoint(x: 0.5, y: 0.5)`. + // Setting it to `.zero` makes the layer have the same coordinate space + // as its superlayer, which lets use use `superlayer.bounds` directly. + anchorPoint = .zero + + bounds = superlayer.bounds + } + } +} + +// MARK: - CustomLayoutLayer + +/// A `CALayer` that sets a custom `bounds` and `anchorPoint` relative to its superlayer +protocol CustomLayoutLayer: CALayer { + func layout(superlayerBounds: CGRect) +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..627d7429367aac0c43bfde0816c00193b793d05d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift @@ -0,0 +1,29 @@ +// Created by Cal Stephens on 1/11/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - KeyframeGroup + exactlyOneKeyframe + +extension KeyframeGroup { + /// Retrieves the first `Keyframe` from this group, + /// and asserts that there are not any extra keyframes that would be ignored + /// - This should only be used in cases where it's fundamentally not possible to + /// support animating a given property (e.g. if Core Animation itself doesn't + /// support the property). + func exactlyOneKeyframe( + context: CompatibilityTrackerProviding, + description: String, + fileID _: StaticString = #fileID, + line _: UInt = #line) + throws + -> T + { + try context.compatibilityAssert( + keyframes.count == 1, + """ + The Core Animation rendering engine does not support animating multiple keyframes + for \(description) values, due to limitations of Core Animation. + """) + + return keyframes[0].value + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/Keyframes+combined.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/Keyframes+combined.swift new file mode 100644 index 0000000000000000000000000000000000000000..5b70c512a716413be3bfe01ad67340e939d57059 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/Keyframes+combined.swift @@ -0,0 +1,328 @@ +// Created by Cal Stephens on 1/28/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +// MARK: - Keyframes + +enum Keyframes { + + // MARK: Internal + + /// Combines the given keyframe groups of `Keyframe`s into a single keyframe group of of `Keyframe<[T]>`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + static func combined( + _ allGroups: [KeyframeGroup], + requiresManualInterpolation: Bool = false) + -> KeyframeGroup<[T]> + where T: AnyInterpolatable + { + Keyframes.combined( + allGroups, + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { untypedValues in + untypedValues.compactMap { $0 as? T } + }) + } + + /// Combines the given keyframe groups of `Keyframe`s into a single keyframe group of of `Keyframe<[T]>`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + static func combined( + _ k1: KeyframeGroup, + _ k2: KeyframeGroup, + requiresManualInterpolation: Bool = false, + makeCombinedResult: (T1, T2) throws -> CombinedResult) + rethrows + -> KeyframeGroup + where T1: AnyInterpolatable, T2: AnyInterpolatable + { + try Keyframes.combined( + [k1, k2], + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { untypedValues in + guard + let t1 = untypedValues[0] as? T1, + let t2 = untypedValues[1] as? T2 + else { return nil } + + return try makeCombinedResult(t1, t2) + }) + } + + /// Combines the given keyframe groups of `Keyframe`s into a single keyframe group of of `Keyframe<[T]>`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + static func combined( + _ k1: KeyframeGroup, + _ k2: KeyframeGroup, + _ k3: KeyframeGroup, + requiresManualInterpolation: Bool = false, + makeCombinedResult: (T1, T2, T3) -> CombinedResult) + -> KeyframeGroup + where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable + { + Keyframes.combined( + [k1, k2, k3], + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { untypedValues in + guard + let t1 = untypedValues[0] as? T1, + let t2 = untypedValues[1] as? T2, + let t3 = untypedValues[2] as? T3 + else { return nil } + + return makeCombinedResult(t1, t2, t3) + }) + } + + /// Combines the given keyframe groups of `Keyframe`s into a single keyframe group of of `Keyframe<[T]>`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + static func combined( + _ k1: KeyframeGroup, + _ k2: KeyframeGroup, + _ k3: KeyframeGroup, + _ k4: KeyframeGroup, + _ k5: KeyframeGroup, + _ k6: KeyframeGroup, + _ k7: KeyframeGroup, + requiresManualInterpolation: Bool = false, + makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7) -> CombinedResult) + -> KeyframeGroup + where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable, + T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable + { + Keyframes.combined( + [k1, k2, k3, k4, k5, k6, k7], + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { untypedValues in + guard + let t1 = untypedValues[0] as? T1, + let t2 = untypedValues[1] as? T2, + let t3 = untypedValues[2] as? T3, + let t4 = untypedValues[3] as? T4, + let t5 = untypedValues[4] as? T5, + let t6 = untypedValues[5] as? T6, + let t7 = untypedValues[6] as? T7 + else { return nil } + + return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7) + }) + } + + /// Combines the given keyframe groups of `Keyframe`s into a single keyframe group of of `Keyframe<[T]>`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + static func combined( + _ k1: KeyframeGroup, + _ k2: KeyframeGroup, + _ k3: KeyframeGroup, + _ k4: KeyframeGroup, + _ k5: KeyframeGroup, + _ k6: KeyframeGroup, + _ k7: KeyframeGroup, + _ k8: KeyframeGroup, + requiresManualInterpolation: Bool = false, + makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7, T8) -> CombinedResult) + -> KeyframeGroup + where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable, + T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable, T8: AnyInterpolatable + { + Keyframes.combined( + [k1, k2, k3, k4, k5, k6, k7, k8], + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { untypedValues in + guard + let t1 = untypedValues[0] as? T1, + let t2 = untypedValues[1] as? T2, + let t3 = untypedValues[2] as? T3, + let t4 = untypedValues[3] as? T4, + let t5 = untypedValues[4] as? T5, + let t6 = untypedValues[5] as? T6, + let t7 = untypedValues[6] as? T7, + let t8 = untypedValues[7] as? T8 + else { return nil } + + return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7, t8) + }) + } + + /// Combines the given keyframe groups of `Keyframe`s into a single keyframe group of of `Keyframe<[T]>`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + static func combined( + _ k1: KeyframeGroup, + _ k2: KeyframeGroup, + _ k3: KeyframeGroup, + _ k4: KeyframeGroup, + _ k5: KeyframeGroup, + _ k6: KeyframeGroup, + _ k7: KeyframeGroup, + _ k8: KeyframeGroup, + _ k9: KeyframeGroup, + _ k10: KeyframeGroup, + requiresManualInterpolation: Bool = false, + makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> CombinedResult) + -> KeyframeGroup + where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable, + T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable, T8: AnyInterpolatable, + T9: AnyInterpolatable, T10: AnyInterpolatable + { + Keyframes.combined( + [k1, k2, k3, k4, k5, k6, k7, k8, k9, k10], + requiresManualInterpolation: requiresManualInterpolation, + makeCombinedResult: { untypedValues in + guard + let t1 = untypedValues[0] as? T1, + let t2 = untypedValues[1] as? T2, + let t3 = untypedValues[2] as? T3, + let t4 = untypedValues[3] as? T4, + let t5 = untypedValues[4] as? T5, + let t6 = untypedValues[5] as? T6, + let t7 = untypedValues[6] as? T7, + let t8 = untypedValues[7] as? T8, + let t9 = untypedValues[8] as? T9, + let t10 = untypedValues[9] as? T10 + else { return nil } + + return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) + }) + } + + // MARK: Private + + /// Combines the given `[KeyframeGroup]` of `Keyframe`s into a single `KeyframeGroup` of `Keyframe`s + /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged + /// - Otherwise, the keyframes are manually interpolated at each frame in the animation + /// + /// `makeCombinedResult` is a closure that takes an array of keyframe values (with the exact same length as `AnyKeyframeGroup`), + /// casts them to the expected type, and combined them into the final resulting keyframe. + /// + /// `requiresManualInterpolation` determines whether the keyframes must be computed using `Keyframes.manuallyInterpolated`, + /// which interpolates the value at each frame, or if the keyframes can simply be combined. + private static func combined( + _ allGroups: [AnyKeyframeGroup], + requiresManualInterpolation: Bool, + makeCombinedResult: ([Any]) throws -> CombinedResult?) + rethrows + -> KeyframeGroup + { + let untypedGroups = allGroups.map { $0.untyped } + + // Animations with no timing information (e.g. with just a single keyframe) + // can be trivially combined with any other set of keyframes, so we don't need + // to check those. + let animatingKeyframes = untypedGroups.filter { $0.keyframes.count > 1 } + + guard + !requiresManualInterpolation, + !allGroups.isEmpty, + animatingKeyframes.allSatisfy({ $0.hasSameTimingParameters(as: animatingKeyframes[0]) }) + else { + // If the keyframes don't all share the same timing information, + // we have to interpolate the value at each individual frame + return try Keyframes.manuallyInterpolated(allGroups, makeCombinedResult: makeCombinedResult) + } + + var combinedKeyframes = ContiguousArray>() + let baseKeyframes = (animatingKeyframes.first ?? untypedGroups[0]).keyframes + + for index in baseKeyframes.indices { + let baseKeyframe = baseKeyframes[index] + let untypedValues = untypedGroups.map { $0.valueForCombinedKeyframes(at: index) } + + if let combinedValue = try makeCombinedResult(untypedValues) { + combinedKeyframes.append(baseKeyframe.withValue(combinedValue)) + } else { + LottieLogger.shared.assertionFailure(""" + Failed to cast untyped keyframe values to expected type. This is an internal error. + """) + } + } + + return KeyframeGroup(keyframes: combinedKeyframes) + } + + private static func manuallyInterpolated( + _ allGroups: [AnyKeyframeGroup], + makeCombinedResult: ([Any]) throws -> CombinedResult?) + rethrows + -> KeyframeGroup + { + let untypedGroups = allGroups.map { $0.untyped } + let untypedInterpolators = allGroups.map { $0.interpolator } + + let times = untypedGroups.flatMap { $0.keyframes.map { $0.time } } + + let minimumTime = times.min() ?? 0 + let maximumTime = times.max() ?? 0 + + // We disable Core Animation interpolation when using manually interpolated keyframes, + // so we don't animate between these values. To prevent the animation from being choppy + // even at low playback speed, we have to interpolate at a very high fidelity. + let animationLocalTimeRange = stride(from: minimumTime, to: maximumTime, by: 0.1) + + let interpolatedKeyframes = try animationLocalTimeRange.compactMap { localTime -> Keyframe? in + let interpolatedValues = untypedInterpolators.map { interpolator in + interpolator.value(frame: AnimationFrameTime(localTime)) + } + + guard let combinedResult = try makeCombinedResult(interpolatedValues) else { + LottieLogger.shared.assertionFailure(""" + Failed to cast untyped keyframe values to expected type. This is an internal error. + """) + return nil + } + + return Keyframe( + value: combinedResult, + time: AnimationFrameTime(localTime), + // Since we already manually interpolated the keyframes, have Core Animation display + // each value as a static keyframe rather than trying to interpolate between them. + isHold: true) + } + + return KeyframeGroup(keyframes: ContiguousArray(interpolatedKeyframes)) + } + +} + +extension KeyframeGroup { + /// Whether or not all of the keyframes in this `KeyframeGroup` have the same + /// timing parameters as the corresponding keyframe in the other given `KeyframeGroup` + func hasSameTimingParameters(as other: KeyframeGroup) -> Bool { + guard keyframes.count == other.keyframes.count else { + return false + } + + return zip(keyframes, other.keyframes).allSatisfy { + $0.hasSameTimingParameters(as: $1) + } + } +} + +extension Keyframe { + /// Whether or not this keyframe has the same timing parameters as the given keyframe, + /// excluding `spatialInTangent` and `spatialOutTangent`. + fileprivate func hasSameTimingParameters(as other: Keyframe) -> Bool { + time == other.time + && isHold == other.isHold + && inTangent == other.inTangent + && outTangent == other.outTangent + // We intentionally don't compare spatial in/out tangents, + // since those values are only used in very specific cases + // (animating the x/y position of a layer), which aren't ever + // combined in this way. + } +} + +extension KeyframeGroup { + /// The value to use for a combined set of keyframes, for the given index + fileprivate func valueForCombinedKeyframes(at index: Int) -> T { + if keyframes.count == 1 { + keyframes[0].value + } else { + keyframes[index].value + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/Keyframes+timeRemapping.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/Keyframes+timeRemapping.swift new file mode 100644 index 0000000000000000000000000000000000000000..217e38ddae20b698f5cac997b75b0ac611518666 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Extensions/Keyframes+timeRemapping.swift @@ -0,0 +1,46 @@ +// Created by Cal Stephens on 1/8/24. +// Copyright © 2024 Airbnb Inc. All rights reserved. + +extension Keyframes { + /// Manually interpolates the given keyframes, and applies `context.complexTimeRemapping`. + /// - Since `complexTimeRemapping` is a mapping from "global time" to "local time", + /// we have to manually interpolate the keyframes at every frame in the animation. + static func manuallyInterpolatedWithTimeRemapping( + _ keyframes: KeyframeGroup, + context: LayerAnimationContext) + -> KeyframeGroup + { + let minimumTime = context.animation.startFrame + let maximumTime = context.animation.endFrame + let animationLocalTimeRange = stride(from: minimumTime, to: maximumTime, by: 1.0) + + let interpolator = keyframes.interpolator + + // Since potentially many global times can refer to the same local time, + // we can cache and reused these local-time values. + var localTimeCache = [AnimationFrameTime: T]() + + let interpolatedRemappedKeyframes = animationLocalTimeRange.compactMap { globalTime -> Keyframe? in + let remappedLocalTime = context.complexTimeRemapping(globalTime) + + let valueAtRemappedTime: T + if let cachedValue = localTimeCache[remappedLocalTime] { + valueAtRemappedTime = cachedValue + } else if let interpolatedValue = interpolator.value(frame: remappedLocalTime) as? T { + valueAtRemappedTime = interpolatedValue + localTimeCache[remappedLocalTime] = interpolatedValue + } else { + LottieLogger.shared.assertionFailure(""" + Failed to cast untyped keyframe values to expected type. This is an internal error. + """) + return nil + } + + return Keyframe( + value: valueAtRemappedTime, + time: AnimationFrameTime(globalTime)) + } + + return KeyframeGroup(keyframes: ContiguousArray(interpolatedRemappedKeyframes)) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/AnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/AnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..dfd10b58af4bd530e433fe02efb6c68b132e43d0 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/AnimationLayer.swift @@ -0,0 +1,169 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - AnimationLayer + +/// A type of `CALayer` that can be used in a Lottie animation +/// - Layers backed by a `LayerModel` subclass should subclass `BaseCompositionLayer` +protocol AnimationLayer: CALayer { + /// Instructs this layer to setup its `CAAnimation`s + /// using the given `LayerAnimationContext` + func setupAnimations(context: LayerAnimationContext) throws +} + +// MARK: - LayerAnimationContext + +/// Context describing the timing parameters of the current animation +struct LayerAnimationContext { + /// The animation being played + let animation: LottieAnimation + + /// The timing configuration that should be applied to `CAAnimation`s + let timingConfiguration: CoreAnimationLayer.CAMediaTimingConfiguration + + /// The absolute frame number that this animation begins at + let startFrame: AnimationFrameTime + + /// The absolute frame number that this animation ends at + let endFrame: AnimationFrameTime + + /// The set of custom Value Providers applied to this animation + let valueProviderStore: ValueProviderStore + + /// Information about whether or not an animation is compatible with the Core Animation engine + let compatibilityTracker: CompatibilityTracker + + /// The logger that should be used for assertions and warnings + let logger: LottieLogger + + /// Mutable state related to log events, stored on the `CoreAnimationLayer`. + let loggingState: LoggingState + + /// The AnimationKeypath represented by the current layer + var currentKeypath: AnimationKeypath + + /// The `AnimationKeypathTextProvider` + var textProvider: AnimationKeypathTextProvider + + /// Records the given animation keypath so it can be logged or collected into a list + /// - Used for `CoreAnimationLayer.logHierarchyKeypaths()` and `allHierarchyKeypaths()` + var recordHierarchyKeypath: ((String) -> Void)? + + /// A closure that remaps the given frame in the child layer's local time to a frame + /// in the animation's overall global time. + /// - This time remapping is simple and only used `preCompLayer.timeStretch` and `preCompLayer.startTime`, + /// so is a trivial function and is invertible. This allows us to invert the time remapping from + /// "global time to local time" to instead be "local time to global time". + private(set) var simpleTimeRemapping: ((_ localTime: AnimationFrameTime) -> AnimationFrameTime) = { $0 } + + /// A complex time remapping closure that remaps the given frame in the animation's overall global time + /// into the child layer's local time. + /// - This time remapping is arbitrarily complex because it includes the full `preCompLayer.timeRemapping`. + /// - Since it isn't possible to invert the time remapping function, this can only be applied by converting + /// from global time to local time. This requires using `Keyframes.manuallyInterpolatedWithTimeRemapping`. + private(set) var complexTimeRemapping: ((_ globalTime: AnimationFrameTime) -> AnimationFrameTime) = { $0 } + + /// Whether or not this layer is required to use the `complexTimeRemapping` via + /// the more expensive `Keyframes.manuallyInterpolatedWithTimeRemapping` codepath. + var mustUseComplexTimeRemapping = false + + /// The duration of the animation + var animationDuration: AnimationFrameTime { + // Normal animation playback (like when looping) skips the last frame. + // However when the animation is paused, we need to be able to render the final frame. + // To allow this we have to extend the length of the animation by one frame. + let animationEndFrame: AnimationFrameTime = + if timingConfiguration.speed == 0 { + animation.endFrame + 1 + } else { + animation.endFrame + } + + return Double(animationEndFrame - animation.startFrame) / animation.framerate + } + + /// Adds the given component string to the `AnimationKeypath` stored + /// that describes the current path being configured by this context value + func addingKeypathComponent(_ component: String) -> LayerAnimationContext { + var context = self + context.currentKeypath.keys.append(component) + return context + } + + /// The `AnimationProgressTime` for the given `AnimationFrameTime` within this layer, + /// accounting for the `simpleTimeRemapping` applied to this layer. + func progressTime(for frame: AnimationFrameTime) throws -> AnimationProgressTime { + try compatibilityAssert( + !mustUseComplexTimeRemapping, + "LayerAnimationContext.time(forFrame:) does not support complex time remapping") + + let animationFrameCount = animationDuration * animation.framerate + return (simpleTimeRemapping(frame) - animation.startFrame) / animationFrameCount + } + + /// The real-time `TimeInterval` for the given `AnimationFrameTime` within this layer, + /// accounting for the `simpleTimeRemapping` applied to this layer. + func time(forFrame frame: AnimationFrameTime) throws -> TimeInterval { + try compatibilityAssert( + !mustUseComplexTimeRemapping, + "LayerAnimationContext.time(forFrame:) does not support complex time remapping") + + return animation.time(forFrame: simpleTimeRemapping(frame)) + } + + /// Chains an additional time remapping closure onto the `simpleTimeRemapping` closure + func withSimpleTimeRemapping( + _ additionalSimpleTimeRemapping: @escaping (_ localTime: AnimationFrameTime) -> AnimationFrameTime) + -> LayerAnimationContext + { + var copy = self + copy.simpleTimeRemapping = { [existingSimpleTimeRemapping = simpleTimeRemapping] time in + existingSimpleTimeRemapping(additionalSimpleTimeRemapping(time)) + } + return copy + } + + /// Chains an additional time remapping closure onto the `complexTimeRemapping` closure. + /// - If `required` is `true`, all subsequent child layers will be required to use the expensive + /// `complexTimeRemapping` / `Keyframes.manuallyInterpolatedWithTimeRemapping` codepath. + /// - `required: true` is necessary when this time remapping is not available via `simpleTimeRemapping`. + func withComplexTimeRemapping( + required: Bool, + _ additionalComplexTimeRemapping: @escaping (_ globalTime: AnimationFrameTime) -> AnimationFrameTime) + -> LayerAnimationContext + { + var copy = self + copy.mustUseComplexTimeRemapping = copy.mustUseComplexTimeRemapping || required + copy.complexTimeRemapping = { [existingComplexTimeRemapping = complexTimeRemapping] time in + additionalComplexTimeRemapping(existingComplexTimeRemapping(time)) + } + return copy + } + + /// Returns a copy of this context with time remapping removed + func withoutTimeRemapping() -> LayerAnimationContext { + var copy = self + copy.simpleTimeRemapping = { $0 } + copy.complexTimeRemapping = { $0 } + copy.mustUseComplexTimeRemapping = false + return copy + } +} + +// MARK: - LoggingState + +/// Mutable state related to log events, stored on the `CoreAnimationLayer`. +final class LoggingState { + + // MARK: Lifecycle + + init() { } + + // MARK: Internal + + /// Whether or not the warning about unsupported After Effects expressions + /// has been logged yet for this layer. + var hasLoggedAfterEffectsExpressionsWarning = false +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/BaseAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/BaseAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..06248b20f87dd81d04a733e0773c6f166f847322 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/BaseAnimationLayer.swift @@ -0,0 +1,33 @@ +// Created by Cal Stephens on 1/27/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +/// A base `CALayer` that manages the frame and animations +/// of its `sublayers` and `mask` +class BaseAnimationLayer: CALayer, AnimationLayer { + + // MARK: Internal + + override func layoutSublayers() { + super.layoutSublayers() + + for sublayer in managedSublayers { + sublayer.fillBoundsOfSuperlayer() + } + } + + func setupAnimations(context: LayerAnimationContext) throws { + for childAnimationLayer in managedSublayers { + try (childAnimationLayer as? AnimationLayer)?.setupAnimations(context: context) + } + } + + // MARK: Private + + /// All of the sublayers managed by this container + private var managedSublayers: [CALayer] { + (sublayers ?? []) + [mask].compactMap { $0 } + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/BaseCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/BaseCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..976580338e0eee51eb02ee3b274a318dbb2ca3a3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/BaseCompositionLayer.swift @@ -0,0 +1,110 @@ +// Created by Cal Stephens on 12/20/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - BaseCompositionLayer + +/// The base type of `AnimationLayer` that can contain other `AnimationLayer`s +class BaseCompositionLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + init(layerModel: LayerModel) { + baseLayerModel = layerModel + super.init() + + setupSublayers() + compositingFilter = layerModel.blendMode.filterName + name = layerModel.name + contentsLayer.name = "\(layerModel.name) (Content)" + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + baseLayerModel = typedLayer.baseLayerModel + super.init(layer: typedLayer) + } + + // MARK: Internal + + /// The layer that content / sublayers should be rendered in. + /// This is the layer that transform animations are applied to. + let contentsLayer = BaseAnimationLayer() + + /// Whether or not this layer render should render any visible content + var renderLayerContents: Bool { true } + + /// Sets up the base `LayerModel` animations for this layer, + /// and all child `AnimationLayer`s. + /// - Can be overridden by subclasses, which much call `super`. + override func setupAnimations(context: LayerAnimationContext) throws { + let layerContext = context.addingKeypathComponent(baseLayerModel.name) + let childContext = renderLayerContents ? layerContext : context + + try setupLayerAnimations(context: layerContext) + try setupChildAnimations(context: childContext) + } + + func setupLayerAnimations(context: LayerAnimationContext) throws { + let transformContext = context.addingKeypathComponent("Transform") + + try contentsLayer.addTransformAnimations(for: baseLayerModel.transform, context: transformContext) + + if renderLayerContents { + try contentsLayer.addOpacityAnimation(for: baseLayerModel.transform, context: transformContext) + + try contentsLayer.addVisibilityAnimation( + inFrame: CGFloat(baseLayerModel.inFrame), + outFrame: CGFloat(baseLayerModel.outFrame), + context: context) + + // There are two different drop shadow schemas, either using `DropShadowEffect` or `DropShadowStyle`. + // If both happen to be present, prefer the `DropShadowEffect` (which is the drop shadow schema + // supported on other platforms). + let dropShadowEffect = baseLayerModel.effects.first(where: { $0 is DropShadowEffect }) as? DropShadowModel + let dropShadowStyle = baseLayerModel.styles.first(where: { $0 is DropShadowStyle }) as? DropShadowModel + if let dropShadowModel = dropShadowEffect ?? dropShadowStyle { + try contentsLayer.addDropShadowAnimations(for: dropShadowModel, context: context) + } + } + } + + func setupChildAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + } + + override func addSublayer(_ layer: CALayer) { + if layer === contentsLayer { + super.addSublayer(contentsLayer) + } else { + contentsLayer.addSublayer(layer) + } + } + + // MARK: Private + + private let baseLayerModel: LayerModel + + private func setupSublayers() { + addSublayer(contentsLayer) + + if + renderLayerContents, + let masks = baseLayerModel.masks?.filter({ $0.mode != .none }), + !masks.isEmpty + { + contentsLayer.mask = MaskCompositionLayer(masks: masks) + } + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift new file mode 100644 index 0000000000000000000000000000000000000000..dfbf28cff55fb08537e7325dcdac92b363d49e68 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift @@ -0,0 +1,169 @@ +// Created by Cal Stephens on 1/11/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CALayer { + + // MARK: Internal + + /// Sets up an `AnimationLayer` / `CALayer` hierarchy in this layer, + /// using the given list of layers. + @nonobjc + func setupLayerHierarchy( + for layers: [LayerModel], + context: LayerContext) + throws + { + // A `LottieAnimation`'s `LayerModel`s are listed from front to back, + // but `CALayer.sublayers` are listed from back to front. + // We reverse the layer ordering to match what Core Animation expects. + // The final view hierarchy must display the layers in this exact order. + let layersInZAxisOrder = layers.reversed() + + let layersByIndex = Dictionary(grouping: layersInZAxisOrder, by: \.index) + .compactMapValues(\.first) + + /// Layers specify a `parent` layer. Child layers inherit the `transform` of their parent. + /// - We can't add the child as a sublayer of the parent `CALayer`, since that would + /// break the ordering specified in `layersInZAxisOrder`. + /// - Instead, we create an invisible `TransformLayer` to handle the parent + /// transform animations, and add the child layer to that `TransformLayer`. + func makeParentTransformLayer( + childLayerModel: LayerModel, + childLayer: CALayer, + name: (LayerModel) -> String) + -> CALayer + { + guard + let parentIndex = childLayerModel.parent, + let parentLayerModel = layersByIndex[parentIndex] + else { return childLayer } + + let parentLayer = TransformLayer(layerModel: parentLayerModel) + parentLayer.name = name(parentLayerModel) + parentLayer.addSublayer(childLayer) + + return makeParentTransformLayer( + childLayerModel: parentLayerModel, + childLayer: parentLayer, + name: name) + } + + // Create an `AnimationLayer` for each `LayerModel` + for (layerModel, mask) in try layersInZAxisOrder.pairedLayersAndMasks() { + guard let layer = try layerModel.makeAnimationLayer(context: context) else { + continue + } + + // If this layer has a `parent`, we create an invisible `TransformLayer` + // to handle displaying / animating the parent transform. + let parentTransformLayer = makeParentTransformLayer( + childLayerModel: layerModel, + childLayer: layer, + name: { parentLayerModel in + "\(layerModel.name) (parent, \(parentLayerModel.name))" + }) + + // Create the `mask` layer for this layer, if it has a `MatteType` + if + let mask, + let maskLayer = try maskLayer(for: mask.model, type: mask.matteType, context: context) + { + let maskParentTransformLayer = makeParentTransformLayer( + childLayerModel: mask.model, + childLayer: maskLayer, + name: { parentLayerModel in + "\(mask.model.name) (mask of \(layerModel.name)) (parent, \(parentLayerModel.name))" + }) + + // Set up a parent container to host both the layer + // and its mask in the same coordinate space + let maskContainer = BaseAnimationLayer() + maskContainer.name = "\(layerModel.name) (parent, masked)" + maskContainer.addSublayer(parentTransformLayer) + + // Core Animation will silently fail to apply a mask if a `mask` layer + // itself _also_ has a `mask`. As a workaround, we can wrap this layer's + // mask in an additional container layer which never has its own `mask`. + let additionalMaskParent = BaseAnimationLayer() + additionalMaskParent.addSublayer(maskParentTransformLayer) + maskContainer.mask = additionalMaskParent + + addSublayer(maskContainer) + } + + else { + addSublayer(parentTransformLayer) + } + } + } + + // MARK: Fileprivate + + /// Creates a mask `CALayer` from the given matte layer model, using the `MatteType` + /// from the layer that is being masked. + fileprivate func maskLayer( + for matteLayerModel: LayerModel, + type: MatteType, + context: LayerContext) + throws -> CALayer? + { + switch type { + case .add: + return try matteLayerModel.makeAnimationLayer(context: context) + + case .invert: + guard let maskLayer = try matteLayerModel.makeAnimationLayer(context: context) else { + return nil + } + + // We can invert the mask layer by having a large solid black layer with the + // given mask layer subtracted out using the `xor` blend mode. When applied to the + // layer being masked, this creates an inverted mask where only areas _outside_ + // of the mask layer are visible. + // https://developer.apple.com/documentation/coregraphics/cgblendmode/xor + // - The inverted mask is supposed to expand infinitely around the shape, + // so we use `InfiniteOpaqueAnimationLayer` + let base = InfiniteOpaqueAnimationLayer() + base.backgroundColor = .rgb(0, 0, 0) + base.addSublayer(maskLayer) + maskLayer.compositingFilter = "xor" + return base + + case .none, .unknown: + return nil + } + } + +} + +extension Collection { + /// Pairs each `LayerModel` within this array with + /// a `LayerModel` to use as its mask, if applicable + /// based on the layer's `MatteType` configuration. + /// - Assumes the layers are sorted in z-axis order. + fileprivate func pairedLayersAndMasks() throws + -> [(layer: LayerModel, mask: (model: LayerModel, matteType: MatteType)?)] + { + var layersAndMasks = [(layer: LayerModel, mask: (model: LayerModel, matteType: MatteType)?)]() + var unprocessedLayers = reversed() + + while let layer = unprocessedLayers.popLast() { + /// If a layer has a `MatteType`, then the next layer will be used as its `mask` + if + let matteType = layer.matte, + matteType != .none, + let maskLayer = unprocessedLayers.popLast() + { + layersAndMasks.append((layer: layer, mask: (model: maskLayer, matteType: matteType))) + } + + else { + layersAndMasks.append((layer: layer, mask: nil)) + } + } + + return layersAndMasks + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/GradientRenderLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/GradientRenderLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..cb2fe4888e7a0891a26e306e71db6cdb02870d5c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/GradientRenderLayer.swift @@ -0,0 +1,97 @@ +// Created by Cal Stephens on 1/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - GradientRenderLayer + +/// A `CAGradientLayer` subclass used to render a gradient _outside_ the normal layer bounds +/// +/// - `GradientFill.startPoint` and `GradientFill.endPoint` are expressed +/// with respect to the `bounds` of the `ShapeItemLayer`. +/// +/// - The gradient itself is supposed to be rendered infinitely in all directions +/// (e.g. including outside of `bounds`). This is because `ShapeItemLayer` paths +/// don't necessarily sit within the layer's `bounds`. +/// +/// - To support this, `GradientRenderLayer` tracks a `gradientReferenceBounds` +/// that `startPoint` / `endPoint` are calculated relative to. +/// The _actual_ `bounds` of this layer is padded by a large amount so that +/// the gradient can be drawn outside of the `gradientReferenceBounds`. +/// +final class GradientRenderLayer: CAGradientLayer { + + // MARK: Internal + + /// The reference bounds within this layer that the gradient's + /// `startPoint` and `endPoint` should be calculated relative to + var gradientReferenceBounds: CGRect = .zero { + didSet { + if oldValue != gradientReferenceBounds { + updateLayout() + } + } + } + + /// Converts the given `CGPoint` within `gradientReferenceBounds` + /// to a percentage value relative to the full `bounds` of this layer + /// - This converts absolute `startPoint` and `endPoint` values into + /// the percent-based values expected by Core Animation, + /// with respect to the custom bounds geometry used by this layer type. + func percentBasedPointInBounds(from referencePoint: CGPoint) -> CGPoint { + guard bounds.width > 0, bounds.height > 0 else { + LottieLogger.shared.assertionFailure("Size must be non-zero before an animation can be played") + return .zero + } + + let pointInBounds = CGPoint( + x: referencePoint.x + CALayer.veryLargeLayerPadding, + y: referencePoint.y + CALayer.veryLargeLayerPadding) + + return CGPoint( + x: CGFloat(pointInBounds.x) / bounds.width, + y: CGFloat(pointInBounds.y) / bounds.height) + } + + // MARK: Private + + private func updateLayout() { + anchorPoint = .zero + + bounds = CGRect( + x: gradientReferenceBounds.origin.x, + y: gradientReferenceBounds.origin.y, + width: CALayer.veryLargeLayerPadding + gradientReferenceBounds.width + CALayer.veryLargeLayerPadding, + height: CALayer.veryLargeLayerPadding + gradientReferenceBounds.height + CALayer.veryLargeLayerPadding) + + // Align the center of this layer to be at the center point of its parent layer + let superlayerSize = superlayer?.frame.size ?? gradientReferenceBounds.size + + transform = CATransform3DMakeTranslation( + (superlayerSize.width - bounds.width) / 2, + (superlayerSize.height - bounds.height) / 2, + 0) + } + +} + +// MARK: CustomLayoutLayer + +extension GradientRenderLayer: CustomLayoutLayer { + func layout(superlayerBounds: CGRect) { + gradientReferenceBounds = superlayerBounds + + if let gradientMask = mask as? GradientRenderLayer { + gradientMask.layout(superlayerBounds: superlayerBounds) + } + } +} + +extension CALayer { + /// Extra padding to add around layers that should be very large or "infinite" in size. + /// Examples include `GradientRenderLayer` and `InfiniteOpaqueAnimationLayer`. + /// - This specific value is arbitrary and can be increased if necessary. + /// - Theoretically this should be "infinite", to match the behavior of + /// `CGContext.drawLinearGradient` with `[.drawsAfterEndLocation, .drawsBeforeStartLocation]` etc. + static let veryLargeLayerPadding: CGFloat = 10_000 +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ImageLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ImageLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..433c06f3b75b44c6ccf96136cf124c669333cf6f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ImageLayer.swift @@ -0,0 +1,80 @@ +// Created by Cal Stephens on 1/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ImageLayer + +/// The `CALayer` type responsible for rendering `ImageLayerModel`s +final class ImageLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init( + imageLayer: ImageLayerModel, + context: LayerContext) + { + self.imageLayer = imageLayer + super.init(layerModel: imageLayer) + setupImage(context: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + imageLayer = typedLayer.imageLayer + super.init(layer: typedLayer) + } + + // MARK: Internal + + func setupImage(context: LayerContext) { + guard + let imageAsset = context.animation.assetLibrary?.imageAssets[imageLayer.referenceID], + let image = context.imageProvider.imageForAsset(asset: imageAsset) + else { + self.imageAsset = nil + contentsLayer.contents = nil + return + } + + self.imageAsset = imageAsset + contentsLayer.contents = image + contentsLayer.contentsGravity = context.imageProvider.contentsGravity(for: imageAsset) + setNeedsLayout() + } + + // MARK: Private + + private let imageLayer: ImageLayerModel + private var imageAsset: ImageAsset? + +} + +// MARK: CustomLayoutLayer + +extension ImageLayer: CustomLayoutLayer { + func layout(superlayerBounds: CGRect) { + anchorPoint = .zero + + guard let imageAsset else { + bounds = superlayerBounds + return + } + + // Image layers specifically need to use the size of the image itself + bounds = CGRect( + x: superlayerBounds.origin.x, + y: superlayerBounds.origin.y, + width: CGFloat(imageAsset.width), + height: CGFloat(imageAsset.height)) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/InfiniteOpaqueAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/InfiniteOpaqueAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..4549fea691b0a57971498713631ceba7441dee39 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/InfiniteOpaqueAnimationLayer.swift @@ -0,0 +1,56 @@ +// Created by Cal Stephens on 10/10/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ExpandedAnimationLayer + +/// A `BaseAnimationLayer` subclass that renders its background color +/// as if the layer is infinitely large, without affecting its bounds +/// or the bounds of its sublayers +final class InfiniteOpaqueAnimationLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + override init() { + super.init() + addSublayer(additionalPaddingLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + super.init(layer: layer) + } + + // MARK: Internal + + override func layoutSublayers() { + super.layoutSublayers() + + masksToBounds = false + additionalPaddingLayer.backgroundColor = backgroundColor + + // Scale `additionalPaddingLayer` to be larger than this layer + // by `additionalPadding` at each size, and centered at the center + // of this layer. Since `additionalPadding` is very large, this has + // the affect of making `additionalPaddingLayer` appear infinite. + let scaleRatioX = (bounds.width + (CALayer.veryLargeLayerPadding * 2)) / bounds.width + let scaleRatioY = (bounds.height + (CALayer.veryLargeLayerPadding * 2)) / bounds.height + + additionalPaddingLayer.transform = CATransform3DScale( + CATransform3DMakeTranslation(-CALayer.veryLargeLayerPadding, -CALayer.veryLargeLayerPadding, 0), + scaleRatioX, + scaleRatioY, + 1) + } + + // MARK: Private + + private let additionalPaddingLayer = CALayer() + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..0ca9339049b61975d17449fad34b09f6fa12cb49 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift @@ -0,0 +1,59 @@ +// Created by Cal Stephens on 12/20/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +// MARK: - LayerContext + +/// Context available when constructing an `AnimationLayer` +struct LayerContext { + let animation: LottieAnimation + let imageProvider: AnimationImageProvider + let textProvider: AnimationKeypathTextProvider + let fontProvider: AnimationFontProvider + let compatibilityTracker: CompatibilityTracker + var layerName: String + + func forLayer(_ layer: LayerModel) -> LayerContext { + var context = self + context.layerName = layer.name + return context + } +} + +// MARK: - LayerModel + makeAnimationLayer + +extension LayerModel { + /// Constructs an `AnimationLayer` / `CALayer` that represents this `LayerModel` + func makeAnimationLayer(context: LayerContext) throws -> BaseCompositionLayer? { + let context = context.forLayer(self) + + if hidden { + return TransformLayer(layerModel: self) + } + + switch (type, self) { + case (.precomp, let preCompLayerModel as PreCompLayerModel): + let preCompLayer = PreCompLayer(preCompLayer: preCompLayerModel) + try preCompLayer.setup(context: context) + return preCompLayer + + case (.solid, let solidLayerModel as SolidLayerModel): + return SolidLayer(solidLayerModel) + + case (.shape, let shapeLayerModel as ShapeLayerModel): + return try ShapeLayer(shapeLayer: shapeLayerModel, context: context) + + case (.image, let imageLayerModel as ImageLayerModel): + return ImageLayer(imageLayer: imageLayerModel, context: context) + + case (.text, let textLayerModel as TextLayerModel): + return try TextLayer(textLayerModel: textLayerModel, context: context) + + case (.null, _): + return TransformLayer(layerModel: self) + + case (.unknown, _), (.precomp, _), (.solid, _), (.image, _), (.shape, _), (.text, _): + return nil + } + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/MaskCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/MaskCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..d032ab5dabb60c1f1a0270ec617eb764d9f4fa68 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/MaskCompositionLayer.swift @@ -0,0 +1,138 @@ +// Created by Cal Stephens on 1/6/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - MaskCompositionLayer + +/// The CALayer type responsible for rendering the `Mask` of a `BaseCompositionLayer` +final class MaskCompositionLayer: CALayer { + + // MARK: Lifecycle + + init(masks: [Mask]) { + maskLayers = masks.map(MaskLayer.init(mask:)) + super.init() + + var containerLayer = BaseAnimationLayer() + var firstObject = true + for maskLayer in maskLayers { + if maskLayer.maskModel.mode.usableMode == .none { + continue + } else if maskLayer.maskModel.mode.usableMode == .add || firstObject { + firstObject = false + containerLayer.addSublayer(maskLayer) + } else { + containerLayer.mask = maskLayer + let newContainer = BaseAnimationLayer() + newContainer.addSublayer(containerLayer) + containerLayer = newContainer + } + } + + addSublayer(containerLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + maskLayers = typedLayer.maskLayers + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func layoutSublayers() { + super.layoutSublayers() + + for sublayer in sublayers ?? [] { + sublayer.fillBoundsOfSuperlayer() + } + } + + // MARK: Private + + private let maskLayers: [MaskLayer] + +} + +// MARK: AnimationLayer + +extension MaskCompositionLayer: AnimationLayer { + func setupAnimations(context: LayerAnimationContext) throws { + for maskLayer in maskLayers { + try maskLayer.setupAnimations(context: context) + } + } +} + +// MARK: MaskCompositionLayer.MaskLayer + +extension MaskCompositionLayer { + final class MaskLayer: CAShapeLayer { + + // MARK: Lifecycle + + init(mask: Mask) { + maskModel = mask + super.init() + + fillRule = .evenOdd + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + maskModel = typedLayer.maskModel + super.init(layer: typedLayer) + } + + // MARK: Internal + + let maskModel: Mask + + } +} + +// MARK: - MaskCompositionLayer.MaskLayer + AnimationLayer + +extension MaskCompositionLayer.MaskLayer: AnimationLayer { + func setupAnimations(context: LayerAnimationContext) throws { + let shouldInvertMask = (maskModel.mode.usableMode == .subtract && !maskModel.inverted) + || (maskModel.mode.usableMode == .add && maskModel.inverted) + + try addAnimations( + for: maskModel.shape, + context: context, + transformPath: { maskPath in + // If the mask is using `MaskMode.subtract` or has `inverted: true`, + // we have to invert the area filled by the path. We can do that by + // drawing a rectangle, and then adding a path (which is subtracted + // from the rectangle based on the .evenOdd fill mode). + if shouldInvertMask { + let path = CGMutablePath() + path.addRect(.veryLargeRect) + path.addPath(maskPath) + return path + } else { + return maskPath + } + }) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/PreCompLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/PreCompLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..700619273822cdabfaeacfd497818256e270d9e4 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/PreCompLayer.swift @@ -0,0 +1,105 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - PreCompLayer + +/// The `CALayer` type responsible for rendering `PreCompLayerModel`s +final class PreCompLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init(preCompLayer: PreCompLayerModel) { + self.preCompLayer = preCompLayer + super.init(layerModel: preCompLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + preCompLayer = typedLayer.preCompLayer + super.init(layer: typedLayer) + } + + // MARK: Internal + + let preCompLayer: PreCompLayerModel + + /// Post-init setup for `PreCompLayer`s. + /// Should always be called after `PreCompLayer.init(preCompLayer:)`. + /// + /// This is a workaround for a hard-to-reproduce crash that was + /// triggered when `PreCompLayer.init` was called reentantly. We didn't + /// have any consistent repro steps for this crash (it happened 100% of + /// the time for some testers, and 0% of the time for other testers), + /// but moving this code out of `PreCompLayer.init` does seem to fix it. + /// + /// The stack trace looked like: + /// - `_os_unfair_lock_recursive_abort` + /// - `-[CALayerAccessibility__UIKit__QuartzCore dealloc]` + /// - `PreCompLayer.__allocating_init(preCompLayer:context:)` <- reentrant init call + /// - ... + /// - `CALayer.setupLayerHierarchy(for:context:)` + /// - `PreCompLayer.init(preCompLayer:context:)` + /// + func setup(context: LayerContext) throws { + try setupLayerHierarchy( + for: context.animation.assetLibrary?.precompAssets[preCompLayer.referenceID]?.layers ?? [], + context: context) + } + + override func setupAnimations(context: LayerAnimationContext) throws { + var context = context + context = context.addingKeypathComponent(preCompLayer.name) + try setupLayerAnimations(context: context) + + let timeRemappingInterpolator = preCompLayer.timeRemapping.flatMap { KeyframeInterpolator(keyframes: $0.keyframes) } + + let contextForChildren = context + // `timeStretch` and `startTime` are a simple linear function so can be inverted from a + // "global time to local time" function into the simpler "local time to global time". + .withSimpleTimeRemapping { [preCompLayer] layerLocalFrame in + (layerLocalFrame * AnimationFrameTime(preCompLayer.timeStretch)) + AnimationFrameTime(preCompLayer.startTime) + } + // `timeRemappingInterpolator` is arbitrarily complex and cannot be inverted, + // so can only be applied via `complexTimeRemapping` from global time to local time. + .withComplexTimeRemapping(required: preCompLayer.timeRemapping != nil) { [preCompLayer] globalTime in + if let timeRemappingInterpolator { + let remappedLocalTime = timeRemappingInterpolator.value(frame: globalTime) as! LottieVector1D + return remappedLocalTime.cgFloatValue * context.animation.framerate + } else { + return (globalTime - preCompLayer.startTime) / preCompLayer.timeStretch + } + } + + try setupChildAnimations(context: contextForChildren) + } + +} + +// MARK: CustomLayoutLayer + +extension PreCompLayer: CustomLayoutLayer { + func layout(superlayerBounds: CGRect) { + anchorPoint = .zero + + // Pre-comp layers use a size specified in the layer model, + // and clip the composition to that bounds + bounds = CGRect( + x: superlayerBounds.origin.x, + y: superlayerBounds.origin.y, + width: CGFloat(preCompLayer.width), + height: CGFloat(preCompLayer.height)) + + contentsLayer.masksToBounds = true + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/RepeaterLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/RepeaterLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..e27e7e876b5d1b248329cf53cc37bd8fb2bfaa3a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/RepeaterLayer.swift @@ -0,0 +1,98 @@ +// Created by Cal Stephens on 8/1/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - RepeaterLayer + +/// A layer that renders a child layer at some offset using a `Repeater` +final class RepeaterLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + init(repeater: Repeater, childLayer: CALayer, index: Int) { + repeaterTransform = RepeaterTransform(repeater: repeater, index: index) + super.init() + addSublayer(childLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + repeaterTransform = typedLayer.repeaterTransform + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + try addTransformAnimations(for: repeaterTransform, context: context) + } + + // MARK: Private + + private let repeaterTransform: RepeaterTransform + +} + +// MARK: - RepeaterTransform + +/// A transform model created from a `Repeater` +private struct RepeaterTransform { + + // MARK: Lifecycle + + init(repeater: Repeater, index: Int) { + anchorPoint = repeater.anchorPoint + scale = repeater.scale + + rotationX = repeater.rotationX.map { rotation in + LottieVector1D(rotation.value * Double(index)) + } + + rotationY = repeater.rotationY.map { rotation in + LottieVector1D(rotation.value * Double(index)) + } + + rotationZ = repeater.rotationZ.map { rotation in + LottieVector1D(rotation.value * Double(index)) + } + + position = repeater.position.map { position in + LottieVector3D( + x: position.x * Double(index), + y: position.y * Double(index), + z: position.z * Double(index)) + } + } + + // MARK: Internal + + let anchorPoint: KeyframeGroup + let position: KeyframeGroup + let rotationX: KeyframeGroup + let rotationY: KeyframeGroup + let rotationZ: KeyframeGroup + + let scale: KeyframeGroup + +} + +// MARK: TransformModel + +extension RepeaterTransform: TransformModel { + var _position: KeyframeGroup? { position } + var _positionX: KeyframeGroup? { nil } + var _positionY: KeyframeGroup? { nil } + var _skew: KeyframeGroup? { nil } + var _skewAxis: KeyframeGroup? { nil } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ShapeItemLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ShapeItemLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..2b3412d67fb06cf6169f86a5c48d9a37706f300b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ShapeItemLayer.swift @@ -0,0 +1,344 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ShapeItemLayer + +/// A CALayer type that renders an array of `[ShapeItem]`s, +/// from a `Group` in a `ShapeLayerModel`. +final class ShapeItemLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + /// Initializes a `ShapeItemLayer` that renders a `Group` from a `ShapeLayerModel` + /// - Parameters: + /// - shape: The `ShapeItem` in this group that renders a `GGPath` + /// - otherItems: Other items in this group that affect the appearance of the shape + init(shape: Item, otherItems: [Item], context: LayerContext) throws { + self.shape = shape + self.otherItems = otherItems + + try context.compatibilityAssert( + shape.item.drawsCGPath, + "`ShapeItemLayer` must contain exactly one `ShapeItem` that draws a `GPPath`") + + try context.compatibilityAssert( + !otherItems.contains(where: { $0.item.drawsCGPath }), + "`ShapeItemLayer` must contain exactly one `ShapeItem` that draws a `GPPath`") + + super.init() + + setupLayerHierarchy() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + shape = typedLayer.shape + otherItems = typedLayer.otherItems + super.init(layer: typedLayer) + } + + // MARK: Internal + + /// An item that can be displayed by this layer + struct Item { + /// A `ShapeItem` that should be rendered by this layer + let item: ShapeItem + + /// The set of groups that this item descends from + /// - Due to the way `GroupLayer`s are setup, the original `ShapeItem` + /// hierarchy from the `ShapeLayer` data model may no longer exactly + /// match the hierarchy of `GroupLayer` / `ShapeItemLayer`s constructed + /// at runtime. Since animation keypaths need to match the original + /// structure of the `ShapeLayer` data model, we track that info here. + let groupPath: [String] + } + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + + guard let sublayerConfiguration else { return } + + switch sublayerConfiguration.fill { + case .solidFill(let shapeLayer): + try setupSolidFillAnimations(shapeLayer: shapeLayer, context: context) + + case .gradientFill(let gradientLayers): + try setupGradientFillAnimations(layers: gradientLayers, context: context) + } + + if let gradientStrokeConfiguration = sublayerConfiguration.gradientStroke { + try setupGradientStrokeAnimations(layers: gradientStrokeConfiguration, context: context) + } + } + + // MARK: Private + + private struct GradientLayers { + /// The `CALayer` that renders the RGB components of the gradient + let gradientColorLayer: GradientRenderLayer + /// The `CALayer` that renders the alpha components of the gradient, + /// masking the `gradientColorLayer` + let gradientAlphaLayer: GradientRenderLayer? + /// The `CAShapeLayer` that clips the gradient layers to the expected shape + let shapeMaskLayer: CAShapeLayer + /// The top-most `CAShapeLayer` used to render `Stroke`s over the gradient if necessary + let overlayLayer: CAShapeLayer? + } + + /// The configuration of this layer's `fill` sublayers + private enum FillLayerConfiguration { + /// This layer displays a single `CAShapeLayer` + case solidFill(CAShapeLayer) + + /// This layer displays a `GradientRenderLayer` masked by a `CAShapeLayer`. + case gradientFill(GradientLayers) + } + + /// The `ShapeItem` in this group that renders a `GGPath` + private let shape: Item + + /// Other items in this group that affect the appearance of the shape + private let otherItems: [Item] + + /// The current configuration of this layer's sublayer(s) + private var sublayerConfiguration: (fill: FillLayerConfiguration, gradientStroke: GradientLayers?)? + + private func setupLayerHierarchy() { + // We have to build a different layer hierarchy depending on if + // we're rendering a gradient (a `CAGradientLayer` masked by a `CAShapeLayer`) + // or a solid shape (a simple `CAShapeLayer`). + let fillLayerConfiguration: FillLayerConfiguration = + if let gradientFill = otherItems.first(GradientFill.self) { + setupGradientFillLayerHierarchy(for: gradientFill) + } else { + setupSolidFillLayerHierarchy() + } + + let gradientStrokeConfiguration: GradientLayers? = + if let gradientStroke = otherItems.first(GradientStroke.self) { + setupGradientStrokeLayerHierarchy(for: gradientStroke) + } else { + nil + } + + sublayerConfiguration = (fillLayerConfiguration, gradientStrokeConfiguration) + } + + private func setupSolidFillLayerHierarchy() -> FillLayerConfiguration { + let shapeLayer = CAShapeLayer() + addSublayer(shapeLayer) + + // `CAShapeLayer.fillColor` defaults to black, so we have to + // nil out the background color if there isn't an expected fill color + if !otherItems.contains(where: { $0.item is Fill }) { + shapeLayer.fillColor = nil + } + + return .solidFill(shapeLayer) + } + + private func setupGradientFillLayerHierarchy( + for gradientFill: GradientFill) + -> FillLayerConfiguration + { + let container = BaseAnimationLayer() + let pathContainer = BaseAnimationLayer() + + let pathMask = CAShapeLayer() + pathMask.fillColor = .rgb(0, 0, 0) + pathContainer.mask = pathMask + + let rgbGradientLayer = GradientRenderLayer() + pathContainer.addSublayer(rgbGradientLayer) + container.addSublayer(pathContainer) + + let overlayLayer = CAShapeLayer() + overlayLayer.fillColor = nil + container.addSublayer(overlayLayer) + + addSublayer(container) + + let alphaGradientLayer: GradientRenderLayer? + if gradientFill.hasAlphaComponent { + alphaGradientLayer = GradientRenderLayer() + rgbGradientLayer.mask = alphaGradientLayer + } else { + alphaGradientLayer = nil + } + + return .gradientFill(GradientLayers( + gradientColorLayer: rgbGradientLayer, + gradientAlphaLayer: alphaGradientLayer, + shapeMaskLayer: pathMask, + overlayLayer: overlayLayer)) + } + + private func setupGradientStrokeLayerHierarchy( + for gradientStroke: GradientStroke) + -> GradientLayers + { + let container = BaseAnimationLayer() + + let pathMask = CAShapeLayer() + pathMask.fillColor = nil + pathMask.strokeColor = .rgb(0, 0, 0) + container.mask = pathMask + + let rgbGradientLayer = GradientRenderLayer() + container.addSublayer(rgbGradientLayer) + addSublayer(container) + + let alphaGradientLayer: GradientRenderLayer? + if gradientStroke.hasAlphaComponent { + alphaGradientLayer = GradientRenderLayer() + rgbGradientLayer.mask = alphaGradientLayer + } else { + alphaGradientLayer = nil + } + + return GradientLayers( + gradientColorLayer: rgbGradientLayer, + gradientAlphaLayer: alphaGradientLayer, + shapeMaskLayer: pathMask, + overlayLayer: nil) + } + + private func setupSolidFillAnimations( + shapeLayer: CAShapeLayer, + context: LayerAnimationContext) + throws + { + var trimPathMultiplier: PathMultiplier? = nil + if let (trim, context) = otherItems.first(Trim.self, where: { !$0.isEmpty }, context: context) { + trimPathMultiplier = try shapeLayer.addAnimations(for: trim, context: context) + + try context.compatibilityAssert( + otherItems.first(Fill.self) == nil, + """ + The Core Animation rendering engine doesn't currently support applying + trims to filled shapes (only stroked shapes). + """) + } + + try shapeLayer.addAnimations( + for: shape.item, + context: context.for(shape), + pathMultiplier: trimPathMultiplier ?? 1, + roundedCorners: otherItems.first(RoundedCorners.self)) + + if let (fill, context) = otherItems.first(Fill.self, context: context) { + try shapeLayer.addAnimations(for: fill, context: context) + } + + if let (stroke, context) = otherItems.first(Stroke.self, context: context) { + try shapeLayer.addStrokeAnimations(for: stroke, context: context) + } + } + + private func setupGradientFillAnimations( + layers: GradientLayers, + context: LayerAnimationContext) + throws + { + let pathLayers = [layers.shapeMaskLayer, layers.overlayLayer] + for pathLayer in pathLayers { + try pathLayer?.addAnimations( + for: shape.item, + context: context.for(shape), + pathMultiplier: 1, + roundedCorners: otherItems.first(RoundedCorners.self)) + } + + if let (gradientFill, context) = otherItems.first(GradientFill.self, context: context) { + layers.shapeMaskLayer.fillRule = gradientFill.fillRule.caFillRule + try layers.gradientColorLayer.addGradientAnimations(for: gradientFill, type: .rgb, context: context) + try layers.gradientAlphaLayer?.addGradientAnimations(for: gradientFill, type: .alpha, context: context) + } + + if let (stroke, context) = otherItems.first(Stroke.self, context: context) { + try layers.overlayLayer?.addStrokeAnimations(for: stroke, context: context) + } + } + + private func setupGradientStrokeAnimations( + layers: GradientLayers, + context: LayerAnimationContext) + throws + { + var trimPathMultiplier: PathMultiplier? = nil + if let (trim, context) = otherItems.first(Trim.self, context: context) { + trimPathMultiplier = try layers.shapeMaskLayer.addAnimations(for: trim, context: context) + } + + try layers.shapeMaskLayer.addAnimations( + for: shape.item, + context: context.for(shape), + pathMultiplier: trimPathMultiplier ?? 1, + roundedCorners: otherItems.first(RoundedCorners.self)) + + if let (gradientStroke, context) = otherItems.first(GradientStroke.self, context: context) { + try layers.gradientColorLayer.addGradientAnimations(for: gradientStroke, type: .rgb, context: context) + try layers.gradientAlphaLayer?.addGradientAnimations(for: gradientStroke, type: .alpha, context: context) + + try layers.shapeMaskLayer.addStrokeAnimations(for: gradientStroke, context: context) + } + } + +} + +// MARK: - [ShapeItem] helpers + +extension [ShapeItemLayer.Item] { + /// The first `ShapeItem` in this array of the given type + func first( + _: ItemType.Type, + where condition: (ItemType) -> Bool = { _ in true }, + context: LayerAnimationContext) + -> (item: ItemType, context: LayerAnimationContext)? + { + for item in self { + if let match = item.item as? ItemType, condition(match) { + return (match, context.for(item)) + } + } + + return nil + } + + /// The first `ShapeItem` in this array of the given type + func first(_: ItemType.Type) -> ItemType? { + for item in self { + if let match = item.item as? ItemType { + return match + } + } + + return nil + } +} + +extension LayerAnimationContext { + /// An updated `LayerAnimationContext` with the`AnimationKeypath` + /// that refers to this specific `ShapeItem`. + func `for`(_ item: ShapeItemLayer.Item) -> LayerAnimationContext { + var context = self + + for parentGroupName in item.groupPath { + context.currentKeypath.keys.append(parentGroupName) + } + + context.currentKeypath.keys.append(item.item.name) + return context + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ShapeLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ShapeLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..6a38808af937ec2540fd788b8aad17dec9977613 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/ShapeLayer.swift @@ -0,0 +1,561 @@ +// Created by Cal Stephens on 12/14/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ShapeLayer + +/// The CALayer type responsible for rendering `ShapeLayerModel`s +final class ShapeLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init(shapeLayer: ShapeLayerModel, context: LayerContext) throws { + self.shapeLayer = shapeLayer + super.init(layerModel: shapeLayer) + try setUpGroups(context: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + shapeLayer = typedLayer.shapeLayer + super.init(layer: typedLayer) + } + + // MARK: Private + + private let shapeLayer: ShapeLayerModel + + private func setUpGroups(context: LayerContext) throws { + let shapeItems = shapeLayer.items.map { ShapeItemLayer.Item(item: $0, groupPath: []) } + try setupGroups(from: shapeItems, parentGroup: nil, parentGroupPath: [], context: context) + } + +} + +// MARK: - GroupLayer + +/// The CALayer type responsible for rendering `Group`s +final class GroupLayer: BaseAnimationLayer { + + // MARK: Lifecycle + + init(group: Group, items: [ShapeItemLayer.Item], groupPath: [String], context: LayerContext) throws { + self.group = group + self.items = items + self.groupPath = groupPath + super.init() + try setupLayerHierarchy(context: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + group = typedLayer.group + items = typedLayer.items + groupPath = typedLayer.groupPath + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + + if let (shapeTransform, context) = nonGroupItems.first(ShapeTransform.self, context: context) { + try addTransformAnimations(for: shapeTransform, context: context) + try addOpacityAnimation(for: shapeTransform, context: context) + } + } + + // MARK: Private + + private let group: Group + + /// `ShapeItemLayer.Item`s rendered by this `Group` + /// - In the original `ShapeLayer` data model, these items could have originated from a different group + private let items: [ShapeItemLayer.Item] + + /// The keypath that represents this group, with respect to the parent `ShapeLayer` + /// - Due to the way `GroupLayer`s are setup, the original `ShapeItem` + /// hierarchy from the `ShapeLayer` data model may no longer exactly + /// match the hierarchy of `GroupLayer` / `ShapeItemLayer`s constructed + /// at runtime. Since animation keypaths need to match the original + /// structure of the `ShapeLayer` data model, we track that info here. + private let groupPath: [String] + + /// Child group items contained in this group. Correspond to a child `GroupLayer` + private lazy var childGroups = items.filter { $0.item is Group } + + /// `ShapeItem`s (other than nested `Group`s) that are rendered by this layer + private lazy var nonGroupItems = items.filter { !($0.item is Group) } + + private func setupLayerHierarchy(context: LayerContext) throws { + // Groups can contain other groups, so we may have to continue + // recursively creating more `GroupLayer`s + try setupGroups(from: items, parentGroup: group, parentGroupPath: groupPath, context: context) + + // Create `ShapeItemLayer`s for each subgroup of shapes that should be rendered as a single unit + // - These groups are listed from front-to-back, so we have to add the sublayers in reverse order + let renderGroups = items.shapeRenderGroups(groupHasChildGroupsToInheritUnusedItems: !childGroups.isEmpty) + for shapeRenderGroup in renderGroups.validGroups.reversed() { + // When there are multiple path-drawing items, they're supposed to be rendered + // in a single `CAShapeLayer` (instead of rendering them in separate layers) so + // `CAShapeLayerFillRule.evenOdd` can be applied correctly if the paths overlap. + // Since a `CAShapeLayer` only supports animating a single `CGPath` from a single `KeyframeGroup`, + // this requires combining all of the path-drawing items into a single set of keyframes. + if + shapeRenderGroup.pathItems.count > 1, + // We currently only support this codepath for `Shape` items that directly contain bezier path keyframes. + // We could also support this for other path types like rectangles, ellipses, and polygons with more work. + shapeRenderGroup.pathItems.allSatisfy({ $0.item is Shape }), + // `Trim`s are currently only applied correctly using individual `ShapeItemLayer`s, + // because each path has to be trimmed separately. + !shapeRenderGroup.otherItems.contains(where: { $0.item is Trim }) + { + let allPathKeyframes = shapeRenderGroup.pathItems.compactMap { ($0.item as? Shape)?.path } + let combinedShape = CombinedShapeItem( + shapes: Keyframes.combined(allPathKeyframes), + name: group.name) + + let sublayer = try ShapeItemLayer( + shape: ShapeItemLayer.Item(item: combinedShape, groupPath: shapeRenderGroup.pathItems[0].groupPath), + otherItems: shapeRenderGroup.otherItems, + context: context) + + addSublayer(sublayer) + } + + // Otherwise, if each `ShapeItem` that draws a `GGPath` animates independently, + // we have to create a separate `ShapeItemLayer` for each one. This may render + // incorrectly if there are multiple paths that overlap with each other. + else { + for pathDrawingItem in shapeRenderGroup.pathItems { + let sublayer = try ShapeItemLayer( + shape: pathDrawingItem, + otherItems: shapeRenderGroup.otherItems, + context: context) + + addSublayer(sublayer) + } + } + } + } + +} + +extension CALayer { + + // MARK: Fileprivate + + /// Sets up `GroupLayer`s for each `Group` in the given list of `ShapeItem`s + /// - Each `Group` item becomes its own `GroupLayer` sublayer. + /// - Other `ShapeItem` are applied to all sublayers + fileprivate func setupGroups( + from items: [ShapeItemLayer.Item], + parentGroup: Group?, + parentGroupPath: [String], + context: LayerContext) + throws + { + // If the layer has any `Repeater`s, set up each repeater + // and then handle any remaining groups like normal. + if items.contains(where: { $0.item is Repeater }) { + let repeaterGroupings = items.split(whereSeparator: { $0.item is Repeater }) + + // Iterate through the groupings backwards to preserve the expected rendering order + for repeaterGrouping in repeaterGroupings.reversed() { + // Each repeater applies to the previous items in the list + if let repeater = repeaterGrouping.trailingSeparator?.item as? Repeater { + try setUpRepeater( + repeater, + items: repeaterGrouping.grouping, + parentGroupPath: parentGroupPath, + context: context) + } + + // Any remaining items after the last repeater are handled like normal + else { + try setupGroups( + from: repeaterGrouping.grouping, + parentGroup: parentGroup, + parentGroupPath: parentGroupPath, + context: context) + } + } + } + + else { + let groupLayers = try makeGroupLayers( + from: items, + parentGroup: parentGroup, + parentGroupPath: parentGroupPath, + context: context) + + for groupLayer in groupLayers { + addSublayer(groupLayer) + } + } + } + + // MARK: Private + + /// Sets up this layer using the given `Repeater` + private func setUpRepeater( + _ repeater: Repeater, + items allItems: [ShapeItemLayer.Item], + parentGroupPath: [String], + context: LayerContext) + throws + { + let items = allItems.filter { !($0.item is Repeater) } + let copyCount = Int(try repeater.copies.exactlyOneKeyframe(context: context, description: "repeater copies").value) + + for index in 0.. [GroupLayer] + { + var groupItems = items.compactMap { $0.item as? Group }.filter { !$0.hidden } + var otherItems = items.filter { !($0.item is Group) && !$0.item.hidden } + + // Handle the top-level `shapeLayer.items` array. This is typically just a single `Group`, + // but in practice can be any combination of items. The implementation expects all path-drawing + // shape items to be managed by a `GroupLayer`, so if there's a top-level path item we + // have to create a placeholder group. + if parentGroup == nil, otherItems.contains(where: { $0.item.drawsCGPath }) { + groupItems = [Group(items: items.map { $0.item }, name: "")] + otherItems = [] + } + + // Any child items that wouldn't be included in a valid shape render group + // need to be applied to child groups (otherwise they'd be silently ignored). + let inheritedItemsForChildGroups = otherItems + .shapeRenderGroups(groupHasChildGroupsToInheritUnusedItems: !groupItems.isEmpty) + .unusedItems + + // Groups are listed from front to back, + // but `CALayer.sublayers` are listed from back to front. + let groupsInZAxisOrder = groupItems.reversed() + + return try groupsInZAxisOrder.compactMap { group in + var pathForChildren = parentGroupPath + if !group.name.isEmpty { + pathForChildren.append(group.name) + } + + let childItems = group.items + .filter { !$0.hidden } + .map { ShapeItemLayer.Item(item: $0, groupPath: pathForChildren) } + + // Some shape item properties are affected by scaling (e.g. stroke width). + // The child group may have a `ShapeTransform` that affects the scale of its items, + // but shouldn't affect the scale of any inherited items. To prevent this scale + // from affecting inherited items, we have to apply an inverse scale to them. + let inheritedItems = try inheritedItemsForChildGroups.map { item in + ShapeItemLayer.Item( + item: try item.item.scaledCopyForChildGroup(group, context: context), + groupPath: item.groupPath) + } + + return try GroupLayer( + group: group, + items: childItems + inheritedItems, + groupPath: pathForChildren, + context: context) + } + } +} + +extension ShapeItem { + /// Whether or not this `ShapeItem` is responsible for rendering a `CGPath` + var drawsCGPath: Bool { + switch type { + case .ellipse, .rectangle, .shape, .star: + true + + case .fill, .gradientFill, .group, .gradientStroke, .merge, + .repeater, .round, .stroke, .trim, .transform, .unknown: + false + } + } + + /// Whether or not this `ShapeItem` provides a fill for a set of shapes + var isFill: Bool { + switch type { + case .fill, .gradientFill: + true + + case .ellipse, .rectangle, .shape, .star, .group, .gradientStroke, + .merge, .repeater, .round, .stroke, .trim, .transform, .unknown: + false + } + } + + /// Whether or not this `ShapeItem` provides a stroke for a set of shapes + var isStroke: Bool { + switch type { + case .stroke, .gradientStroke: + true + + case .ellipse, .rectangle, .shape, .star, .group, .gradientFill, + .merge, .repeater, .round, .fill, .trim, .transform, .unknown: + false + } + } + + /// For any inherited shape items that are affected by scaling (e.g. strokes but not fills), + /// any `ShapeTransform` in the given child group isn't supposed to be applied to the item. + /// To cancel out the effect of the transform, we can apply an inverse transform to the + /// shape item. + func scaledCopyForChildGroup(_ childGroup: Group, context: LayerContext) throws -> ShapeItem { + guard + // Path-drawing items aren't inherited by child groups in this way + !drawsCGPath, + // Stroke widths are affected by scaling, but fill colors aren't. + // We can expand this to other types of items in the future if necessary. + let stroke = self as? StrokeShapeItem, + // We only need to handle scaling if there's a `ShapeTransform` present + let transform = childGroup.items.first(where: { $0 is ShapeTransform }) as? ShapeTransform + else { return self } + + let newWidth = try Keyframes.combined(stroke.width, transform.scale) { strokeWidth, scale -> LottieVector1D in + // Since we're applying this scale to a scalar value rather than to a layer, + // we can only handle cases where the scale is also a scalar (e.g. the same for both x and y) + try context.compatibilityAssert(scale.x == scale.y, """ + The Core Animation rendering engine doesn't support applying separate x/y scale values \ + (x: \(scale.x), y: \(scale.y)) to this stroke item (\(self.name)). + """) + + return LottieVector1D(strokeWidth.value * (100 / scale.x)) + } + + return stroke.copy(width: newWidth) + } +} + +extension Collection { + /// Splits this collection into two groups, based on the given predicate + func grouped(by predicate: (Element) -> Bool) -> (trueElements: [Element], falseElements: [Element]) { + var trueElements = [Element]() + var falseElements = [Element]() + + for element in self { + if predicate(element) { + trueElements.append(element) + } else { + falseElements.append(element) + } + } + + return (trueElements, falseElements) + } + + /// Splits this collection into an array of grouping separated by the given separator. + /// For example, `[A, B, C]` split by `B` returns an array with two elements: + /// 1. `(grouping: [A], trailingSeparator: B)` + /// 2. `(grouping: [C], trailingSeparator: nil)` + func split(whereSeparator separatorPredicate: (Element) -> Bool) + -> [(grouping: [Element], trailingSeparator: Element?)] + { + guard !isEmpty else { return [] } + + var groupings: [(grouping: [Element], trailingSeparator: Element?)] = [] + + for element in self { + if groupings.isEmpty || groupings.last?.trailingSeparator != nil { + groupings.append((grouping: [], trailingSeparator: nil)) + } + + if separatorPredicate(element) { + groupings[groupings.indices.last!].trailingSeparator = element + } else { + groupings[groupings.indices.last!].grouping.append(element) + } + } + + return groupings + } +} + +// MARK: - ShapeRenderGroup + +/// A group of `ShapeItem`s that should be rendered together as a single unit +struct ShapeRenderGroup { + /// The items in this group that render `CGPath`s. + /// Valid shape render groups must have at least one path-drawing item. + var pathItems: [ShapeItemLayer.Item] = [] + /// Shape items that modify the appearance of the shapes rendered by this group + var otherItems: [ShapeItemLayer.Item] = [] +} + +extension [ShapeItemLayer.Item] { + /// Splits this list of `ShapeItem`s into groups that should be rendered together as individual units, + /// plus the remaining items that were not included in any group. + /// - groupHasChildGroupsToInheritUnusedItems: whether or not this group has child groups + /// that will inherit any items that aren't used as part of a valid render group + func shapeRenderGroups(groupHasChildGroupsToInheritUnusedItems: Bool) + -> (validGroups: [ShapeRenderGroup], unusedItems: [ShapeItemLayer.Item]) + { + var renderGroups = [ShapeRenderGroup()] + + for item in self { + // `renderGroups` is non-empty, so is guaranteed to have a valid end index + var lastIndex: Int { + renderGroups.indices.last! + } + + if item.item.drawsCGPath { + // Trims should only affect paths that precede them in the group, + // so if the existing group already has a trim we create a new group for this path item. + if renderGroups[lastIndex].otherItems.contains(where: { $0.item is Trim }) { + renderGroups.append(ShapeRenderGroup()) + } + + renderGroups[lastIndex].pathItems.append(item) + } + + // `Fill` items are unique, because they specifically only apply to _previous_ shapes in a `Group` + // - For example, with [Rectangle, Fill(Red), Circle, Fill(Blue)], the Rectangle should be Red + // but the Circle should be Blue. + // - To handle this, we create a new `ShapeRenderGroup` when we encounter a `Fill` item + else if item.item.isFill { + renderGroups[lastIndex].otherItems.append(item) + + // There are cases where the current render group doesn't have a path-drawing + // shape item yet, and could just contain this fill. Some examples: + // - `[Circle, Fill(Red), Fill(Green)]`: In this case, the second fill would + // be unused and silently ignored. To avoid this we render the fill using + // the shape items from the previous group. + // - `[Circle, Fill(Red), Group, Fill(Green)]`: In this case, the second fill + // is inherited and rendered by the child group. + if + renderGroups[lastIndex].pathItems.isEmpty, + !groupHasChildGroupsToInheritUnusedItems, + lastIndex != renderGroups.indices.first + { + renderGroups[lastIndex].pathItems = renderGroups[lastIndex - 1].pathItems + } + + // Finalize the group so the fill item doesn't affect following shape items + renderGroups.append(ShapeRenderGroup()) + } + + // Other items in the list are applied to all subgroups + else { + for index in renderGroups.indices { + renderGroups[index].otherItems.append(item) + } + } + } + + /// The main thread rendering engine draws each Stroke and Fill as a separate `CAShapeLayer`. + /// As an optimization, we can combine them into a single shape layer when a few conditions are met: + /// 1. There is at most one stroke and one fill (a `CAShapeLayer` can only render one of each) + /// 2. The stroke is drawn on top of the fill (the behavior of a `CAShapeLayer`) + /// 3. The fill and stroke have the same `opacity` animations (since a `CAShapeLayer` can only render + /// a single set of `opacity` animations). + /// Otherwise, each stroke / fill needs to be split into a separate layer. + renderGroups = renderGroups.flatMap { group -> [ShapeRenderGroup] in + let (strokesAndFills, otherItems) = group.otherItems.grouped(by: { $0.item.isFill || $0.item.isStroke }) + let (strokes, fills) = strokesAndFills.grouped(by: { $0.item.isStroke }) + + // A `CAShapeLayer` can only draw a single fill and a single stroke + let hasAtMostOneFill = fills.count <= 1 + let hasAtMostOneStroke = strokes.count <= 1 + + // A `CAShapeLayer` can only draw a stroke on top of a fill -- if the fill is supposed to be + // drawn on top of the stroke, then they have to be rendered as separate layers. + let strokeDrawnOnTopOfFill: Bool = + if + let strokeIndex = strokesAndFills.firstIndex(where: { $0.item.isStroke }), + let fillIndex = strokesAndFills.firstIndex(where: { $0.item.isFill }) + { + strokeIndex < fillIndex + } else { + false + } + + // `Fill` and `Stroke` items have an `alpha` property that can be animated separately, + // but each layer only has a single `opacity` property. We can only use a single `CAShapeLayer` + // when the items have the same `alpha` animations. + let allAlphaAnimationsAreIdentical = { + strokesAndFills.allSatisfy { item in + (item.item as? OpacityAnimationModel)?.opacity + == (strokesAndFills.first?.item as? OpacityAnimationModel)?.opacity + } + } + + // If all the required conditions are met, this group can be rendered using a single `ShapeItemLayer` + if + hasAtMostOneFill, + hasAtMostOneStroke, + strokeDrawnOnTopOfFill, + allAlphaAnimationsAreIdentical() + { + return [group] + } + + // Otherwise each stroke / fill needs to be rendered as a separate `ShapeItemLayer` + return strokesAndFills.map { strokeOrFill in + ShapeRenderGroup( + pathItems: group.pathItems, + otherItems: [strokeOrFill] + otherItems) + } + } + + // All valid render groups must have a path, otherwise the items wouldn't be rendered + renderGroups = renderGroups.filter { renderGroup in + !renderGroup.pathItems.isEmpty + } + + let itemsInValidRenderGroups = NSSet( + array: renderGroups.lazy + .flatMap { $0.pathItems + $0.otherItems } + .map { $0.item }) + + // `unusedItems` should only include each original item a single time, + // and should preserve the existing order + let itemsNotInValidRenderGroups = filter { item in + !itemsInValidRenderGroups.contains(item.item) + } + + return (validGroups: renderGroups, unusedItems: itemsNotInValidRenderGroups) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/SolidLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/SolidLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..2e1d5e33e154fd8ba1f8a4ce96d91f73ed40f703 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/SolidLayer.swift @@ -0,0 +1,65 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - SolidLayer + +final class SolidLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init(_ solidLayer: SolidLayerModel) { + self.solidLayer = solidLayer + super.init(layerModel: solidLayer) + setupContentLayer() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + solidLayer = typedLayer.solidLayer + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + + var context = context + context = context.addingKeypathComponent(solidLayer.name) + + // Even though the Lottie json schema provides a fixed `solidLayer.colorHex` value, + // we still need to create a set of keyframes and go through the standard `CAAnimation` + // codepath so that this value can be customized using the custom `ValueProvider`s API. + try shapeLayer.addAnimation( + for: .fillColor, + keyframes: KeyframeGroup(solidLayer.colorHex.lottieColor), + value: { $0.cgColorValue }, + context: context) + } + + // MARK: Private + + private let solidLayer: SolidLayerModel + + /// Render the fill color in a child `CAShapeLayer` + /// - Using a `CAShapeLayer` specifically, instead of a `CALayer` with a `backgroundColor`, + /// allows the size of the fill shape to be different from `contentsLayer.size`. + private let shapeLayer = CAShapeLayer() + + private func setupContentLayer() { + shapeLayer.path = CGPath(rect: .init(x: 0, y: 0, width: solidLayer.width, height: solidLayer.height), transform: nil) + addSublayer(shapeLayer) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/TextLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/TextLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..48b77096208c46e3794e0420aee8c6fdc91370c9 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/TextLayer.swift @@ -0,0 +1,115 @@ +// Created by Cal Stephens on 2/9/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +/// The `CALayer` type responsible for rendering `TextLayer`s +final class TextLayer: BaseCompositionLayer { + + // MARK: Lifecycle + + init( + textLayerModel: TextLayerModel, + context: LayerContext) + throws + { + self.textLayerModel = textLayerModel + super.init(layerModel: textLayerModel) + setupSublayers() + try configureRenderLayer(with: context) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + textLayerModel = typedLayer.textLayerModel + super.init(layer: typedLayer) + } + + // MARK: Internal + + override func setupAnimations(context: LayerAnimationContext) throws { + try super.setupAnimations(context: context) + let textAnimationContext = context.addingKeypathComponent(textLayerModel.name) + + let sourceText = try textLayerModel.text.exactlyOneKeyframe( + context: textAnimationContext, + description: "text layer text") + + // Prior to Lottie 4.3.0 the Core Animation rendering engine always just used `LegacyAnimationTextProvider` + // but incorrectly called it with the full keypath string, unlike the Main Thread rendering engine + // which only used the last component of the keypath. Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider` + // instead if implemented. + if let keypathTextValue = context.textProvider.text(for: textAnimationContext.currentKeypath, sourceText: sourceText.text) { + renderLayer.text = keypathTextValue + } else if let legacyTextProvider = context.textProvider as? LegacyAnimationTextProvider { + renderLayer.text = legacyTextProvider.textFor( + keypathName: textAnimationContext.currentKeypath.fullPath, + sourceText: sourceText.text) + } else { + renderLayer.text = sourceText.text + } + + renderLayer.sizeToFit() + } + + func configureRenderLayer(with context: LayerContext) throws { + // We can't use `CATextLayer`, because it doesn't support enough features we use. + // Instead, we use the same `CoreTextRenderLayer` (with a custom `draw` implementation) + // used by the Main Thread rendering engine. This means the Core Animation engine can't + // _animate_ text properties, but it can display static text without any issues. + let text = try textLayerModel.text.exactlyOneKeyframe(context: context, description: "text layer text") + + // The Core Animation engine doesn't currently support `TextAnimator`s. + // - We could add support for animating the transform-related properties without much trouble. + // - We may be able to support animating `fillColor` by getting clever with layer blend modes + // or masks (e.g. use `CoreTextRenderLayer` to draw black glyphs, and then fill them in + // using a `CAShapeLayer`). + if !textLayerModel.animators.isEmpty { + try context.logCompatibilityIssue(""" + The Core Animation rendering engine currently doesn't support text animators. + """) + } + + renderLayer.font = context.fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) + + renderLayer.alignment = text.justification.textAlignment + renderLayer.lineHeight = CGFloat(text.lineHeight) + renderLayer.tracking = (CGFloat(text.fontSize) * CGFloat(text.tracking)) / 1000 + + renderLayer.fillColor = text.fillColorData?.cgColorValue + renderLayer.strokeColor = text.strokeColorData?.cgColorValue + renderLayer.strokeWidth = CGFloat(text.strokeWidth ?? 0) + renderLayer.strokeOnTop = text.strokeOverFill ?? false + + renderLayer.preferredSize = text.textFrameSize?.sizeValue + renderLayer.sizeToFit() + + renderLayer.transform = CATransform3DIdentity + renderLayer.position = text.textFramePosition?.pointValue ?? .zero + } + + // MARK: Private + + private let textLayerModel: TextLayerModel + private let renderLayer = CoreTextRenderLayer() + + private func setupSublayers() { + // Place the text render layer in an additional container + // - Direct sublayers of a `BaseCompositionLayer` always fill the bounds + // of their superlayer -- so this container will be the bounds of self, + // and the text render layer can be positioned anywhere. + let textContainerLayer = CALayer() + textContainerLayer.addSublayer(renderLayer) + addSublayer(textContainerLayer) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/TransformLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/TransformLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..027739a4473334a52f0987d82ef69d69a2372608 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/Layers/TransformLayer.swift @@ -0,0 +1,11 @@ +// Created by Cal Stephens on 12/21/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +/// The CALayer type responsible for only rendering the `transform` of a `LayerModel` +final class TransformLayer: BaseCompositionLayer { + + /// `TransformLayer`s don't render any visible content, + /// they just `transform` their sublayers + override var renderLayerContents: Bool { false } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/ValueProviderStore.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/ValueProviderStore.swift new file mode 100644 index 0000000000000000000000000000000000000000..76934114bcc2db611368e17785d8777d410965d3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/CoreAnimation/ValueProviderStore.swift @@ -0,0 +1,151 @@ +// Created by Cal Stephens on 1/13/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - ValueProviderStore + +/// Registration and storage for `AnyValueProvider`s that can dynamically +/// provide custom values for `AnimationKeypath`s within a `LottieAnimation`. +final class ValueProviderStore { + + // MARK: Lifecycle + + init(logger: LottieLogger) { + self.logger = logger + } + + // MARK: Internal + + /// Registers an `AnyValueProvider` for the given `AnimationKeypath` + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + logger.assert( + valueProvider.typeErasedStorage.isSupportedByCoreAnimationRenderingEngine, + """ + The Core Animation rendering engine doesn't support Value Providers that vend a closure, + because that would require calling the closure on the main thread once per frame. + """) + + let supportedProperties = PropertyName.allCases.map { $0.rawValue } + let propertyBeingCustomized = keypath.keys.last ?? "" + + logger.assert( + supportedProperties.contains(propertyBeingCustomized), + """ + The Core Animation rendering engine currently doesn't support customizing "\(propertyBeingCustomized)" \ + properties. Supported properties are: \(supportedProperties.joined(separator: ", ")). + """) + + valueProviders.removeAll(where: { $0.keypath == keypath }) + valueProviders.append((keypath: keypath, valueProvider: valueProvider)) + } + + /// Retrieves the custom value keyframes for the given property, + /// if an `AnyValueProvider` was registered for the given keypath. + func customKeyframes( + of customizableProperty: CustomizableProperty, + for keypath: AnimationKeypath, + context: LayerAnimationContext) + throws -> KeyframeGroup? + { + context.recordHierarchyKeypath?(keypath.fullPath) + + guard let anyValueProvider = valueProvider(for: keypath) else { + return nil + } + + // Retrieve the type-erased keyframes from the custom `ValueProvider` + let typeErasedKeyframes: [Keyframe] + switch anyValueProvider.typeErasedStorage { + case .singleValue(let typeErasedValue): + typeErasedKeyframes = [Keyframe(typeErasedValue)] + + case .keyframes(let keyframes, _): + typeErasedKeyframes = keyframes + + case .closure: + try context.logCompatibilityIssue(""" + The Core Animation rendering engine doesn't support Value Providers that vend a closure, + because that would require calling the closure on the main thread once per frame. + """) + return nil + } + + // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure + let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe? in + guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value, anyValueProvider) else { + logger.assertionFailure(""" + Could not convert value of type \(type(of: typeErasedKeyframe.value)) from \(anyValueProvider) to expected type \( + Value + .self) + """) + return nil + } + + return typeErasedKeyframe.withValue(convertedValue) + } + + // Verify that all of the keyframes were successfully converted to the expected type + guard typedKeyframes.count == typeErasedKeyframes.count else { + return nil + } + + return KeyframeGroup(keyframes: ContiguousArray(typedKeyframes)) + } + + // MARK: Private + + private let logger: LottieLogger + private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]() + + /// Retrieves the most-recently-registered Value Provider that matches the given keypath. + private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? { + // Find the last keypath matching the given keypath, + // so we return the value provider that was registered most-recently + valueProviders.last(where: { registeredKeypath, _ in + keypath.matches(registeredKeypath) + })?.valueProvider + } + +} + +extension AnyValueProviderStorage { + /// Whether or not this type of value provider is supported + /// by the new Core Animation rendering engine + var isSupportedByCoreAnimationRenderingEngine: Bool { + switch self { + case .singleValue, .keyframes: + true + case .closure: + false + } + } +} + +extension AnimationKeypath { + /// Whether or not this keypath from the animation hierarchy + /// matches the given keypath (which may contain wildcards) + func matches(_ keypath: AnimationKeypath) -> Bool { + var regex = "^" // match the start of the string + + keypath.keys.joined(separator: "\\.") // match this keypath, escaping "." characters + + "$" // match the end of the string + + // Replace the ** and * wildcards with markers that are guaranteed to be unique + // and won't conflict with regex syntax (e.g. `.*`). + let doubleWildcardMarker = UUID().uuidString + let singleWildcardMarker = UUID().uuidString + regex = regex.replacingOccurrences(of: "**", with: doubleWildcardMarker) + regex = regex.replacingOccurrences(of: "*", with: singleWildcardMarker) + + // "**" wildcards match zero or more path segments separated by "\\." + // - "**.Color" matches any of "Color", "Layer 1.Color", and "Layer 1.Layer 2.Color" + regex = regex.replacingOccurrences(of: "\(doubleWildcardMarker)\\.", with: ".*") + regex = regex.replacingOccurrences(of: doubleWildcardMarker, with: ".*") + + // "*" wildcards match exactly one path component + // - "*.Color" matches "Layer 1.Color" but not "Layer 1.Layer 2.Color" + regex = regex.replacingOccurrences(of: singleWildcardMarker, with: "[^.]+") + + return fullPath.range(of: regex, options: .regularExpression) != nil + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift new file mode 100644 index 0000000000000000000000000000000000000000..572e2a2812eba9f6046061ccc47a50b1e86fb526 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift @@ -0,0 +1,263 @@ +// Created by Laura Skelton on 11/25/16. +// Copyright © 2016 Airbnb. All rights reserved. + +// MARK: - Collection + +extension Collection where Element: Diffable, Index == Int { + + /// Diffs two collections (e.g. `Array`s) of `Diffable` items, returning an `IndexChangeset` + /// representing the minimal set of changes to get from the other collection to this collection. + /// + /// - Parameters: + /// - from other: The collection of old data. + func makeChangeset(from other: Self) -> IndexChangeset { + // Arranging the elements contiguously prior to diffing improves performance by ~40%. + let new = ContiguousArray(self) + let old = ContiguousArray(other) + + /// The entries in both this and the other collection, keyed by their `dataID`s. + var entries = [AnyHashable: EpoxyEntry](minimumCapacity: new.count) + var duplicates = [EpoxyEntry]() + + var newResults = ContiguousArray() + newResults.reserveCapacity(new.count) + + for index in new.indices { + let id = new[index].diffIdentifier + let entry = entries[id, default: EpoxyEntry()] + if entry.trackNewIndex(index) { + duplicates.append(entry) + } + entries[id] = entry + newResults.append(NewRecord(entry: entry)) + } + + var oldResults = ContiguousArray() + oldResults.reserveCapacity(old.count) + + for index in old.indices { + let id = old[index].diffIdentifier + let entry = entries[id] + entry?.pushOldIndex(index) + oldResults.append(OldRecord(entry: entry)) + } + + for newIndex in new.indices { + let entry = newResults[newIndex].entry + if let oldIndex = entry.popOldIndex() { + let newItem = new[newIndex] + let oldItem = other[oldIndex] + + if !oldItem.isDiffableItemEqual(to: newItem) { + entry.isUpdated = true + } + + newResults[newIndex].correspondingOldIndex = oldIndex + oldResults[oldIndex].correspondingNewIndex = newIndex + } + } + + var deletes = [Int]() + var deleteOffsets = [Int]() + deleteOffsets.reserveCapacity(old.count) + var runningDeleteOffset = 0 + + for index in old.indices { + deleteOffsets.append(runningDeleteOffset) + + let record = oldResults[index] + + if record.correspondingNewIndex == nil { + deletes.append(index) + runningDeleteOffset += 1 + } + } + + var inserts = [Int]() + var updates = [(Int, Int)]() + var moves = [(Int, Int)]() + var insertOffsets = [Int]() + insertOffsets.reserveCapacity(new.count) + var runningInsertOffset = 0 + + for index in new.indices { + insertOffsets.append(runningInsertOffset) + + let record = newResults[index] + + if let oldArrayIndex = record.correspondingOldIndex { + if record.entry.isUpdated { + updates.append((oldArrayIndex, index)) + } + + let insertOffset = insertOffsets[index] + let deleteOffset = deleteOffsets[oldArrayIndex] + if (oldArrayIndex - deleteOffset + insertOffset) != index { + moves.append((oldArrayIndex, index)) + } + + } else { + inserts.append(index) + runningInsertOffset += 1 + } + } + + EpoxyLogger.shared.assert( + old.count + inserts.count - deletes.count == new.count, + "Failed sanity check for old count with changes matching new count.") + + return IndexChangeset( + inserts: inserts, + deletes: deletes, + updates: updates, + moves: moves, + newIndices: oldResults.map { $0.correspondingNewIndex }, + duplicates: duplicates.map { $0.newIndices }) + } + + /// Diffs between two collections (eg. `Array`s) of `Diffable` items, and returns an `IndexPathChangeset` + /// representing the minimal set of changes to get from the other collection to this collection. + /// + /// - Parameters: + /// - from other: The collection of old data. + /// - fromSection: The section the other collection's data exists within. Defaults to `0`. + /// - toSection: The section this collection's data exists within. Defaults to `0`. + func makeIndexPathChangeset( + from other: Self, + fromSection: Int = 0, + toSection: Int = 0) + -> IndexPathChangeset + { + let indexChangeset = makeChangeset(from: other) + + return IndexPathChangeset( + inserts: indexChangeset.inserts.map { index in + [toSection, index] + }, + deletes: indexChangeset.deletes.map { index in + [fromSection, index] + }, + updates: indexChangeset.updates.map { fromIndex, toIndex in + ([fromSection, fromIndex], [toSection, toIndex]) + }, + moves: indexChangeset.moves.map { fromIndex, toIndex in + ([fromSection, fromIndex], [toSection, toIndex]) + }, + duplicates: indexChangeset.duplicates.map { duplicate in + duplicate.map { index in + [toSection, index] + } + }) + } + + /// Diffs between two collections (e.g. `Array`s) of `Diffable` items, returning an + /// `IndexSetChangeset` representing the minimal set of changes to get from the other collection + /// to this collection. + /// + /// - Parameters: + /// - from other: The collection of old data. + func makeIndexSetChangeset(from other: Self) -> IndexSetChangeset { + let indexChangeset = makeChangeset(from: other) + + return IndexSetChangeset( + inserts: .init(indexChangeset.inserts), + deletes: .init(indexChangeset.deletes), + updates: indexChangeset.updates, + moves: indexChangeset.moves, + newIndices: indexChangeset.newIndices, + duplicates: indexChangeset.duplicates.map { .init($0) }) + } + +} + +extension Collection where Element: DiffableSection, Index == Int { + /// Diffs between two collections (e.g. `Array`s) of `DiffableSection` items, returning an + /// `SectionedChangeset` representing the minimal set of changes to get from the other collection + /// to this collection. + /// + /// - Parameters: + /// - from other: The collection of old data. + func makeSectionedChangeset(from other: Self) -> SectionedChangeset { + let sectionChangeset = makeIndexSetChangeset(from: other) + var itemChangeset = IndexPathChangeset() + + for fromSectionIndex in other.indices { + guard let toSectionIndex = sectionChangeset.newIndices[fromSectionIndex] else { + continue + } + + let fromItems = other[fromSectionIndex].diffableItems + let toItems = self[toSectionIndex].diffableItems + + let itemIndexChangeset = toItems.makeIndexPathChangeset( + from: fromItems, + fromSection: fromSectionIndex, + toSection: toSectionIndex) + + itemChangeset += itemIndexChangeset + } + + return SectionedChangeset(sectionChangeset: sectionChangeset, itemChangeset: itemChangeset) + } +} + +// MARK: - EpoxyEntry + +/// A bookkeeping refrence type for the diffing algorithm. +private final class EpoxyEntry { + + // MARK: Internal + + private(set) var oldIndices = [Int]() + private(set) var newIndices = [Int]() + var isUpdated = false + + /// Tracks an index from the new indices, returning `true` if this entry has previously tracked + /// a new index as a means to identify duplicates and `false` otherwise. + func trackNewIndex(_ index: Int) -> Bool { + let previouslyEmpty = newIndices.isEmpty + + newIndices.append(index) + + // We've encountered a duplicate, return true so we can track it. + if !previouslyEmpty, newIndices.count == 2 { + return true + } + + return false + } + + func pushOldIndex(_ index: Int) { + oldIndices.append(index) + } + + func popOldIndex() -> Int? { + guard currentOldIndex < oldIndices.endIndex else { + return nil + } + defer { + currentOldIndex += 1 + } + return oldIndices[currentOldIndex] + } + + // MARK: Private + + private var currentOldIndex = 0 +} + +// MARK: - OldRecord + +/// A bookkeeping type for pairing up an old element with its new index. +private struct OldRecord { + var entry: EpoxyEntry? + var correspondingNewIndex: Int? = nil +} + +// MARK: - NewRecord + +/// A bookkeeping type for pairing up a new element with its old index. +private struct NewRecord { + var entry: EpoxyEntry + var correspondingOldIndex: Int? = nil +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift new file mode 100644 index 0000000000000000000000000000000000000000..4cb0b2d2cdf8e018c73b8d9353b1b820ef0cdc38 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift @@ -0,0 +1,18 @@ +// Created by Laura Skelton on 5/11/17. +// Copyright © 2017 Airbnb. All rights reserved. + +// MARK: - Diffable + +/// A protocol that allows us to check identity and equality between items for the purposes of +/// diffing. +protocol Diffable { + + /// Checks for equality between items when diffing. + /// + /// - Parameters: + /// - otherDiffableItem: The other item to check equality against while diffing. + func isDiffableItemEqual(to otherDiffableItem: Diffable) -> Bool + + /// The identifier to use when checking identity while diffing. + var diffIdentifier: AnyHashable { get } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift new file mode 100644 index 0000000000000000000000000000000000000000..acbf93a639a51c34e9f34bdfbdef10786008a2a6 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift @@ -0,0 +1,16 @@ +// Created by eric_horacek on 12/9/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - DiffableSection + +/// A protocol that allows us to check identity and equality between sections of `Diffable` items +/// for the purposes of diffing. +protocol DiffableSection: Diffable { + /// The diffable items in this section. + associatedtype DiffableItems: Collection where + DiffableItems.Index == Int, + DiffableItems.Element: Diffable + + /// The diffable items in this section. + var diffableItems: DiffableItems { get } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift new file mode 100644 index 0000000000000000000000000000000000000000..2cb8653db8826477b900ab9eee671a02b1cf8740 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift @@ -0,0 +1,187 @@ +// Created by Laura Skelton on 11/25/16. +// Copyright © 2016 Airbnb. All rights reserved. + +import Foundation + +// MARK: - IndexChangeset + +/// A set of inserts, deletes, updates, and moves that define the changes between two collections. +struct IndexChangeset { + + // MARK: Lifecycle + + init( + inserts: [Int] = [], + deletes: [Int] = [], + updates: [(old: Int, new: Int)] = [], + moves: [(old: Int, new: Int)] = [], + newIndices: [Int?] = [], + duplicates: [[Int]] = []) + { + self.inserts = inserts + self.deletes = deletes + self.updates = updates + self.moves = moves + self.newIndices = newIndices + self.duplicates = duplicates + } + + // MARK: Internal + + /// The inserted indices needed to get from the old collection to the new collection. + var inserts: [Int] + + /// The deleted indices needed to get from the old collection to the new collection. + var deletes: [Int] + + /// The updated indices needed to get from the old collection to the new collection. + var updates: [(old: Int, new: Int)] + + /// The moved indices needed to get from the old collection to the new collection. + var moves: [(old: Int, new: Int)] + + /// A record for each old collection item to its index (if any) is in the new collection. + /// + /// The indexes of this `Array` represent the indexes old collection, with elements of the + /// corresponding index of the same item in the new collection it exists, else `nil`. + var newIndices: [Int?] + + /// A record of each element in the new collection that has an identical `diffIdentifier` with + /// another element in the same collection. + /// + /// Each element in the outer `Array` corresponds to a duplicated identifier, with each inner + /// `[Int]` containing the indexes that share a duplicate identifier in the new collection. + /// + /// While the diffing algorithm makes a best effort to handle duplicates, they can lead to + /// unexpected behavior since identity of elements cannot be established and should be avoided if + /// possible. + var duplicates: [[Int]] + + /// Whether there are any inserts, deletes, moves, or updates in this changeset. + var isEmpty: Bool { + inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty + } +} + +// MARK: - IndexPathChangeset + +/// A set of inserts, deletes, updates, and moves that define the changes between two collections +/// with indexes stored as `IndexPath`s. +struct IndexPathChangeset { + + // MARK: Lifecycle + + init( + inserts: [IndexPath] = [], + deletes: [IndexPath] = [], + updates: [(old: IndexPath, new: IndexPath)] = [], + moves: [(old: IndexPath, new: IndexPath)] = [], + duplicates: [[IndexPath]] = []) + { + self.inserts = inserts + self.deletes = deletes + self.updates = updates + self.moves = moves + self.duplicates = duplicates + } + + // MARK: Internal + + /// The inserted `IndexPath`s needed to get from the old collection to the new collection. + var inserts: [IndexPath] + + /// The deleted `IndexPath`s needed to get from the old collection to the new collection. + var deletes: [IndexPath] + + /// The updated `IndexPath`s needed to get from the old collection to the new collection. + var updates: [(old: IndexPath, new: IndexPath)] + + /// The moved `IndexPath`s needed to get from the old collection to the new collection. + var moves: [(old: IndexPath, new: IndexPath)] + + /// A record for each element in the new collection that has an identical `diffIdentifier` with + /// another element in the same collection. + /// + /// Each element in the outer `Array` corresponds to a duplicated identifier, with each inner + /// `[IndexPath]` corresponding to the indexes that share a duplicate identifier in the new + /// collection. + /// + /// While the diffing algorithm makes a best effort to handle duplicates, they can lead to + /// unexpected behavior since identity of elements cannot be established and should be avoided if + /// possible. + var duplicates: [[IndexPath]] + + /// Whether there are any inserts, deletes, moves, or updates in this changeset. + var isEmpty: Bool { + inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty + } + + static func += (left: inout IndexPathChangeset, right: IndexPathChangeset) { + left.inserts += right.inserts + left.deletes += right.deletes + left.updates += right.updates + left.moves += right.moves + left.duplicates += right.duplicates + } +} + +// MARK: - IndexSetChangeset + +/// A set of inserts, deletes, updates, and moves that define the changes between two collections +/// with indexes stored as `IndexSet`. +struct IndexSetChangeset { + + // MARK: Lifecycle + + init( + inserts: IndexSet = [], + deletes: IndexSet = [], + updates: [(old: Int, new: Int)] = [], + moves: [(old: Int, new: Int)] = [], + newIndices: [Int?] = [], + duplicates: [IndexSet] = []) + { + self.inserts = inserts + self.deletes = deletes + self.updates = updates + self.moves = moves + self.newIndices = newIndices + self.duplicates = duplicates + } + + // MARK: Internal + + /// An `IndexSet` of inserts needed to get from the old collection to the new collection. + var inserts: IndexSet + + /// An `IndexSet` of deletes needed to get from the old collection to the new collection. + var deletes: IndexSet + + /// The updated indices needed to get from the old collection to the new collection. + var updates: [(old: Int, new: Int)] + + /// The moved indices needed to get from the old collection to the new collection. + var moves: [(old: Int, new: Int)] + + /// A record for each old collection item of what its index (if any) is in the new collection. + /// + /// The indexes of this `Array` represent the indexes old collection, with elements of the + /// corresponding index of the same item in the new collection it exists, else `nil`. + var newIndices: [Int?] + + /// A record for each element in the new collection that has an identical `diffIdentifier` with + /// another element in the same collection. + /// + /// Each element in the `Array` corresponds to a duplicated identifier, with each `IndexSet` + /// containing the indexes that share a duplicate identifier in the new collection. + /// + /// While the diffing algorithm makes a best effort to handle duplicates, they can lead to + /// unexpected behavior since identity of elements cannot be established and should be avoided if + /// possible. + var duplicates: [IndexSet] + + /// Whether there are any inserts, deletes, moves, or updates in this changeset. + var isEmpty: Bool { + inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift new file mode 100644 index 0000000000000000000000000000000000000000..f18fae5737fbdfd3762ec63a9d81c7d5e3d436ef --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift @@ -0,0 +1,32 @@ +// Created by Laura Skelton on 5/11/17. +// Copyright © 2017 Airbnb. All rights reserved. + +/// A set of the minimum changes to get from one array of `DiffableSection`s to another, used for +/// diffing. +struct SectionedChangeset { + + // MARK: Lifecycle + + init( + sectionChangeset: IndexSetChangeset, + itemChangeset: IndexPathChangeset) + { + self.sectionChangeset = sectionChangeset + self.itemChangeset = itemChangeset + } + + // MARK: Internal + + /// A set of the minimum changes to get from one set of sections to another. + var sectionChangeset: IndexSetChangeset + + /// A set of the minimum changes to get from one set of items to another, aggregated across all + /// sections. + var itemChangeset: IndexPathChangeset + + /// Whether there are any inserts, deletes, moves, or updates in this changeset. + var isEmpty: Bool { + sectionChangeset.isEmpty && itemChangeset.isEmpty + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift new file mode 100644 index 0000000000000000000000000000000000000000..405c4d7f68baaf961a6367149b23c9eb885cbe16 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift @@ -0,0 +1,99 @@ +// Created by eric_horacek on 12/9/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +/// A shared logger that allows consumers to intercept Epoxy assertions and warning messages to pipe +/// into their own logging systems. +final class EpoxyLogger { + + // MARK: Lifecycle + + init( + assert: @escaping Assert = { condition, message, file, line in + // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`, + // the call will unexpectedly not respect the -O flag and will crash in release + // https://github.com/apple/swift/issues/60249 + Swift.assert(condition(), message(), file: file, line: line) + }, + assertionFailure: @escaping AssertionFailure = { message, file, line in + // If we default to `Swift.assertionFailure` directly with + // `assertionFailure: AssertionFailure = Swift.assertionFailure`, + // the call will unexpectedly not respect the -O flag and will crash in release + // https://github.com/apple/swift/issues/60249 + Swift.assertionFailure(message(), file: file, line: line) + }, + warn: @escaping Warn = { message, _, _ in + #if DEBUG + // swiftlint:disable:next no_direct_standard_out_logs + print(message()) + #endif + }) + { + _assert = assert + _assertionFailure = assertionFailure + _warn = warn + } + + // MARK: Internal + + /// Logs that an assertion occurred. + typealias Assert = ( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Logs that an assertion failure occurred. + typealias AssertionFailure = ( + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Logs a warning message. + typealias Warn = ( + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// The shared instance used to log Epoxy assertions and warnings. + /// + /// Set this to a new logger instance to intercept assertions and warnings logged by Epoxy. + static var shared = EpoxyLogger() + + /// Logs that an assertion occurred. + func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _assert(condition(), message(), fileID, line) + } + + /// Logs that an assertion failure occurred. + func assertionFailure( + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _assertionFailure(message(), fileID, line) + } + + /// Logs a warning message. + func warn( + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _warn(message(), fileID, line) + } + + // MARK: Private + + private let _assert: Assert + private let _assertionFailure: AssertionFailure + private let _warn: Warn + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift new file mode 100644 index 0000000000000000000000000000000000000000..7a6ecbc07043bcd698b692450b65d128c2519a4c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift @@ -0,0 +1,8 @@ +// Created by eric_horacek on 12/15/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +/// An Epoxy model with an associated context type that's passed into callback closures. +protocol CallbackContextEpoxyModeled: EpoxyModeled { + /// A context type that's passed into callback closures. + associatedtype CallbackContext +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift new file mode 100644 index 0000000000000000000000000000000000000000..a846ba27eb1e41adc4fadb2cd91d120b9682be84 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift @@ -0,0 +1,48 @@ +// Created by eric_horacek on 3/15/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +/// A generic result builder that enables a DSL for building arrays of Epoxy models. +@resultBuilder +enum EpoxyModelArrayBuilder { + typealias Expression = Model + typealias Component = [Model] + + static func buildExpression(_ expression: Expression) -> Component { + [expression] + } + + static func buildExpression(_ expression: Component) -> Component { + expression + } + + static func buildExpression(_ expression: Expression?) -> Component { + if let expression { + return [expression] + } + return [] + } + + static func buildBlock(_ children: Component...) -> Component { + children.flatMap { $0 } + } + + static func buildBlock(_ component: Component) -> Component { + component + } + + static func buildOptional(_ children: Component?) -> Component { + children ?? [] + } + + static func buildEither(first child: Component) -> Component { + child + } + + static func buildEither(second child: Component) -> Component { + child + } + + static func buildArray(_ components: [Component]) -> Component { + components.flatMap { $0 } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift new file mode 100644 index 0000000000000000000000000000000000000000..a7905cb58858c446ee94d697867b5c59810632e1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift @@ -0,0 +1,158 @@ +// Created by eric_horacek on 11/18/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - EpoxyModelProperty + +/// A property that can be stored in any concrete `EpoxyModeled` type. +/// +/// Custom model properties can be declared in any module. It's recommended that properties are +/// declared as `var`s in extensions to `EpoxyModeled` with a `*Property` suffix. +/// +/// For example, to declare a `EpoxyModelProperty` that fulfills the `TitleProviding` protocol: +/// +/// ```` +/// internal protocol TitleProviding { +/// var title: String? { get } +/// } +/// +/// extension EpoxyModeled where Self: TitleProviding { +/// internal var title: String? { +/// get { self[titleProperty] } +/// set { self[titleProperty] = newValue } +/// } +/// +/// internal func title(_ value: String?) -> Self { +/// copy(updating: titleProperty, to: value) +/// } +/// +/// private var titleProperty: EpoxyModelProperty { +/// .init(keyPath: \TitleProviding.title, defaultValue: nil, updateStrategy: .replace) +/// } +/// } +/// ```` +struct EpoxyModelProperty { + + // MARK: Lifecycle + + /// Creates a property identified by a `KeyPath` to its provided `value` and with its default + /// value if not customized in content by consumers. + /// + /// The `updateStrategy` is used to update the value when updating from an old value to a new + /// value. + init( + keyPath: KeyPath, + defaultValue: @escaping @autoclosure () -> Value, + updateStrategy: UpdateStrategy) + { + self.keyPath = keyPath + self.defaultValue = defaultValue + self.updateStrategy = updateStrategy + } + + // MARK: Internal + + /// The `KeyPath` that uniquely identifies this property. + let keyPath: AnyKeyPath + + /// A closure that produces the default property value when called. + let defaultValue: () -> Value + + /// A closure used to update an `EpoxyModelProperty` from an old value to a new value. + let updateStrategy: UpdateStrategy + +} + +// MARK: EpoxyModelProperty.UpdateStrategy + +extension EpoxyModelProperty { + /// A closure used to update an `EpoxyModelProperty` from an old value to a new value. + struct UpdateStrategy { + + // MARK: Lifecycle + + init(update: @escaping (Value, Value) -> Value) { + self.update = update + } + + // MARK: Public + + /// A closure used to update an `EpoxyModelProperty` from an old value to a new value. + var update: (_ old: Value, _ new: Value) -> Value + } +} + +// MARK: Defaults + +extension EpoxyModelProperty.UpdateStrategy { + /// Replaces the old value with the new value when an update occurs. + static var replace: Self { + .init { _, new in new } + } + + /// Chains the new closure value onto the old closure value, returning a new closure that first + /// calls the old closure and then subsequently calls the new closure. + static func chain() -> EpoxyModelProperty<(() -> Void)?>.UpdateStrategy { + .init { old, new in + guard let new else { return old } + guard let old else { return new } + return { + old() + new() + } + } + } + + /// Chains the new closure value onto the old closure value, returning a new closure that first + /// calls the old closure and then subsequently calls the new closure. + static func chain() -> EpoxyModelProperty<((A) -> Void)?>.UpdateStrategy { + .init { old, new in + guard let new else { return old } + guard let old else { return new } + return { a in + old(a) + new(a) + } + } + } + + /// Chains the new closure value onto the old closure value, returning a new closure that first + /// calls the old closure and then subsequently calls the new closure. + static func chain() -> EpoxyModelProperty<((A, B) -> Void)?>.UpdateStrategy { + .init { old, new in + guard let new else { return old } + guard let old else { return new } + return { a, b in + old(a, b) + new(a, b) + } + } + } + + /// Chains the new closure value onto the old closure value, returning a new closure that first + /// calls the old closure and then subsequently calls the new closure. + static func chain() -> EpoxyModelProperty<((A, B, C) -> Void)?>.UpdateStrategy { + .init { old, new in + guard let new else { return old } + guard let old else { return new } + return { a, b, c in + old(a, b, c) + new(a, b, c) + } + } + } + + /// Chains the new closure value onto the old closure value, returning a new closure that first + /// calls the old closure and then subsequently calls the new closure. + static func chain() -> EpoxyModelProperty<((A, B, C, D) -> Void)?>.UpdateStrategy { + .init { old, new in + guard let new else { return old } + guard let old else { return new } + return { a, b, c, d in + old(a, b, c, d) + new(a, b, c, d) + } + } + } + + // Add more arities as needed +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift new file mode 100644 index 0000000000000000000000000000000000000000..e6377cba7dfb71c912e498846da2896024ed091f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift @@ -0,0 +1,88 @@ +// Created by eric_horacek on 11/18/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - EpoxyModelStorage + +/// The underlying storage for an `EpoxyModeled` model that is capable of storing any +/// `EpoxyModelProperty`. +/// +/// Supports being extended with additional storage capabilities in other modules and conditionally +/// based on the provider capabilities that the content containing this storage conforms to. +struct EpoxyModelStorage { + + // MARK: Lifecycle + + init() { } + + // MARK: Internal + + /// Stores or retrieves the value of the specified property. + subscript(property: EpoxyModelProperty) -> Property { + get { + guard let propertyStorage = storage[property.keyPath] else { + return property.defaultValue() + } + + // This cast will never fail as the storage is only settable via this subscript and the + // `KeyPath` key is unique for any provider and value type pair. + // swiftlint:disable:next force_cast + return propertyStorage.value as! Property + } + set { + // We first update the value without using the `updateStrategy` since the likely scenario + // is that there won't be a collision that requires the `updateStrategy`, and we'll be able to + // return without incurring the cost of another write. + let propertyStorage = PropertyStorage(value: newValue, property: property) + + guard var replaced = storage.updateValue(propertyStorage, forKey: property.keyPath) else { + return + } + + // This cast will never fail as the storage is only settable via this subscript and the + // `KeyPath` key is unique for any provider and value type pair. + // swiftlint:disable:next force_cast + replaced.value = property.updateStrategy.update(replaced.value as! Property, newValue) + + storage[property.keyPath] = replaced + } + } + + /// Merges the given storage into this storage. + /// + /// In the case of a collision, the `UpdateStrategy` of the property is used to determine the + /// resulting value in this storage. + mutating func merge(_ other: Self) { + for (key, otherValue) in other.storage { + // We first update the value without using the `updateStrategy` since the likely scenario + // is that there won't be a collision that requires the `updateStrategy`, and we'll be able to + // return without incurring the cost of another write. + guard var replaced = storage.updateValue(otherValue, forKey: key) else { + continue + } + + replaced.value = replaced.property.update(old: replaced.value, new: otherValue.value) + + storage[key] = replaced + } + } + + // MARK: Private + + /// The underlying storage for the properties, with a key of the `EpoxyModelProperty.keyPath` and + /// a value of the property's `PropertyStorage`. + /// + /// Does not include default values. + private var storage = [AnyKeyPath: PropertyStorage]() + +} + +// MARK: - PropertyStorage + +/// A value stored within an `EpoxyModelStorage`. +private struct PropertyStorage { + /// The type-erased value of the `EpoxyModelProperty`. + var value: Any + + /// The property's corresponding `EpoxyModelProperty`, erased to an `AnyEpoxyModelProperty`. + var property: AnyEpoxyModelProperty +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift new file mode 100644 index 0000000000000000000000000000000000000000..98e6787d5d71edb3cc3756f6f0f5d198508731a4 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift @@ -0,0 +1,54 @@ +// Created by eric_horacek on 11/18/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - EpoxyModeled + +/// A protocol that all concrete Epoxy declarative UI model types conform to. +/// +/// This protocol should be conditionally extended to fulfill provider protocols and with chainable +/// setters for those providers that concrete model types can receive by declaring conformance to +/// provider protocols. +protocol EpoxyModeled { + /// The underlying storage of this model that stores the current property values. + var storage: EpoxyModelStorage { get set } +} + +// MARK: Extensions + +extension EpoxyModeled { + /// Stores or retrieves a value of the specified property in `storage`. + /// + /// If the value was set previously for the given `property`, the conflict is resolved using the + /// `EpoxyModelProperty.UpdateStrategy` of the `property`. + subscript(property: EpoxyModelProperty) -> Property { + get { storage[property] } + set { storage[property] = newValue } + } + + /// Returns a copy of this model with the given property updated to the provided value. + /// + /// Typically called from within the context of a chainable setter to allow fluent setting of a + /// property, e.g.: + /// + /// ```` + /// internal func title(_ value: String?) -> Self { + /// copy(updating: titleProperty, to: value) + /// } + /// ```` + /// + /// If a `value` was set previously for the given `property`, the conflict is resolved using the + /// `EpoxyModelProperty.UpdateStrategy` of the `property`. + func copy(updating property: EpoxyModelProperty, to value: Value) -> Self { + var copy = self + copy.storage[property] = value + return copy + } + + /// Returns a copy of this model produced by merging the given `other` model's storage into this + /// model's storage. + func merging(_ other: EpoxyModeled) -> Self { + var copy = self + copy.storage.merge(other.storage) + return copy + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift new file mode 100644 index 0000000000000000000000000000000000000000..f5fb10abd673737378328ccb419faf9a8a7e576f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift @@ -0,0 +1,29 @@ +// Created by eric_horacek on 12/1/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - AnyEpoxyModelProperty + +/// An erased `EpoxyModelProperty`, with the ability to call the `UpdateStrategy` even when the type +/// has been erased. +protocol AnyEpoxyModelProperty { + /// Returns the updated property from updating from given old to new property. + func update(old: Any, new: Any) -> Any +} + +// MARK: - EpoxyModelProperty + AnyEpoxyModelProperty + +extension EpoxyModelProperty: AnyEpoxyModelProperty { + func update(old: Any, new: Any) -> Any { + guard let typedOld = old as? Value else { + EpoxyLogger.shared.assertionFailure( + "Expected old to be of type \(Value.self), instead found \(old). This is programmer error.") + return defaultValue() + } + guard let typedNew = new as? Value else { + EpoxyLogger.shared.assertionFailure( + "Expected new to be of type \(Value.self), instead found \(old). This is programmer error.") + return defaultValue() + } + return updateStrategy.update(typedOld, typedNew) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift new file mode 100644 index 0000000000000000000000000000000000000000..e3edc246c3f9ace5288d3d3277527cae1364bf44 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift @@ -0,0 +1,39 @@ +// Created by Cal Stephens on 10/15/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +// MARK: - ClassReference + +/// A `Hashable` value wrapper around an `AnyClass` value +/// - Unlike `ObjectIdentifier(class)`, `ClassReference(class)` +/// preserves the `AnyClass` value and is more human-readable. +struct ClassReference { + init(_ class: AnyClass) { + self.class = `class` + } + + let `class`: AnyClass +} + +// MARK: Equatable + +extension ClassReference: Equatable { + static func ==(_ lhs: Self, _ rhs: Self) -> Bool { + ObjectIdentifier(lhs.class) == ObjectIdentifier(rhs.class) + } +} + +// MARK: Hashable + +extension ClassReference: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(`class`)) + } +} + +// MARK: CustomStringConvertible + +extension ClassReference: CustomStringConvertible { + var description: String { + String(describing: `class`) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..1862b2515b1a6eee3358777b871c5e41d5020db1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift @@ -0,0 +1,10 @@ +// Created by eric_horacek on 12/16/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +/// The capability of providing a flag indicating whether an operation should be animated. +/// +/// Typically conformed to by the `CallbackContext` of a `CallbackContextEpoxyModeled`. +protocol AnimatedProviding { + /// Whether this operation should be animated. + var animated: Bool { get } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..bf82156db7cf210d683587bd0fc27e1625e18bfd --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift @@ -0,0 +1,57 @@ +// Created by eric_horacek on 12/1/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - DataIDProviding + +/// The capability of providing a stable data identifier with an erased type. +/// +/// While it has similar semantics, this type cannot inherit from `Identifiable` as this would give +/// it an associated type, which would cause the `keyPath` used in its `EpoxyModelProperty` to not +/// be stable across types if written as `\Self.dataID` since the `KeyPath` `Root` would be +/// different for each type. +/// +/// - SeeAlso: `Identifiable`. +protocol DataIDProviding { + /// A stable identifier that uniquely identifies this instance, with its typed erased. + /// + /// Defaults to `DefaultDataID.noneProvided` if no data ID is provided. + var dataID: AnyHashable { get } +} + +// MARK: - EpoxyModeled + +extension EpoxyModeled where Self: DataIDProviding { + + // MARK: Internal + + /// A stable identifier that uniquely identifies this model, with its typed erased. + var dataID: AnyHashable { + get { self[dataIDProperty] } + set { self[dataIDProperty] = newValue } + } + + /// Returns a copy of this model with the ID replaced with the provided ID. + func dataID(_ value: AnyHashable) -> Self { + copy(updating: dataIDProperty, to: value) + } + + // MARK: Private + + private var dataIDProperty: EpoxyModelProperty { + EpoxyModelProperty( + keyPath: \DataIDProviding.dataID, + defaultValue: DefaultDataID.noneProvided, + updateStrategy: .replace) + } +} + +// MARK: - DefaultDataID + +/// The default data ID when none is provided. +enum DefaultDataID: Hashable, CustomDebugStringConvertible { + case noneProvided + + var debugDescription: String { + "DefaultDataID.noneProvided" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..5a2f2cbb57e85548dba49815df9930f0165a9f22 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift @@ -0,0 +1,41 @@ +// Created by eric_horacek on 1/6/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +// MARK: - DidDisplayProviding + +/// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `didDisplay` +/// closure property. +/// +/// - SeeAlso: `WillDisplayProviding` +/// - SeeAlso: `DidEndDisplayingProviding` +protocol DidDisplayProviding { } + +// MARK: - CallbackContextEpoxyModeled + +extension CallbackContextEpoxyModeled where Self: DidDisplayProviding { + + // MARK: Internal + + /// A closure that's called after a view has been added to the view hierarchy following any + /// appearance animations. + typealias DidDisplay = (_ context: CallbackContext) -> Void + + /// A closure that's called after the view has been added to the view hierarchy following any + /// appearance animations. + var didDisplay: DidDisplay? { + get { self[didDisplayProperty] } + set { self[didDisplayProperty] = newValue } + } + + /// Returns a copy of this model with the given did display closure called after the current did + /// display closure of this model, if there is one. + func didDisplay(_ value: DidDisplay?) -> Self { + copy(updating: didDisplayProperty, to: value) + } + + // MARK: Private + + private var didDisplayProperty: EpoxyModelProperty { + .init(keyPath: \Self.didDisplay, defaultValue: nil, updateStrategy: .chain()) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..db71be6dc2964f65b76f17a58681ba4f540fc868 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift @@ -0,0 +1,41 @@ +// Created by eric_horacek on 12/15/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - DidEndDisplayingProviding + +/// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a +/// `didEndDisplaying` closure property. +protocol DidEndDisplayingProviding { } + +// MARK: - CallbackContextEpoxyModeled + +extension CallbackContextEpoxyModeled where Self: DidEndDisplayingProviding { + + // MARK: Internal + + /// A closure that's called when a view is no longer displayed following any disappearance + /// animations and when it has been removed from the view hierarchy. + typealias DidEndDisplaying = (_ context: CallbackContext) -> Void + + /// A closure that's called when the view is no longer displayed following any disappearance + /// animations and when it has been removed from the view hierarchy. + var didEndDisplaying: DidEndDisplaying? { + get { self[didEndDisplayingProperty] } + set { self[didEndDisplayingProperty] = newValue } + } + + /// Returns a copy of this model with the given did end displaying closure called after the + /// current did end displaying closure of this model, if there is one. + func didEndDisplaying(_ value: DidEndDisplaying?) -> Self { + copy(updating: didEndDisplayingProperty, to: value) + } + + // MARK: Private + + private var didEndDisplayingProperty: EpoxyModelProperty { + .init( + keyPath: \Self.didEndDisplaying, + defaultValue: nil, + updateStrategy: .chain()) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..007e0244e338bccc960d160132ac35ca18972b11 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift @@ -0,0 +1,36 @@ +// Created by eric_horacek on 12/2/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - DidSelectProviding + +/// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `didSelect` +/// closure property. +protocol DidSelectProviding { } + +// MARK: - CallbackContextEpoxyModeled + +extension CallbackContextEpoxyModeled where Self: DidSelectProviding { + + // MARK: Internal + + /// A closure that's called to handle this model's view being selected. + typealias DidSelect = (CallbackContext) -> Void + + /// A closure that's called to handle this model's view being selected. + var didSelect: DidSelect? { + get { self[didSelectProperty] } + set { self[didSelectProperty] = newValue } + } + + /// Returns a copy of this model with the given did select closure called after the current did + /// select closure of this model, if there is one. + func didSelect(_ value: DidSelect?) -> Self { + copy(updating: didSelectProperty, to: value) + } + + // MARK: Private + + private var didSelectProperty: EpoxyModelProperty { + .init(keyPath: \Self.didSelect, defaultValue: nil, updateStrategy: .chain()) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..556c3b8cbf00238681b26a65dd6ee79463a62454 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift @@ -0,0 +1,49 @@ +// Created by eric_horacek on 12/2/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - ErasedContentProviding + +/// The capability of providing an type-erased `Equatable` content instance. +protocol ErasedContentProviding { + /// The type-erased content instance of this model, else `nil` if there is no content. + /// + /// If there was an `AnyEquatable` type, we could store this property using it. Instead we need + /// need to store `isErasedContentEqual` to determine equality. + var erasedContent: Any? { get } + + /// A closure that can be called to determine whether the given `model`'s `erasedContent` is equal + /// to this model's `erasedContent`, else `nil` if there is no content or the content is always + /// equal. + var isErasedContentEqual: ((Self) -> Bool)? { get } +} + +// MARK: - EpoxyModeled + +extension EpoxyModeled where Self: ErasedContentProviding { + + // MARK: Internal + + /// The type-erased content instance of this model, else `nil` if there is no content. + var erasedContent: Any? { + get { self[contentProperty] } + set { self[contentProperty] = newValue } + } + + /// A closure that can be called to determine whether the given `model`'s `erasedContent` is equal + /// to this model's `erasedContent`, else `nil` if there is no content or the content is always + /// equal. + var isErasedContentEqual: ((Self) -> Bool)? { + get { self[isContentEqualProperty] } + set { self[isContentEqualProperty] = newValue } + } + + // MARK: Private + + private var contentProperty: EpoxyModelProperty { + .init(keyPath: \Self.erasedContent, defaultValue: nil, updateStrategy: .replace) + } + + private var isContentEqualProperty: EpoxyModelProperty<((Self) -> Bool)?> { + .init(keyPath: \Self.isErasedContentEqual, defaultValue: nil, updateStrategy: .replace) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..0b4301f4e09ef2a74cf1c353b5a1dfc891ac4f4c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift @@ -0,0 +1,60 @@ +// Created by eric_horacek on 12/1/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - MakeViewProviding + +/// The capability of constructing a `UIView`. +protocol MakeViewProviding { + /// The view constructed when the `MakeView` closure is called. + associatedtype View: ViewType + + /// A closure that's called to construct an instance of `View`. + typealias MakeView = () -> View + + /// A closure that's called to construct an instance of `View`. + var makeView: MakeView { get } +} + +// MARK: - ViewEpoxyModeled + +extension ViewEpoxyModeled where Self: MakeViewProviding { + + // MARK: Internal + + /// A closure that's called to construct an instance of `View` represented by this model. + var makeView: MakeView { + get { self[makeViewProperty] } + set { self[makeViewProperty] = newValue } + } + + /// Replaces the default closure to construct the view with the given closure. + func makeView(_ value: @escaping MakeView) -> Self { + copy(updating: makeViewProperty, to: value) + } + + // MARK: Private + + private var makeViewProperty: EpoxyModelProperty { + // If you're getting a `EXC_BAD_INSTRUCTION` crash with this property in your stack trace, you + // probably either: + // - Conformed a view to `EpoxyableView` / `StyledView` with a custom initializer that + // takes parameters, or: + // - Used the `EpoxyModeled.init(dataID:)` initializer on a view has required initializer + // parameters. + // If you have parameters to view initialization, they should either be passed to `init(style:)` + // or you should provide a `makeView` closure when constructing your view's corresponding model, + // e.g: + // ``` + // MyView.itemModel(…) + // .makeView { MyView(customParameter: …) } + // .styleID(…) + // ``` + // Note that with the above approach that you must supply an `styleID` with the same identity as + // your view parameters to ensure that views with different parameters are not reused in place + // of one another. + .init( + keyPath: \Self.makeView, + defaultValue: View.init, + updateStrategy: .replace) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..a73de043de623d72a035ca2a04f7383d027485f1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift @@ -0,0 +1,38 @@ +// Created by eric_horacek on 12/2/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - SetBehaviorsProviding + +/// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `setBehaviors` +/// closure property. +protocol SetBehaviorsProviding { } + +// MARK: - CallbackContextEpoxyModeled + +extension CallbackContextEpoxyModeled where Self: SetBehaviorsProviding { + + // MARK: Internal + + /// A closure that's called to set the content on this model's view with behaviors (e.g. tap handler + /// closures) whenever this model is updated. + typealias SetBehaviors = (CallbackContext) -> Void + + /// A closure that's called to set the content on this model's view with behaviors (e.g. tap handler + /// closures) whenever this model is updated. + var setBehaviors: SetBehaviors? { + get { self[setBehaviorsProperty] } + set { self[setBehaviorsProperty] = newValue } + } + + /// Returns a copy of this model with the set behaviors closure called after the current set + /// behaviors closure of this model, if there is one. + func setBehaviors(_ value: SetBehaviors?) -> Self { + copy(updating: setBehaviorsProperty, to: value) + } + + // MARK: Private + + private var setBehaviorsProperty: EpoxyModelProperty { + .init(keyPath: \Self.setBehaviors, defaultValue: nil, updateStrategy: .chain()) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..e1456dceb480f6836408940025cd23fb22efa5b8 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift @@ -0,0 +1,38 @@ +// Created by eric_horacek on 12/1/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - SetContentProviding + +/// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `setContent` +/// closure property. +protocol SetContentProviding { } + +// MARK: - CallbackContextEpoxyModeled + +extension CallbackContextEpoxyModeled where Self: SetContentProviding { + + // MARK: Internal + + /// A closure that's called to set the content on this model's view when it is first created and + /// subsequently when the content changes. + typealias SetContent = (CallbackContext) -> Void + + /// A closure that's called to set the content on this model's view when it is first created and + /// subsequently when the content changes. + var setContent: SetContent? { + get { self[setContentProperty] } + set { self[setContentProperty] = newValue } + } + + /// Returns a copy of this model with the given setContent view closure called after the current + /// setContent view closure of this model, if there is one. + func setContent(_ value: SetContent?) -> Self { + copy(updating: setContentProperty, to: value) + } + + // MARK: Private + + private var setContentProperty: EpoxyModelProperty { + .init(keyPath: \Self.setContent, defaultValue: nil, updateStrategy: .chain()) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..8242de12d48bdb2bab7a77c91f1d6583d9b2bd67 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift @@ -0,0 +1,37 @@ +// Created by eric_horacek on 12/1/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - StyleIDProviding + +protocol StyleIDProviding { + /// An optional ID for a style type to use for reuse of a view. + /// + /// Use this to differentiate between different styling configurations. + var styleID: AnyHashable? { get } +} + +// MARK: - EpoxyModeled + +extension EpoxyModeled where Self: StyleIDProviding { + + // MARK: Internal + + var styleID: AnyHashable? { + get { self[styleIDProperty] } + set { self[styleIDProperty] = newValue } + } + + /// Returns a copy of this model with the `styleID` replaced with the provided `value`. + func styleID(_ value: AnyHashable?) -> Self { + copy(updating: styleIDProperty, to: value) + } + + // MARK: Private + + private var styleIDProperty: EpoxyModelProperty { + .init( + keyPath: \StyleIDProviding.styleID, + defaultValue: nil, + updateStrategy: .replace) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..e41ac7906ddd47507393ce2274de05daef9b747f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift @@ -0,0 +1,14 @@ +// Created by eric_horacek on 12/16/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +#if !os(macOS) +import UIKit + +/// The capability of providing a `UITraitCollection` instance. +/// +/// Typically conformed to by the `CallbackContext` of a `CallbackContextEpoxyModeled`. +protocol TraitCollectionProviding { + /// The `UITraitCollection` instance provided by this type. + var traitCollection: UITraitCollection { get } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..45c0130e8755da013db7ee1d0a039c25c8c5bc40 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift @@ -0,0 +1,34 @@ +// Created by Bryan Keller on 12/17/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - ViewDifferentiatorProviding + +/// The capability of providing a view differentiator that facilitates generating collection view +/// cell reuse identifiers. +protocol ViewDifferentiatorProviding { + /// The view differentiator for the item model. + var viewDifferentiator: ViewDifferentiator { get } +} + +// MARK: - ViewDifferentiator + +/// Facilitates differentiating between two models' views, based on their view type, optional style +/// identifier, and optional element kind for supplementary view models. If two models have the same +/// view differentiator, then they're compatible with one another for element reuse. If two models +/// have different view differentiators, then they're incompatible with one another for element +/// reuse. +struct ViewDifferentiator: Hashable { + + // MARK: Lifecycle + + init(viewType: AnyClass, styleID: AnyHashable?) { + viewTypeDescription = "\(type(of: viewType.self))" + self.styleID = styleID + } + + // MARK: Public + + var viewTypeDescription: String + var styleID: AnyHashable? + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..b36eed8cbe7ac2d5d9ccb8bcf00eb51d2d15e267 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift @@ -0,0 +1,13 @@ +// Created by eric_horacek on 12/16/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +/// The capability of providing an `View` instance +/// +/// Typically conformed to by the `CallbackContext` of a `CallbackContextEpoxyModeled`. +protocol ViewProviding { + /// The `UIView` view of this type. + associatedtype View: ViewType + + /// The `UIView` view instance provided by this type. + var view: View { get } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift new file mode 100644 index 0000000000000000000000000000000000000000..9bf2163024c7e7c77da364b71422b6540eaed642 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift @@ -0,0 +1,41 @@ +// Created by eric_horacek on 12/15/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - WillDisplayProviding + +/// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `willDisplay` +/// closure property. +/// +/// - SeeAlso: `DidDisplayProviding` +/// - SeeAlso: `DidEndDisplayingProviding` +protocol WillDisplayProviding { } + +// MARK: - CallbackContextEpoxyModeled + +extension CallbackContextEpoxyModeled where Self: WillDisplayProviding { + + // MARK: Internal + + /// A closure that's called when a view is about to be displayed, before it has been added to the + /// view hierarcy. + typealias WillDisplay = (_ context: CallbackContext) -> Void + + /// A closure that's called when the view is about to be displayed, before it has been added to + /// the view hierarcy. + var willDisplay: WillDisplay? { + get { self[willDisplayProperty] } + set { self[willDisplayProperty] = newValue } + } + + /// Returns a copy of this model with the given will display closure called after the current will + /// display closure of this model, if there is one. + func willDisplay(_ value: WillDisplay?) -> Self { + copy(updating: willDisplayProperty, to: value) + } + + // MARK: Private + + private var willDisplayProperty: EpoxyModelProperty { + .init(keyPath: \Self.willDisplay, defaultValue: nil, updateStrategy: .chain()) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift new file mode 100644 index 0000000000000000000000000000000000000000..27901b28fb70a437dfb56aae76f6113ca8d88181 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift @@ -0,0 +1,10 @@ +// Created by eric_horacek on 12/4/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +/// An Epoxy model with an associated `UIView` type. +protocol ViewEpoxyModeled: EpoxyModeled { + /// The view type associated with this model. + /// + /// An instance of this view is typically configured by this model. + associatedtype View: ViewType +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/README.md b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b8cfab17f3a660837c83c011c5b8263ed2adc33f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/README.md @@ -0,0 +1,30 @@ +## EpoxyCore + +This directory includes the source code of the EpoxyCore library, from the following release: +https://github.com/airbnb/epoxy-ios/releases/tag/0.10.0 + +Lottie is distributed via multiple package managers (SPM, Cocoapods, Carthage, and NPM), +each with different packaging and compilation requirements. + +Due to limitations of these package managers, we can't depend on / import +a separate EpoxyCore module / library. Instead, we include the source +directly within the Lottie library and compile everything as a single unit. + +### Update instructions + +From time to time we may need to update to a more recent version of EpoxyCore. +When doing this, follow these steps: + + 1. Download the latest release from https://github.com/epoxy-ios/EpoxyCore + and replace the source code in this directory with the updated code. + + 2. Update the URL at the top of this file to indicate what release is being used. + + 3. Change all of the `public` symbols defined in this module to instead be `internal` + to prevent Lottie from exposing any EpoxyCore APIs. + + 4. Namespace any types that conflict with other existing types. + For example, the EpoxyCore `Entry` type conflicts with the ZipFoundation `Entry` type, + so the EpoxyCore type has been renamed to `EpoxyEntry`. + + 5. Delete `EpoxySwiftUIHostingController.swift` and `EpoxySwiftUIHostingView.swift`, which are not used by Lottie, and emit deprecation warnings when building for visionOS. diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift new file mode 100644 index 0000000000000000000000000000000000000000..0b28e0b1c200acb220675f669eb6cbd4642b1da0 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift @@ -0,0 +1,45 @@ +// Created by matthew_cheok on 11/19/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - EpoxyIntrinsicContentSizeInvalidator + +/// Allows the SwiftUI view contained in an Epoxy model to request the invalidation of +/// the container's intrinsic content size. +/// +/// ``` +/// @Environment(\.epoxyIntrinsicContentSizeInvalidator) var invalidateIntrinsicContentSize +/// +/// var body: some View { +/// ... +/// .onChange(of: size) { +/// invalidateIntrinsicContentSize() +/// } +/// } +/// ``` +struct EpoxyIntrinsicContentSizeInvalidator { + let invalidate: () -> Void + + func callAsFunction() { + invalidate() + } +} + +// MARK: - EnvironmentValues + +extension EnvironmentValues { + /// A means of invalidating the intrinsic content size of the parent `EpoxySwiftUIHostingView`. + var epoxyIntrinsicContentSizeInvalidator: EpoxyIntrinsicContentSizeInvalidator { + get { self[EpoxyIntrinsicContentSizeInvalidatorKey.self] } + set { self[EpoxyIntrinsicContentSizeInvalidatorKey.self] = newValue } + } +} + +// MARK: - EpoxyIntrinsicContentSizeInvalidatorKey + +private struct EpoxyIntrinsicContentSizeInvalidatorKey: EnvironmentKey { + static let defaultValue = EpoxyIntrinsicContentSizeInvalidator(invalidate: { }) +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift new file mode 100644 index 0000000000000000000000000000000000000000..2e140944d2280813d5fd7ca70bd740770f26281a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift @@ -0,0 +1,49 @@ +// Created by eric_horacek on 10/8/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - View + +extension View { + /// Applies the layout margins from the parent `EpoxySwiftUIHostingView` to this `View`, if there + /// are any. + /// + /// Can be used to have a background in SwiftUI underlap the safe area within a bar installer, for + /// example. + /// + /// These margins are propagated via the `EnvironmentValues.epoxyLayoutMargins`. + func epoxyLayoutMargins() -> some View { + modifier(EpoxyLayoutMarginsPadding()) + } +} + +// MARK: - EnvironmentValues + +extension EnvironmentValues { + /// The layout margins of the parent `EpoxySwiftUIHostingView`, else zero if there is none. + var epoxyLayoutMargins: EdgeInsets { + get { self[EpoxyLayoutMarginsKey.self] } + set { self[EpoxyLayoutMarginsKey.self] = newValue } + } +} + +// MARK: - EpoxyLayoutMarginsKey + +private struct EpoxyLayoutMarginsKey: EnvironmentKey { + static let defaultValue = EdgeInsets() +} + +// MARK: - EpoxyLayoutMarginsPadding + +/// A view modifier that applies the layout margins from an enclosing `EpoxySwiftUIHostingView` to +/// the modified `View`. +private struct EpoxyLayoutMarginsPadding: ViewModifier { + @Environment(\.epoxyLayoutMargins) var epoxyLayoutMargins + + func body(content: Content) -> some View { + content.padding(epoxyLayoutMargins) + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift new file mode 100644 index 0000000000000000000000000000000000000000..be3b8a91f3ef5110e854791b2c8d4f9a538f2cc1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift @@ -0,0 +1,170 @@ +// Created by eric_horacek on 9/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - StyledView + +extension StyledView where Self: ContentConfigurableView & BehaviorsConfigurableView { + /// Returns a SwiftUI `View` representing this `EpoxyableView`. + /// + /// To perform additional configuration of the `EpoxyableView` instance, call `configure` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…) + /// .configure { context in + /// context.view.doSomething() + /// } + /// ``` + /// + /// To configure the sizing behavior of the `EpoxyableView` instance, call `sizing` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…).sizing(.intrinsicSize) + /// ``` + static func swiftUIView( + content: Content, + style: Style, + behaviors: Behaviors? = nil) + -> SwiftUIView + { + SwiftUIView(storage: (content: content, style: style)) { + let view = Self(style: style) + view.setContent(content, animated: false) + return view + } + .configure { context in + // We need to create a new view instance when the style changes. + if context.oldStorage.style != style { + context.view = Self(style: style) + context.view.setContent(content, animated: context.animated) + } + // Otherwise, if the just the content changes, we need to update it. + else if context.oldStorage.content != content { + context.view.setContent(content, animated: context.animated) + context.container.invalidateIntrinsicContentSize() + } + + context.view.setBehaviors(behaviors) + } + } +} + +extension StyledView + where + Self: ContentConfigurableView & BehaviorsConfigurableView, + Style == Never +{ + /// Returns a SwiftUI `View` representing this `EpoxyableView`. + /// + /// To perform additional configuration of the `EpoxyableView` instance, call `configure` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…) + /// .configure { context in + /// context.view.doSomething() + /// } + /// ``` + /// + /// To configure the sizing behavior of the `EpoxyableView` instance, call `sizing` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…).sizing(.intrinsicSize) + /// ``` + static func swiftUIView( + content: Content, + behaviors: Behaviors? = nil) + -> SwiftUIView + { + SwiftUIView(storage: content) { + let view = Self() + view.setContent(content, animated: false) + return view + } + .configure { context in + // We need to update the content of the existing view when the content is updated. + if context.oldStorage != content { + context.view.setContent(content, animated: context.animated) + context.container.invalidateIntrinsicContentSize() + } + + context.view.setBehaviors(behaviors) + } + } +} + +extension StyledView + where + Self: ContentConfigurableView & BehaviorsConfigurableView, + Content == Never +{ + /// Returns a SwiftUI `View` representing this `EpoxyableView`. + /// + /// To perform additional configuration of the `EpoxyableView` instance, call `configure` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…) + /// .configure { context in + /// context.view.doSomething() + /// } + /// ``` + /// + /// To configure the sizing behavior of the `EpoxyableView` instance, call `sizing` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…).sizing(.intrinsicSize) + /// ``` + /// The sizing defaults to `.automatic`. + static func swiftUIView( + style: Style, + behaviors: Behaviors? = nil) + -> SwiftUIView + { + SwiftUIView(storage: style) { + Self(style: style) + } + .configure { context in + // We need to create a new view instance when the style changes. + if context.oldStorage != style { + context.view = Self(style: style) + } + + context.view.setBehaviors(behaviors) + } + } +} + +extension StyledView + where + Self: ContentConfigurableView & BehaviorsConfigurableView, + Content == Never, + Style == Never +{ + /// Returns a SwiftUI `View` representing this `EpoxyableView`. + /// + /// To perform additional configuration of the `EpoxyableView` instance, call `configure` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…) + /// .configure { context in + /// context.view.doSomething() + /// } + /// ``` + /// + /// To configure the sizing behavior of the `EpoxyableView` instance, call `sizing` on the + /// returned SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…).sizing(.intrinsicSize) + /// ``` + /// The sizing defaults to `.automatic`. + static func swiftUIView(behaviors: Behaviors? = nil) -> SwiftUIView { + SwiftUIView { + Self() + } + .configure { context in + context.view.setBehaviors(behaviors) + } + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift new file mode 100644 index 0000000000000000000000000000000000000000..6111e22fde9a5ec872223a820c2cf6eb1976a60a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift @@ -0,0 +1,126 @@ +// Created by eric_horacek on 6/22/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - MeasuringViewRepresentable + +/// A `UIViewRepresentable` that uses a `SwiftUIMeasurementContainer` wrapping its represented +/// `UIView` to report its size that fits a proposed size to SwiftUI. +/// +/// Supports iOS 13-15 using the private `_overrideSizeThatFits(…)` method and iOS 16+ using the +/// `sizeThatFits(…)` method. +/// +/// - SeeAlso: ``SwiftUIMeasurementContainer`` +protocol MeasuringViewRepresentable: ViewRepresentableType + where + RepresentableViewType == SwiftUIMeasurementContainer +{ + /// The `UIView` content that's being measured by the enclosing `SwiftUIMeasurementContainer`. + associatedtype Content: ViewType + + /// The sizing strategy of the represented view. + /// + /// To configure the sizing behavior of the `View` instance, call `sizing` on this `View`, e.g.: + /// ``` + /// myView.sizing(.intrinsicSize) + /// ``` + var sizing: SwiftUIMeasurementContainerStrategy { get set } +} + +// MARK: Extensions + +extension MeasuringViewRepresentable { + /// Returns a copy of this view with its sizing strategy updated to the given `sizing` value. + func sizing(_ strategy: SwiftUIMeasurementContainerStrategy) -> Self { + var copy = self + copy.sizing = strategy + return copy + } +} + +// MARK: Defaults + +#if os(iOS) || os(tvOS) +extension MeasuringViewRepresentable { + func _overrideSizeThatFits( + _ size: inout CGSize, + in proposedSize: _ProposedSize, + uiView: UIViewType) + { + uiView.strategy = sizing + + // Note: this method is not double-called on iOS 16, so we don't need to do anything to prevent + // extra work here. + let children = Mirror(reflecting: proposedSize).children + + // Creates a `CGSize` by replacing `nil`s with `UIView.noIntrinsicMetric` + uiView.proposedSize = .init( + width: children.first { $0.label == "width" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric, + height: children.first { $0.label == "height" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric) + + size = uiView.measuredFittingSize + } + + #if swift(>=5.7) // Proxy check for being built with the iOS 15 SDK + @available(iOS 16.0, tvOS 16.0, macOS 13.0, *) + func sizeThatFits( + _ proposal: ProposedViewSize, + uiView: UIViewType, + context _: Context) + -> CGSize? + { + uiView.strategy = sizing + + // Creates a size by replacing `nil`s with `UIView.noIntrinsicMetric` + uiView.proposedSize = .init( + width: proposal.width ?? ViewType.noIntrinsicMetric, + height: proposal.height ?? ViewType.noIntrinsicMetric) + + return uiView.measuredFittingSize + } + #endif +} + +#elseif os(macOS) +extension MeasuringViewRepresentable { + func _overrideSizeThatFits( + _ size: inout CGSize, + in proposedSize: _ProposedSize, + nsView: NSViewType) + { + nsView.strategy = sizing + + let children = Mirror(reflecting: proposedSize).children + + // Creates a `CGSize` by replacing `nil`s with `UIView.noIntrinsicMetric` + nsView.proposedSize = .init( + width: children.first { $0.label == "width" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric, + height: children.first { $0.label == "height" }?.value as? CGFloat ?? ViewType.noIntrinsicMetric) + + size = nsView.measuredFittingSize + } + + // Proxy check for being built with the macOS 13 SDK. + #if swift(>=5.7.1) + @available(macOS 13.0, *) + func sizeThatFits( + _ proposal: ProposedViewSize, + nsView: NSViewType, + context _: Context) + -> CGSize? + { + nsView.strategy = sizing + + // Creates a size by replacing `nil`s with `UIView.noIntrinsicMetric` + nsView.proposedSize = .init( + width: proposal.width ?? ViewType.noIntrinsicMetric, + height: proposal.height ?? ViewType.noIntrinsicMetric) + + return nsView.measuredFittingSize + } + #endif +} +#endif +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift new file mode 100644 index 0000000000000000000000000000000000000000..cf39e22e9c59b9f157b1a95a471ab20269ad7ff2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift @@ -0,0 +1,460 @@ +// Created by Bryn Bodayle on 1/24/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - SwiftUIMeasurementContainer + +/// A view that has an `intrinsicContentSize` of the `uiView`'s `systemLayoutSizeFitting(…)` and +/// supports double layout pass sizing and content size category changes. +/// +/// This container view uses an injected proposed width to measure the view and return its ideal +/// height through the `SwiftUISizingContext` binding. +/// +/// - SeeAlso: ``MeasuringViewRepresentable`` +final class SwiftUIMeasurementContainer: ViewType { + + // MARK: Lifecycle + + init(content: Content, strategy: SwiftUIMeasurementContainerStrategy) { + self.content = content + self.strategy = strategy + + // On iOS 15 and below, passing zero can result in a constraint failure the first time a view + // is displayed, but the system gracefully recovers afterwards. On iOS 16, it's fine to pass + // zero. + let initialSize: CGSize = + if #available(iOS 16, tvOS 16, macOS 13, *) { + .zero + } else { + .init(width: 375, height: 150) + } + super.init(frame: .init(origin: .zero, size: initialSize)) + + addSubview(content) + setUpConstraints() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + /// The most recently measured fitting size of the `uiView` that fits within the current + /// `proposedSize`. + /// + /// Contains `proposedSize`/`bounds.size` fallbacks for dimensions with no intrinsic size, as + /// compared to `intrinsicContentSize` which has `UIView.noIntrinsicMetric` fields in the case of + /// no intrinsic size. + var measuredFittingSize: CGSize { + _measuredFittingSize ?? measureView() + } + + /// The `UIView` content that's being measured by this container. + var content: Content { + didSet { + guard content !== oldValue else { return } + oldValue.removeFromSuperview() + addSubview(content) + // Invalidate the strategy since it's derived from this view. + _resolvedStrategy = nil + // Re-configure the constraints since they depend on the resolved strategy. + setUpConstraints() + // Finally, we need to re-measure the view. + _measuredFittingSize = nil + } + } + + /// The proposed size at the time of the latest measurement. + /// + /// Has a side-effect of updating the `measuredIntrinsicContentSize` if it's changed. + var proposedSize = CGSize.noIntrinsicMetric { + didSet { + guard oldValue != proposedSize else { return } + // The proposed size is only used by the measurement, so just re-measure. + _measuredFittingSize = nil + } + } + + /// The measurement strategy of this container. + /// + /// Has a side-effect of updating the `measuredIntrinsicContentSize` if it's changed. + var strategy: SwiftUIMeasurementContainerStrategy { + didSet { + guard oldValue != strategy else { return } + // Invalidate the resolved strategy since it's derived from this strategy. + _resolvedStrategy = nil + // Then, re-measure the view. + _measuredFittingSize = nil + } + } + + override var intrinsicContentSize: CGSize { + _intrinsicContentSize + } + + #if os(macOS) + override func layout() { + super.layout() + + // We need to re-measure the view whenever the size of the bounds changes, as the previous size + // may now be incorrect. + if latestMeasurementBoundsSize != nil, bounds.size != latestMeasurementBoundsSize { + // This will trigger SwiftUI to re-measure the view. + super.invalidateIntrinsicContentSize() + } + } + #else + override func layoutSubviews() { + super.layoutSubviews() + + // We need to re-measure the view whenever the size of the bounds changes, as the previous size + // may now be incorrect. + if latestMeasurementBoundsSize != nil, bounds.size != latestMeasurementBoundsSize { + // This will trigger SwiftUI to re-measure the view. + super.invalidateIntrinsicContentSize() + } + } + #endif + + override func invalidateIntrinsicContentSize() { + super.invalidateIntrinsicContentSize() + + // Invalidate the resolved strategy in case it changes with the re-measurement as it relies on + // the intrinsic size. + _resolvedStrategy = nil + _measuredFittingSize = nil + } + + // MARK: Private + + /// The most recently measured intrinsic content size of the `uiView`, else `noIntrinsicMetric` if + /// it has not yet been measured. + /// + /// Contains `UIView.noIntrinsicMetric` fallbacks for dimensions with no intrinsic size, + /// as compared to `measuredFittingSize` which has `proposedSize`/`bounds.size` fallbacks. + private var _intrinsicContentSize = CGSize.noIntrinsicMetric + + /// The bounds size at the time of the latest measurement. + private var latestMeasurementBoundsSize: CGSize? + + /// The most recently updated set of constraints constraining `uiView` to `self`. + private var uiViewConstraints = [NSLayoutConstraint.Attribute: NSLayoutConstraint]() + + /// The cached `resolvedStrategy` to prevent unnecessary re-measurements. + private var _resolvedStrategy: ResolvedSwiftUIMeasurementContainerStrategy? + + /// The cached `measuredFittingSize` to prevent unnecessary re-measurements. + private var _measuredFittingSize: CGSize? + + /// The resolved measurement strategy. + private var resolvedStrategy: ResolvedSwiftUIMeasurementContainerStrategy { + if let resolvedStrategy = _resolvedStrategy { + return resolvedStrategy + } + + let resolved: ResolvedSwiftUIMeasurementContainerStrategy + switch strategy { + case .automatic: + // Perform an intrinsic size measurement pass, which gives us valid values for + // `UILabel.preferredMaxLayoutWidth`. + let intrinsicSize = content.systemLayoutFittingIntrinsicSize() + + // If the view has a intrinsic width and contains a double layout pass subview, give it the + // proposed width to allow the label content to gracefully wrap to multiple lines. + if intrinsicSize.width > 0, content.containsDoubleLayoutPassSubviews() { + resolved = .intrinsicHeightProposedWidth + } else { + let zero = CGFloat(0) + switch (width: intrinsicSize.width, height: intrinsicSize.height) { + case (width: ...zero, height: ...zero): + resolved = .proposed + case (width: ...zero, height: zero.nextUp...): + resolved = .intrinsicHeightProposedWidth + case (width: zero.nextUp..., height: ...zero): + resolved = .intrinsicWidthProposedHeight + default: + resolved = .intrinsic(intrinsicSize) + } + } + + case .proposed: + resolved = .proposed + + case .intrinsicHeightProposedWidth: + resolved = .intrinsicHeightProposedWidth + + case .intrinsicWidthProposedHeight: + resolved = .intrinsicWidthProposedHeight + + case .intrinsic: + resolved = .intrinsic(content.systemLayoutFittingIntrinsicSize()) + } + _resolvedStrategy = resolved + return resolved + } + + private func setUpConstraints() { + content.translatesAutoresizingMaskIntoConstraints = false + + let leading = content.leadingAnchor.constraint(equalTo: leadingAnchor) + let top = content.topAnchor.constraint(equalTo: topAnchor) + let trailing = content.trailingAnchor.constraint(equalTo: trailingAnchor) + let bottom = content.bottomAnchor.constraint(equalTo: bottomAnchor) + let newConstraints: [NSLayoutConstraint.Attribute: NSLayoutConstraint] = [ + .leading: leading, .top: top, .trailing: trailing, .bottom: bottom, + ] + // Start with the lowest priority constraints so we aren't measuring the view too early, the + // priorities will be updated later on. + prioritizeConstraints(newConstraints, strategy: .intrinsic(.zero)) + + NSLayoutConstraint.deactivate(Array(uiViewConstraints.values)) + uiViewConstraints = newConstraints + NSLayoutConstraint.activate(Array(uiViewConstraints.values)) + } + + /// Prioritizes the given constraints based on the provided resolved strategy. + private func prioritizeConstraints( + _ constraints: [NSLayoutConstraint.Attribute: NSLayoutConstraint], + strategy: ResolvedSwiftUIMeasurementContainerStrategy) + { + // Give a required constraint in the dimensions that are fixed to the bounds, otherwise almost + // required. + switch strategy { + case .proposed: + constraints[.trailing]?.priority = .required + constraints[.bottom]?.priority = .required + + case .intrinsicHeightProposedWidth: + constraints[.trailing]?.priority = .required + constraints[.bottom]?.priority = .almostRequired + + case .intrinsicWidthProposedHeight: + constraints[.trailing]?.priority = .almostRequired + constraints[.bottom]?.priority = .required + + case .intrinsic: + constraints[.trailing]?.priority = .almostRequired + constraints[.bottom]?.priority = .almostRequired + } + + #if os(macOS) + // On macOS, views default to having required constraints setting their height / width + // equal to their intrinsic content size. These have to be disabled in favor of the constraints + // we create here. + content.isVerticalContentSizeConstraintActive = false + content.isHorizontalContentSizeConstraintActive = false + #endif + } + + /// Measures the `uiView`, storing the resulting size in `measuredIntrinsicContentSize`. + private func measureView() -> CGSize { + latestMeasurementBoundsSize = bounds.size + prioritizeConstraints(uiViewConstraints, strategy: resolvedStrategy) + + var measuredSize: CGSize + let proposedSizeElseBounds = proposedSize.replacingNoIntrinsicMetric(with: bounds.size) + + switch resolvedStrategy { + case .proposed: + measuredSize = .noIntrinsicMetric + + case .intrinsicHeightProposedWidth: + measuredSize = content.systemLayoutFittingIntrinsicHeightFixedWidth(proposedSizeElseBounds.width) + measuredSize.width = ViewType.noIntrinsicMetric + + case .intrinsicWidthProposedHeight: + measuredSize = content.systemLayoutFittingIntrinsicWidthFixedHeight(proposedSizeElseBounds.height) + measuredSize.height = ViewType.noIntrinsicMetric + + case .intrinsic(let size): + measuredSize = size + + // If the measured size exceeds an available width or height, set the measured size to + // `noIntrinsicMetric` to ensure that the component can be compressed, otherwise it will + // overflow beyond the proposed size. + // - If the previous intrinsic content size is the same as the new proposed size, we don't + // do this as SwiftUI sometimes "proposes" the same intrinsic size back to the component and + // we don't want that scenario to prevent size changes when there is actually more space + // available. + if + proposedSize.width != ViewType.noIntrinsicMetric, + measuredSize.width > proposedSizeElseBounds.width, + _intrinsicContentSize.width != proposedSize.width + { + measuredSize.width = ViewType.noIntrinsicMetric + } + if + proposedSize.height != ViewType.noIntrinsicMetric, + measuredSize.height > proposedSizeElseBounds.height, + _intrinsicContentSize.height != proposedSize.height + { + measuredSize.height = ViewType.noIntrinsicMetric + } + } + + _intrinsicContentSize = measuredSize + + let measuredFittingSize = measuredSize.replacingNoIntrinsicMetric(with: proposedSizeElseBounds) + _measuredFittingSize = measuredFittingSize + return measuredFittingSize + } +} + +// MARK: - SwiftUIMeasurementContainerStrategy + +/// The measurement strategy of a `SwiftUIMeasurementContainer`. +enum SwiftUIMeasurementContainerStrategy { + /// The container makes a best effort to correctly choose the measurement strategy of the view. + /// + /// The best effort is based on a number of heuristics: + /// - The `uiView` will be given its intrinsic width and/or height when measurement in that + /// dimension produces a positive value, while zero/negative values will result in that + /// dimension receiving the available space proposed by the parent. + /// - If the view contains `UILabel` subviews that require a double layout pass as determined by + /// a `preferredMaxLayoutWidth` that's greater than zero after a layout, then the view will + /// default to `intrinsicHeightProposedWidth` to allow the labels to wrap. + /// + /// If you would like to opt out of automatic sizing for performance or to override the default + /// behavior, choose another strategy. + case automatic + + /// The `uiView` is sized to fill the area proposed by its parent. + /// + /// Typically used for views that should expand greedily in both axes, e.g. a background view. + case proposed + + /// The `uiView` is sized with its intrinsic height and expands horizontally to fill the width + /// proposed by its parent. + /// + /// Typically used for views that have a height that's a function of their width, e.g. a row with + /// text that can wrap to multiple lines. + case intrinsicHeightProposedWidth + + /// The `uiView` is sized with its intrinsic width and expands vertically to fill the height + /// proposed by its parent. + /// + /// Typically used for views that are free to grow vertically but have a fixed width, e.g. a view + /// in a horizontal carousel. + case intrinsicWidthProposedHeight + + /// The `uiView` is sized to its intrinsic width and height. + /// + /// Typically used for components with a specific intrinsic size in both axes, e.g. controls or + /// inputs. + case intrinsic +} + +// MARK: - ResolvedSwiftUIMeasurementContainerStrategy + +/// The resolved measurement strategy of a `SwiftUIMeasurementContainer`, matching the cases of the +/// `SwiftUIMeasurementContainerStrategy` without the automatic case. +private enum ResolvedSwiftUIMeasurementContainerStrategy { + case proposed, intrinsicHeightProposedWidth, intrinsicWidthProposedHeight, intrinsic(CGSize) +} + +// MARK: - UILayoutPriority + +extension LayoutPriorityType { + /// An "almost required" constraint, useful for creating near-required constraints that don't + /// error when unable to be satisfied. + @nonobjc + fileprivate static var almostRequired: LayoutPriorityType { .init(rawValue: required.rawValue - 1) } +} + +// MARK: - UIView + +extension ViewType { + /// The `systemLayoutSizeFitting(…)` of this view with a compressed size and fitting priorities. + @nonobjc + fileprivate func systemLayoutFittingIntrinsicSize() -> CGSize { + #if os(macOS) + intrinsicContentSize + #else + systemLayoutSizeFitting( + UIView.layoutFittingCompressedSize, + withHorizontalFittingPriority: .fittingSizeLevel, + verticalFittingPriority: .fittingSizeLevel) + #endif + } + + /// The `systemLayoutSizeFitting(…)` of this view with a compressed height with a fitting size + /// priority and with the given fixed width and fitting priority. + @nonobjc + fileprivate func systemLayoutFittingIntrinsicHeightFixedWidth( + _ width: CGFloat, + priority: LayoutPriorityType = .almostRequired) + -> CGSize + { + #if os(macOS) + return CGSize(width: width, height: intrinsicContentSize.height) + #else + let targetSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height) + + return systemLayoutSizeFitting( + targetSize, + withHorizontalFittingPriority: priority, + verticalFittingPriority: .fittingSizeLevel) + #endif + } + + /// The `systemLayoutSizeFitting(…)` of this view with a compressed width with a fitting size + /// priority and with the given fixed height and fitting priority. + @nonobjc + fileprivate func systemLayoutFittingIntrinsicWidthFixedHeight( + _ height: CGFloat, + priority: LayoutPriorityType = .almostRequired) + -> CGSize + { + #if os(macOS) + return CGSize(width: intrinsicContentSize.width, height: height) + #else + let targetSize = CGSize(width: UIView.layoutFittingCompressedSize.width, height: height) + + return systemLayoutSizeFitting( + targetSize, + withHorizontalFittingPriority: .fittingSizeLevel, + verticalFittingPriority: priority) + #endif + } + + /// Whether this view or any of its subviews has a subview that has a double layout pass `UILabel` + /// as determined by a non-zero `preferredMaxLayoutWidth`, which implies that it should get a + /// `intrinsicHeightProposedWidth` sizing strategy to allow the label to wrap and grow. + @nonobjc + fileprivate func containsDoubleLayoutPassSubviews() -> Bool { + #if os(macOS) + return false + #else + var contains = false + if let label = self as? UILabel, label.preferredMaxLayoutWidth > 0 { + contains = true + } + for subview in subviews { + contains = contains || subview.containsDoubleLayoutPassSubviews() + } + return contains + #endif + } +} + +// MARK: - CGSize + +extension CGSize { + /// A `CGSize` with `noIntrinsicMetric` for both its width and height. + fileprivate static var noIntrinsicMetric: CGSize { + .init(width: ViewType.noIntrinsicMetric, height: ViewType.noIntrinsicMetric) + } + + /// Returns a `CGSize` with its width and/or height replaced with the corresponding field of the + /// provided `fallback` size if they are `UIView.noIntrinsicMetric`. + fileprivate func replacingNoIntrinsicMetric(with fallback: CGSize) -> CGSize { + .init( + width: width == ViewType.noIntrinsicMetric ? fallback.width : width, + height: height == ViewType.noIntrinsicMetric ? fallback.height : height) + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift new file mode 100644 index 0000000000000000000000000000000000000000..885a4e1a9db59dd92703f735029aa41bdb5e1106 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift @@ -0,0 +1,146 @@ +// Created by eric_horacek on 9/8/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - SwiftUIView + +/// A `UIViewRepresentable` SwiftUI `View` that wraps its `Content` `UIView` within a +/// `SwiftUIMeasurementContainer`, used to size a UIKit view correctly within a SwiftUI view +/// hierarchy. +/// +/// Includes an optional generic `Storage` value, which can be used to compare old and new values +/// across state changes to prevent redundant view updates. +struct SwiftUIView: MeasuringViewRepresentable, + UIViewConfiguringSwiftUIView +{ + + // MARK: Lifecycle + + /// Creates a SwiftUI representation of the content view with the given storage and the provided + /// `makeContent` closure to construct the content whenever `makeUIView(…)` is invoked. + init(storage: Storage, makeContent: @escaping () -> Content) { + self.storage = storage + self.makeContent = makeContent + } + + /// Creates a SwiftUI representation of the content view with the provided `makeContent` closure + /// to construct it whenever `makeUIView(…)` is invoked. + init(makeContent: @escaping () -> Content) where Storage == Void { + storage = () + self.makeContent = makeContent + } + + // MARK: Internal + + var configurations: [Configuration] = [] + + var sizing: SwiftUIMeasurementContainerStrategy = .automatic + + // MARK: Private + + /// The current stored value, with the previous value provided to the configuration closure as + /// the `oldStorage`. + private var storage: Storage + + /// A closure that's invoked to construct the represented content view. + private var makeContent: () -> Content +} + +// MARK: UIViewRepresentable + +extension SwiftUIView { + func makeCoordinator() -> Coordinator { + Coordinator(storage: storage) + } + + #if os(macOS) + func makeNSView(context _: Context) -> SwiftUIMeasurementContainer { + SwiftUIMeasurementContainer(content: makeContent(), strategy: sizing) + } + + func updateNSView(_ uiView: SwiftUIMeasurementContainer, context: Context) { + let oldStorage = context.coordinator.storage + context.coordinator.storage = storage + + let configurationContext = ConfigurationContext( + oldStorage: oldStorage, + viewRepresentableContext: context, + container: uiView) + + for configuration in configurations { + configuration(configurationContext) + } + } + #else + func makeUIView(context _: Context) -> SwiftUIMeasurementContainer { + SwiftUIMeasurementContainer(content: makeContent(), strategy: sizing) + } + + func updateUIView(_ uiView: SwiftUIMeasurementContainer, context: Context) { + let oldStorage = context.coordinator.storage + context.coordinator.storage = storage + + let configurationContext = ConfigurationContext( + oldStorage: oldStorage, + viewRepresentableContext: context, + container: uiView) + + for configuration in configurations { + configuration(configurationContext) + } + } + #endif +} + +// MARK: SwiftUIView.ConfigurationContext + +extension SwiftUIView { + /// The configuration context that's available to configure the `Content` view whenever the + /// `updateUIView()` method is invoked via a configuration closure. + struct ConfigurationContext: ViewProviding { + /// The previous value for the `Storage` of this `SwiftUIView`, which can be used to store + /// values across state changes to prevent redundant view updates. + var oldStorage: Storage + + /// The `UIViewRepresentable.Context`, with information about the transaction and environment. + var viewRepresentableContext: Context + + /// The backing measurement container that contains the `Content`. + var container: SwiftUIMeasurementContainer + + /// The `UIView` content that's being configured. + /// + /// Setting this to a new value updates the backing measurement container's `content`. + var view: Content { + get { container.content } + nonmutating set { container.content = newValue } + } + + /// A convenience accessor indicating whether this content update should be animated. + var animated: Bool { + viewRepresentableContext.transaction.animation != nil + } + } +} + +// MARK: SwiftUIView.Coordinator + +extension SwiftUIView { + /// A coordinator that stores the `storage` associated with this view, enabling the old storage + /// value to be accessed during the `updateUIView(…)`. + final class Coordinator { + + // MARK: Lifecycle + + fileprivate init(storage: Storage) { + self.storage = storage + } + + // MARK: Internal + + fileprivate(set) var storage: Storage + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift new file mode 100644 index 0000000000000000000000000000000000000000..bbea0a8784051228fdccb36af1016e5d717a7aee --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift @@ -0,0 +1,41 @@ +// Created by eric_horacek on 3/3/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - ViewTypeProtocol + swiftUIView + +extension ViewTypeProtocol { + /// Returns a SwiftUI `View` representing this `UIView`, constructed with the given `makeView` + /// closure and sized with the given sizing configuration. + /// + /// To perform additional configuration of the `UIView` instance, call `configure` on the + /// returned SwiftUI `View`: + /// ``` + /// MyUIView.swiftUIView(…) + /// .configure { context in + /// context.view.doSomething() + /// } + /// ``` + /// + /// To configure the sizing behavior of the `UIView` instance, call `sizing` on the returned + /// SwiftUI `View`: + /// ``` + /// MyView.swiftUIView(…).sizing(.intrinsicSize) + /// ``` + /// The sizing defaults to `.automatic`. + static func swiftUIView(makeView: @escaping () -> Self) -> SwiftUIView { + SwiftUIView(makeContent: makeView) + } +} + +// MARK: - ViewTypeProtocol + +/// A protocol that all `UIView`s conform to, enabling extensions that have a `Self` reference. +protocol ViewTypeProtocol: ViewType { } + +// MARK: - ViewType + ViewTypeProtocol + +extension ViewType: ViewTypeProtocol { } +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift new file mode 100644 index 0000000000000000000000000000000000000000..d7f8deda9b7382684d19db6ff2f4174b0bb0e097 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift @@ -0,0 +1,43 @@ +// Created by eric_horacek on 3/4/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - UIViewConfiguringSwiftUIView + +/// A protocol describing a SwiftUI `View` that can configure its `UIView` content via an array of +/// `configuration` closures. +protocol UIViewConfiguringSwiftUIView: View { + /// The context available to this configuration, which provides the `UIView` instance at a minimum + /// but can include additional context as needed. + associatedtype ConfigurationContext: ViewProviding + + /// A closure that is invoked to configure the represented content view. + typealias Configuration = (ConfigurationContext) -> Void + + /// A mutable array of configuration closures that should each be invoked with the + /// `ConfigurationContext` whenever `updateUIView` is called in a `UIViewRepresentable`. + var configurations: [Configuration] { get set } +} + +// MARK: Extensions + +extension UIViewConfiguringSwiftUIView { + /// Returns a copy of this view updated to have the given closure applied to its represented view + /// whenever it is updated via the `updateUIView(…)` method. + func configure(_ configure: @escaping Configuration) -> Self { + var copy = self + copy.configurations.append(configure) + return copy + } + + /// Returns a copy of this view updated to have the given closures applied to its represented view + /// whenever it is updated via the `updateUIView(…)` method. + func configurations(_ configurations: [Configuration]) -> Self { + var copy = self + copy.configurations.append(contentsOf: configurations) + return copy + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift new file mode 100644 index 0000000000000000000000000000000000000000..33c63f5684ad9de9eb5a93c80de2051d1d250cad --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift @@ -0,0 +1,45 @@ +// Created by Tyler Hedrick on 5/26/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - BehaviorsConfigurableView + +/// A view that can be configured with a `Behaviors` instance that contains the view's non- +/// `Equatable` properties that can be updated on view instances after initialization, e.g. callback +/// closures or delegates. +/// +/// Since it is not possible to establish the equality of two `Behaviors` instances, `Behaviors` +/// will be set more often than `ContentConfigurableView.Content`, needing to be updated every time +/// the view's corresponding `EpoxyModeled` instance is updated. As such, setting behaviors should +/// be as lightweight as possible. +/// +/// Properties of `Behaviors` should be mutually exclusive with the properties in the +/// `StyledView.Style` and `ContentConfigurableView.Content`. +/// +/// - SeeAlso: `ContentConfigurableView` +/// - SeeAlso: `StyledView` +/// - SeeAlso: `EpoxyableView` +protocol BehaviorsConfigurableView: ViewType { + /// The non-`Equatable` properties that can be changed over of the lifecycle this View's + /// instances, e.g. callback closures or delegates. + /// + /// Defaults to `Never` for views that do not have `Behaviors`. + associatedtype Behaviors = Never + + /// Updates the behaviors of this view to those in the given `behaviors`, else resets the + /// behaviors if `nil`. + /// + /// Behaviors are optional as they must be "resettable" in order for Epoxy to reset the behaviors + /// on your view when no behaviors are provided. + func setBehaviors(_ behaviors: Self.Behaviors?) +} + +// MARK: Defaults + +extension BehaviorsConfigurableView where Behaviors == Never { + func setBehaviors(_ behaviors: Never?) { + switch behaviors { + case nil: + break + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift new file mode 100644 index 0000000000000000000000000000000000000000..6ebce5ac17fbac5228c59b04602df8ed1c3392bb --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift @@ -0,0 +1,36 @@ +// Created by Laura Skelton on 5/30/17. +// Copyright © 2017 Airbnb. All rights reserved. + +// MARK: - ContentConfigurableView + +/// A view that can be configured with a `Content` instance that contains the view's `Equatable` +/// properties that can be updated on existing view instances, e.g. text `String`s or image `URL`s. +/// +/// For performance, it is generally expected that `Content` is only set when it is not equal to the +/// previous `Content` instance that has been set on a view instance. As a further optimization, +/// this view can guard updates on the equality of each property of the `Content` against the +/// current property value when set. +/// +/// Properties of `Content` should be mutually exclusive with the properties of the +/// `StyledView.Style` and `BehaviorsConfigurableView.Behaviors`. +/// +/// - SeeAlso: `BehaviorsConfigurableView` +/// - SeeAlso: `StyledView` +/// - SeeAlso: `EpoxyableView` +protocol ContentConfigurableView: ViewType { + /// The `Equatable` properties that can be updated on instances of this view, e.g. text `String`s + /// or image `URL`s. + /// + /// Defaults to `Never` for views that do not have `Content`. + associatedtype Content: Equatable = Never + + /// Updates the content of this view to the properties of the given `content`, optionally + /// animating the updates. + func setContent(_ content: Self.Content, animated: Bool) +} + +// MARK: Defaults + +extension ContentConfigurableView where Content == Never { + func setContent(_: Never, animated _: Bool) { } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift new file mode 100644 index 0000000000000000000000000000000000000000..5f13195a312f77b94cd2b553ad0397e05e2b2826 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift @@ -0,0 +1,5 @@ +// Created by eric_horacek on 1/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +/// A `UIView` that can be declaratively configured via a concrete `EpoxyableModel` instance. +typealias EpoxyableView = BehaviorsConfigurableView & ContentConfigurableView & StyledView diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift new file mode 100644 index 0000000000000000000000000000000000000000..13f871199b4aa7f97d33fbd5497bfdd085a59a86 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift @@ -0,0 +1,42 @@ +// Created by Laura Skelton on 4/14/16. +// Copyright © 2016 Airbnb. All rights reserved. + +// MARK: - StyledView + +/// A view that can be initialized with a `Style` instance that contains the view's invariant +/// configuration parameters, e.g. the `UIButton.ButtonType` of a `UIButton`. +/// +/// A `Style` is expected to be invariant over the lifecycle of the view; it should not possible to +/// change the `Style` of a view after it is created. All variant properties of the view should +/// either be included in the `ContentConfigurableView.Content` if they are `Equatable` (e.g. a +/// title `String`) or the `BehaviorsConfigurableView.Behaviors` if they are not (e.g. a callback +/// closure). +/// +/// A `Style` is `Hashable` to allow views of the same type with equal `Style`s to be reused by +/// establishing whether their invariant `Style` instances are equal. +/// +/// Properties of `Style` should be mutually exclusive with the properties of the +/// `ContentConfigurableView.Content` and `BehaviorsConfigurableView.Behaviors`. +/// +/// - SeeAlso: `ContentConfigurableView` +/// - SeeAlso: `BehaviorsConfigurableView` +/// - SeeAlso: `EpoxyableView` +protocol StyledView: ViewType { + /// The style type of this view, passed into its initializer to configure the resulting instance. + /// + /// Defaults to `Never` for views that do not have a `Style`. + associatedtype Style: Hashable = Never + + /// Creates an instance of this view configured with the given `Style` instance. + init(style: Style) +} + +// MARK: Defaults + +extension StyledView where Style == Never { + init(style: Never) { + // An empty switch is required to silence the "'self.init' isn't called on all paths before + // returning from initializer" error. + switch style { } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift new file mode 100644 index 0000000000000000000000000000000000000000..27f80c74e95ca453f1edb08cc8ac88e15f0acc8c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift @@ -0,0 +1,48 @@ +// Created by Cal Stephens on 6/26/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI +#if canImport(UIKit) +import UIKit + +/// The platform's main view type. +/// Either `UIView` on iOS/tvOS or `NSView` on macOS. +typealias ViewType = UIView + +/// The platform's SwiftUI view representable type. +/// Either `UIViewRepresentable` on iOS/tvOS or `NSViewRepresentable` on macOS. +typealias ViewRepresentableType = UIViewRepresentable + +/// The platform's layout constraint priority type. +/// Either `UILayoutPriority` on iOS/tvOS or `NSLayoutConstraint.Priority` on macOS. +typealias LayoutPriorityType = UILayoutPriority + +extension ViewRepresentableType { + /// The platform's view type for `ViewRepresentableType`. + /// Either `UIViewType` on iOS/tvOS or `NSViewType` on macOS. + typealias RepresentableViewType = UIViewType +} + +#elseif canImport(AppKit) +import AppKit + +/// The platform's main view type. +/// Either `UIView` on iOS/tvOS, or `NSView` on macOS. +typealias ViewType = NSView + +/// The platform's SwiftUI view representable type. +/// Either `UIViewRepresentable` on iOS/tvOS, or `NSViewRepresentable` on macOS. +typealias ViewRepresentableType = NSViewRepresentable + +/// The platform's layout constraint priority type. +/// Either `UILayoutPriority` on iOS/tvOS, or `NSLayoutConstraint.Priority` on macOS. +typealias LayoutPriorityType = NSLayoutConstraint.Priority + +extension ViewRepresentableType { + /// The platform's view type for `ViewRepresentableType`. + /// Either `UIViewType` on iOS/tvOS or `NSViewType` on macOS. + typealias RepresentableViewType = NSViewType +} +#endif +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/LRUCache/LRUCache.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/LRUCache/LRUCache.swift new file mode 100644 index 0000000000000000000000000000000000000000..771e0b4caa365b6eb8ac05b20ab16030112526ce --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/LRUCache/LRUCache.swift @@ -0,0 +1,256 @@ +// +// LRUCache.swift +// LRUCache +// +// Version 1.0.2 +// +// Created by Nick Lockwood on 05/08/2021. +// Copyright © 2021 Nick Lockwood. All rights reserved. +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/nicklockwood/LRUCache +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +#if canImport(UIKit) +import UIKit + +/// Notification that cache should be cleared +let LRUCacheMemoryWarningNotification: NSNotification.Name = + UIApplication.didReceiveMemoryWarningNotification + +#else + +/// Notification that cache should be cleared +let LRUCacheMemoryWarningNotification: NSNotification.Name = + .init("LRUCacheMemoryWarningNotification") + +#endif + +// MARK: - LRUCache + +final class LRUCache { + + // MARK: Lifecycle + + /// Initialize the cache with the specified `totalCostLimit` and `countLimit` + init( + totalCostLimit: Int = .max, + countLimit: Int = .max, + notificationCenter: NotificationCenter = .default) + { + self.totalCostLimit = totalCostLimit + self.countLimit = countLimit + self.notificationCenter = notificationCenter + + token = notificationCenter.addObserver( + forName: LRUCacheMemoryWarningNotification, + object: nil, + queue: nil) + { [weak self] _ in + self?.removeAllValues() + } + } + + deinit { + if let token { + notificationCenter.removeObserver(token) + } + } + + // MARK: Internal + + /// The current total cost of values in the cache + private(set) var totalCost = 0 + + /// The maximum total cost permitted + var totalCostLimit: Int { + didSet { clean() } + } + + /// The maximum number of values permitted + var countLimit: Int { + didSet { clean() } + } + + // MARK: Private + + private var values: [Key: Container] = [:] + private unowned(unsafe) var head: Container? + private unowned(unsafe) var tail: Container? + private let lock: NSLock = .init() + private var token: AnyObject? + private let notificationCenter: NotificationCenter + +} + +extension LRUCache { + /// The number of values currently stored in the cache + var count: Int { + values.count + } + + /// Is the cache empty? + var isEmpty: Bool { + values.isEmpty + } + + /// Returns all values in the cache from oldest to newest + var allValues: [Value] { + lock.lock() + defer { lock.unlock() } + var values = [Value]() + var next = head + while let container = next { + values.append(container.value) + next = container.next + } + return values + } + + /// Insert a value into the cache with optional `cost` + func setValue(_ value: Value?, forKey key: Key, cost: Int = 0) { + guard let value else { + removeValue(forKey: key) + return + } + lock.lock() + if let container = values[key] { + container.value = value + totalCost -= container.cost + container.cost = cost + remove(container) + append(container) + } else { + let container = Container( + value: value, + cost: cost, + key: key) + values[key] = container + append(container) + } + totalCost += cost + lock.unlock() + clean() + } + + /// Remove a value from the cache and return it + @discardableResult + func removeValue(forKey key: Key) -> Value? { + lock.lock() + defer { lock.unlock() } + guard let container = values.removeValue(forKey: key) else { + return nil + } + remove(container) + totalCost -= container.cost + return container.value + } + + /// Fetch a value from the cache + func value(forKey key: Key) -> Value? { + lock.lock() + defer { lock.unlock() } + if let container = values[key] { + remove(container) + append(container) + return container.value + } + return nil + } + + /// Remove all values from the cache + func removeAllValues() { + lock.lock() + values.removeAll() + head = nil + tail = nil + lock.unlock() + } +} + +extension LRUCache { + + // MARK: Fileprivate + + fileprivate final class Container { + + // MARK: Lifecycle + + init(value: Value, cost: Int, key: Key) { + self.value = value + self.cost = cost + self.key = key + } + + // MARK: Internal + + var value: Value + var cost: Int + let key: Key + unowned(unsafe) var prev: Container? + unowned(unsafe) var next: Container? + + } + + // MARK: Private + + /// Remove container from list (must be called inside lock) + private func remove(_ container: Container) { + if head === container { + head = container.next + } + if tail === container { + tail = container.prev + } + container.next?.prev = container.prev + container.prev?.next = container.next + container.next = nil + } + + /// Append container to list (must be called inside lock) + private func append(_ container: Container) { + assert(container.next == nil) + if head == nil { + head = container + } + container.prev = tail + tail?.next = container + tail = container + } + + /// Remove expired values (must be called outside lock) + private func clean() { + lock.lock() + defer { lock.unlock() } + while + totalCost > totalCostLimit || count > countLimit, + let container = head + { + remove(container) + values.removeValue(forKey: container.key) + totalCost -= container.cost + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/LRUCache/README.md b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/LRUCache/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cd1100fcf36e98f0acaeff98081a6f1f9dc3b211 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/LRUCache/README.md @@ -0,0 +1,24 @@ +## LRUCache + +This directory includes the source code of the LRUCache library, from the following release: +https://github.com/nicklockwood/LRUCache/releases/tag/1.0.4 + +Lottie is distributed via multiple package managers (SPM, Cocoapods, Carthage, and NPM), +each with different packaging and compilation requirements. + +Due to limitations of these package managers, we can't depend on / import +a separate LRUCache module / library. Instead, we include the source +directly within the Lottie library and compile everything as a single unit. + +### Update instructions + +From time to time we may need to update to a more recent version of LRUCache. +When doing this, follow these steps: + + 1. Download the latest release from https://github.com/nicklockwood/LRUCache + and replace the source code in this directory with the updated code. + + 2. Update the URL at the top of this file to indicate what release is being used. + + 3. Change all of the `public` symbols defined in this module to instead be `internal` + to prevent Lottie from exposing any EpoxyCore APIs. diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/README.md b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f925b8895f79a682ef8617c0bc8dc1ed60dd6094 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/README.md @@ -0,0 +1,43 @@ +## ZipFoundation + +This directory includes the source code of libraries that are embedded within lottie-ios. + +This includes: + - ZipFoundation (https://github.com/weichsel/ZIPFoundation) + - EpoxyCore (https://github.com/airbnb/epoxy-ios) + - LRUCache (https://github.com/nicklockwood/LRUCache) + +Lottie is distributed via multiple package managers (SPM, Cocoapods, Carthage, and NPM), +each with different packaging and compilation requirements. + +Due to limitations of these package managers, we can't depend on / import +a separates modules / libraries. Instead, we include the source +directly within the Lottie library and compile everything as a single unit. + +### Update instructions + +From time to time we may need to update to a more recent version of one of these libraries. +When doing this, follow these steps: + + 1. Download the latest release of the library and replace the source code in + the corresponding directory with the updated code. + + 2. Update the URL in the directory's README.md to indicate what release is being used. + + 3. Change all of the `public` symbols defined in the module to instead be `internal` + to prevent Lottie from exposing any APIs from other libraries. + +### Adding a new dependencies + + 1. Create a subdirectory in `EmbeddedLibraries` for the new dependency. + + 2. Add the dependency to the list at the top of this file. + + 3. Add a `README.md` to the directory for the new library, using the same formatting as the `README.md` file used by other dependencies. + + 4. Exclude the new `README.md` file from the lottie-ios package by adding it to the `exclude:` list in `Package.swift`. + + 5. Change all of the `public` symbols defined in the module to instead be `internal` + to prevent Lottie from exposing any APIs from other libraries. + + 6. If the dependency provides a privacy manifest, incorporate content from that dependency's privacy manifest into Lottie's privacy manifest. diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+BackingConfiguration.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+BackingConfiguration.swift new file mode 100644 index 0000000000000000000000000000000000000000..cd9d4418ac769724c7b7b2b1a33f7fd6fdaf78ac --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+BackingConfiguration.swift @@ -0,0 +1,151 @@ +// +// Archive+BackingConfiguration.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + + struct BackingConfiguration { + let file: FILEPointer + let endOfCentralDirectoryRecord: EndOfCentralDirectoryRecord + let zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory? + + #if swift(>=5.0) + let memoryFile: MemoryFile? + + init( + file: FILEPointer, + endOfCentralDirectoryRecord: EndOfCentralDirectoryRecord, + zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory? = nil, + memoryFile: MemoryFile? = nil) + { + self.file = file + self.endOfCentralDirectoryRecord = endOfCentralDirectoryRecord + self.zip64EndOfCentralDirectory = zip64EndOfCentralDirectory + self.memoryFile = memoryFile + } + #else + + init( + file: FILEPointer, + endOfCentralDirectoryRecord: EndOfCentralDirectoryRecord, + zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory?) + { + self.file = file + self.endOfCentralDirectoryRecord = endOfCentralDirectoryRecord + self.zip64EndOfCentralDirectory = zip64EndOfCentralDirectory + } + #endif + } + + static func makeBackingConfiguration(for url: URL, mode: AccessMode) + -> BackingConfiguration? + { + let fileManager = FileManager() + switch mode { + case .read: + let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + guard + let archiveFile = fopen(fileSystemRepresentation, "rb"), + let (eocdRecord, zip64EOCD) = Archive.scanForEndOfCentralDirectoryRecord(in: archiveFile) + else { + return nil + } + return BackingConfiguration( + file: archiveFile, + endOfCentralDirectoryRecord: eocdRecord, + zip64EndOfCentralDirectory: zip64EOCD) + + case .create: + let endOfCentralDirectoryRecord = EndOfCentralDirectoryRecord( + numberOfDisk: 0, + numberOfDiskStart: 0, + totalNumberOfEntriesOnDisk: 0, + totalNumberOfEntriesInCentralDirectory: 0, + sizeOfCentralDirectory: 0, + offsetToStartOfCentralDirectory: 0, + zipFileCommentLength: 0, + zipFileCommentData: Data()) + do { + try endOfCentralDirectoryRecord.data.write(to: url, options: .withoutOverwriting) + } catch { return nil } + fallthrough + + case .update: + let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + guard + let archiveFile = fopen(fileSystemRepresentation, "rb+"), + let (eocdRecord, zip64EOCD) = Archive.scanForEndOfCentralDirectoryRecord(in: archiveFile) + else { + return nil + } + fseeko(archiveFile, 0, SEEK_SET) + return BackingConfiguration( + file: archiveFile, + endOfCentralDirectoryRecord: eocdRecord, + zip64EndOfCentralDirectory: zip64EOCD) + } + } + + #if swift(>=5.0) + static func makeBackingConfiguration(for data: Data, mode: AccessMode) + -> BackingConfiguration? + { + let posixMode = + switch mode { + case .read: "rb" + case .create: "wb+" + case .update: "rb+" + } + let memoryFile = MemoryFile(data: data) + guard let archiveFile = memoryFile.open(mode: posixMode) else { return nil } + + switch mode { + case .read: + guard let (eocdRecord, zip64EOCD) = Archive.scanForEndOfCentralDirectoryRecord(in: archiveFile) else { + return nil + } + + return BackingConfiguration( + file: archiveFile, + endOfCentralDirectoryRecord: eocdRecord, + zip64EndOfCentralDirectory: zip64EOCD, + memoryFile: memoryFile) + + case .create: + let endOfCentralDirectoryRecord = EndOfCentralDirectoryRecord( + numberOfDisk: 0, + numberOfDiskStart: 0, + totalNumberOfEntriesOnDisk: 0, + totalNumberOfEntriesInCentralDirectory: 0, + sizeOfCentralDirectory: 0, + offsetToStartOfCentralDirectory: 0, + zipFileCommentLength: 0, + zipFileCommentData: Data()) + _ = endOfCentralDirectoryRecord.data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) in + fwrite(buffer.baseAddress, buffer.count, 1, archiveFile) // Errors handled during read + } + fallthrough + + case .update: + guard let (eocdRecord, zip64EOCD) = Archive.scanForEndOfCentralDirectoryRecord(in: archiveFile) else { + return nil + } + + fseeko(archiveFile, 0, SEEK_SET) + return BackingConfiguration( + file: archiveFile, + endOfCentralDirectoryRecord: eocdRecord, + zip64EndOfCentralDirectory: zip64EOCD, + memoryFile: memoryFile) + } + } + #endif +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Helpers.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Helpers.swift new file mode 100644 index 0000000000000000000000000000000000000000..34fee1950465de8e3607a82ea97232fe4421355d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Helpers.swift @@ -0,0 +1,355 @@ +// +// Archive+Helpers.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + + // MARK: - Reading + + func readUncompressed( + entry: Entry, + bufferSize: Int, + skipCRC32: Bool, + progress: Progress? = nil, + with consumer: Consumer) + throws -> CRC32 + { + let size = entry.centralDirectoryStructure.effectiveUncompressedSize + guard size <= .max else { throw ArchiveError.invalidEntrySize } + return try Data.consumePart( + of: Int64(size), + chunkSize: bufferSize, + skipCRC32: skipCRC32, + provider: { _, chunkSize -> Data in + try Data.readChunk(of: chunkSize, from: self.archiveFile) + }, + consumer: { data in + if progress?.isCancelled == true { throw ArchiveError.cancelledOperation } + try consumer(data) + progress?.completedUnitCount += Int64(data.count) + }) + } + + func readCompressed( + entry: Entry, + bufferSize: Int, + skipCRC32: Bool, + progress: Progress? = nil, + with consumer: Consumer) + throws -> CRC32 + { + let size = entry.centralDirectoryStructure.effectiveCompressedSize + guard size <= .max else { throw ArchiveError.invalidEntrySize } + return try Data.decompress( + size: Int64(size), + bufferSize: bufferSize, + skipCRC32: skipCRC32, + provider: { _, chunkSize -> Data in + try Data.readChunk(of: chunkSize, from: self.archiveFile) + }, + consumer: { data in + if progress?.isCancelled == true { throw ArchiveError.cancelledOperation } + try consumer(data) + progress?.completedUnitCount += Int64(data.count) + }) + } + + // MARK: - Writing + + func writeEntry( + uncompressedSize: Int64, + type: Entry.EntryType, + compressionMethod: CompressionMethod, + bufferSize: Int, + progress: Progress? = nil, + provider: Provider) throws -> (sizeWritten: Int64, crc32: CRC32) + { + var checksum = CRC32(0) + var sizeWritten = Int64(0) + switch type { + case .file: + switch compressionMethod { + case .none: + (sizeWritten, checksum) = try writeUncompressed( + size: uncompressedSize, + bufferSize: bufferSize, + progress: progress, + provider: provider) + + case .deflate: + (sizeWritten, checksum) = try writeCompressed( + size: uncompressedSize, + bufferSize: bufferSize, + progress: progress, + provider: provider) + } + + case .directory: + _ = try provider(0, 0) + if let progress { progress.completedUnitCount = progress.totalUnitCount } + + case .symlink: + let (linkSizeWritten, linkChecksum) = try writeSymbolicLink( + size: Int(uncompressedSize), + provider: provider) + (sizeWritten, checksum) = (Int64(linkSizeWritten), linkChecksum) + if let progress { progress.completedUnitCount = progress.totalUnitCount } + } + return (sizeWritten, checksum) + } + + func writeLocalFileHeader( + path: String, + compressionMethod: CompressionMethod, + size: (uncompressed: UInt64, compressed: UInt64), + checksum: CRC32, + modificationDateTime: (UInt16, UInt16)) + throws -> LocalFileHeader + { + // We always set Bit 11 in generalPurposeBitFlag, which indicates an UTF-8 encoded path. + guard let fileNameData = path.data(using: .utf8) else { throw ArchiveError.invalidEntryPath } + + var uncompressedSizeOfLFH = UInt32(0) + var compressedSizeOfLFH = UInt32(0) + var extraFieldLength = UInt16(0) + var zip64ExtendedInformation: Entry.ZIP64ExtendedInformation? + var versionNeededToExtract = Version.v20.rawValue + // ZIP64 Extended Information in the Local header MUST include BOTH original and compressed file size fields. + if size.uncompressed >= maxUncompressedSize || size.compressed >= maxCompressedSize { + uncompressedSizeOfLFH = .max + compressedSizeOfLFH = .max + extraFieldLength = UInt16(20) // 2 + 2 + 8 + 8 + versionNeededToExtract = Version.v45.rawValue + zip64ExtendedInformation = Entry.ZIP64ExtendedInformation( + dataSize: extraFieldLength - 4, + uncompressedSize: size.uncompressed, + compressedSize: size.compressed, + relativeOffsetOfLocalHeader: 0, + diskNumberStart: 0) + } else { + uncompressedSizeOfLFH = UInt32(size.uncompressed) + compressedSizeOfLFH = UInt32(size.compressed) + } + + let localFileHeader = LocalFileHeader( + versionNeededToExtract: versionNeededToExtract, + generalPurposeBitFlag: UInt16(2048), + compressionMethod: compressionMethod.rawValue, + lastModFileTime: modificationDateTime.1, + lastModFileDate: modificationDateTime.0, + crc32: checksum, + compressedSize: compressedSizeOfLFH, + uncompressedSize: uncompressedSizeOfLFH, + fileNameLength: UInt16(fileNameData.count), + extraFieldLength: extraFieldLength, + fileNameData: fileNameData, + extraFieldData: zip64ExtendedInformation?.data ?? Data()) + _ = try Data.write(chunk: localFileHeader.data, to: archiveFile) + return localFileHeader + } + + func writeCentralDirectoryStructure( + localFileHeader: LocalFileHeader, + relativeOffset: UInt64, + externalFileAttributes: UInt32) + throws -> CentralDirectoryStructure + { + var extraUncompressedSize: UInt64? + var extraCompressedSize: UInt64? + var extraOffset: UInt64? + var relativeOffsetOfCD = UInt32(0) + var extraFieldLength = UInt16(0) + var zip64ExtendedInformation: Entry.ZIP64ExtendedInformation? + if localFileHeader.uncompressedSize == .max || localFileHeader.compressedSize == .max { + let zip64Field = Entry.ZIP64ExtendedInformation + .scanForZIP64Field(in: localFileHeader.extraFieldData, fields: [.uncompressedSize, .compressedSize]) + extraUncompressedSize = zip64Field?.uncompressedSize + extraCompressedSize = zip64Field?.compressedSize + } + if relativeOffset >= maxOffsetOfLocalFileHeader { + extraOffset = relativeOffset + relativeOffsetOfCD = .max + } else { + relativeOffsetOfCD = UInt32(relativeOffset) + } + extraFieldLength = [extraUncompressedSize, extraCompressedSize, extraOffset] + .compactMap { $0 } + .reduce(UInt16(0)) { $0 + UInt16(MemoryLayout.size(ofValue: $1)) } + if extraFieldLength > 0 { + // Size of extra fields, shouldn't include the leading 4 bytes + zip64ExtendedInformation = Entry.ZIP64ExtendedInformation( + dataSize: extraFieldLength, + uncompressedSize: extraUncompressedSize ?? 0, + compressedSize: extraCompressedSize ?? 0, + relativeOffsetOfLocalHeader: extraOffset ?? 0, + diskNumberStart: 0) + extraFieldLength += Entry.ZIP64ExtendedInformation.headerSize + } + let centralDirectory = CentralDirectoryStructure( + localFileHeader: localFileHeader, + fileAttributes: externalFileAttributes, + relativeOffset: relativeOffsetOfCD, + extraField: ( + extraFieldLength, + zip64ExtendedInformation?.data ?? Data())) + _ = try Data.write(chunk: centralDirectory.data, to: archiveFile) + return centralDirectory + } + + func writeEndOfCentralDirectory( + centralDirectoryStructure: CentralDirectoryStructure, + startOfCentralDirectory: UInt64, + startOfEndOfCentralDirectory: UInt64, + operation: ModifyOperation) + throws -> EndOfCentralDirectoryStructure + { + var record = endOfCentralDirectoryRecord + let sizeOfCD = sizeOfCentralDirectory + let numberOfTotalEntries = totalNumberOfEntriesInCentralDirectory + let countChange = operation.rawValue + var dataLength = centralDirectoryStructure.extraFieldLength + dataLength += centralDirectoryStructure.fileNameLength + dataLength += centralDirectoryStructure.fileCommentLength + let cdDataLengthChange = countChange * (Int(dataLength) + CentralDirectoryStructure.size) + let (updatedSizeOfCD, updatedNumberOfEntries): (UInt64, UInt64) = try { + switch operation { + case .add: + guard .max - sizeOfCD >= cdDataLengthChange else { + throw ArchiveError.invalidCentralDirectorySize + } + guard .max - numberOfTotalEntries >= countChange else { + throw ArchiveError.invalidCentralDirectoryEntryCount + } + return (sizeOfCD + UInt64(cdDataLengthChange), numberOfTotalEntries + UInt64(countChange)) + + case .remove: + return (sizeOfCD - UInt64(-cdDataLengthChange), numberOfTotalEntries - UInt64(-countChange)) + } + }() + let sizeOfCDForEOCD = updatedSizeOfCD >= maxSizeOfCentralDirectory + ? UInt32.max + : UInt32(updatedSizeOfCD) + let numberOfTotalEntriesForEOCD = updatedNumberOfEntries >= maxTotalNumberOfEntries + ? UInt16.max + : UInt16(updatedNumberOfEntries) + let offsetOfCDForEOCD = startOfCentralDirectory >= maxOffsetOfCentralDirectory + ? UInt32.max + : UInt32(startOfCentralDirectory) + // ZIP64 End of Central Directory + var zip64EOCD: ZIP64EndOfCentralDirectory? + if numberOfTotalEntriesForEOCD == .max || offsetOfCDForEOCD == .max || sizeOfCDForEOCD == .max { + zip64EOCD = try writeZIP64EOCD( + totalNumberOfEntries: updatedNumberOfEntries, + sizeOfCentralDirectory: updatedSizeOfCD, + offsetOfCentralDirectory: startOfCentralDirectory, + offsetOfEndOfCentralDirectory: startOfEndOfCentralDirectory) + } + record = EndOfCentralDirectoryRecord( + record: record, + numberOfEntriesOnDisk: numberOfTotalEntriesForEOCD, + numberOfEntriesInCentralDirectory: numberOfTotalEntriesForEOCD, + updatedSizeOfCentralDirectory: sizeOfCDForEOCD, + startOfCentralDirectory: offsetOfCDForEOCD) + _ = try Data.write(chunk: record.data, to: archiveFile) + return (record, zip64EOCD) + } + + func writeUncompressed( + size: Int64, + bufferSize: Int, + progress: Progress? = nil, + provider: Provider) throws -> (sizeWritten: Int64, checksum: CRC32) + { + var position: Int64 = 0 + var sizeWritten: Int64 = 0 + var checksum = CRC32(0) + while position < size { + if progress?.isCancelled == true { throw ArchiveError.cancelledOperation } + let readSize = (size - position) >= bufferSize ? bufferSize : Int(size - position) + let entryChunk = try provider(position, readSize) + checksum = entryChunk.crc32(checksum: checksum) + sizeWritten += Int64(try Data.write(chunk: entryChunk, to: archiveFile)) + position += Int64(bufferSize) + progress?.completedUnitCount = sizeWritten + } + return (sizeWritten, checksum) + } + + func writeCompressed( + size: Int64, + bufferSize: Int, + progress: Progress? = nil, + provider: Provider) throws -> (sizeWritten: Int64, checksum: CRC32) + { + var sizeWritten: Int64 = 0 + let consumer: Consumer = { data in sizeWritten += Int64(try Data.write(chunk: data, to: self.archiveFile)) } + let checksum = try Data.compress( + size: size, + bufferSize: bufferSize, + provider: { position, size -> Data in + if progress?.isCancelled == true { throw ArchiveError.cancelledOperation } + let data = try provider(position, size) + progress?.completedUnitCount += Int64(data.count) + return data + }, + consumer: consumer) + return (sizeWritten, checksum) + } + + func writeSymbolicLink(size: Int, provider: Provider) throws -> (sizeWritten: Int, checksum: CRC32) { + // The reported size of a symlink is the number of characters in the path it points to. + let linkData = try provider(0, size) + let checksum = linkData.crc32(checksum: 0) + let sizeWritten = try Data.write(chunk: linkData, to: archiveFile) + return (sizeWritten, checksum) + } + + func writeZIP64EOCD( + totalNumberOfEntries: UInt64, + sizeOfCentralDirectory: UInt64, + offsetOfCentralDirectory: UInt64, + offsetOfEndOfCentralDirectory: UInt64) + throws -> ZIP64EndOfCentralDirectory + { + var zip64EOCD: ZIP64EndOfCentralDirectory = zip64EndOfCentralDirectory ?? { + // Shouldn't include the leading 12 bytes: (size - 12 = 44) + let record = ZIP64EndOfCentralDirectoryRecord( + sizeOfZIP64EndOfCentralDirectoryRecord: UInt64(44), + versionMadeBy: UInt16(789), + versionNeededToExtract: Version.v45.rawValue, + numberOfDisk: 0, + numberOfDiskStart: 0, + totalNumberOfEntriesOnDisk: 0, + totalNumberOfEntriesInCentralDirectory: 0, + sizeOfCentralDirectory: 0, + offsetToStartOfCentralDirectory: 0, + zip64ExtensibleDataSector: Data()) + let locator = ZIP64EndOfCentralDirectoryLocator( + numberOfDiskWithZIP64EOCDRecordStart: 0, + relativeOffsetOfZIP64EOCDRecord: 0, + totalNumberOfDisk: 1) + return ZIP64EndOfCentralDirectory(record: record, locator: locator) + }() + + let updatedRecord = ZIP64EndOfCentralDirectoryRecord( + record: zip64EOCD.record, + numberOfEntriesOnDisk: totalNumberOfEntries, + numberOfEntriesInCD: totalNumberOfEntries, + sizeOfCentralDirectory: sizeOfCentralDirectory, + offsetToStartOfCD: offsetOfCentralDirectory) + let updatedLocator = ZIP64EndOfCentralDirectoryLocator( + locator: zip64EOCD.locator, + offsetOfZIP64EOCDRecord: offsetOfEndOfCentralDirectory) + zip64EOCD = ZIP64EndOfCentralDirectory(record: updatedRecord, locator: updatedLocator) + _ = try Data.write(chunk: zip64EOCD.data, to: archiveFile) + return zip64EOCD + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+MemoryFile.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+MemoryFile.swift new file mode 100644 index 0000000000000000000000000000000000000000..cda58976bd8374f5e57520553457a3c2893e6903 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+MemoryFile.swift @@ -0,0 +1,183 @@ +// +// Archive+MemoryFile.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + var isMemoryArchive: Bool { url.scheme == memoryURLScheme } +} + +#if swift(>=5.0) + +extension Archive { + /// Returns a `Data` object containing a representation of the receiver. + var data: Data? { memoryFile?.data } +} + +class MemoryFile { + + // MARK: Lifecycle + + init(data: Data = Data()) { + self.data = data + } + + // MARK: Internal + + private(set) var data: Data + + func open(mode: String) -> FILEPointer? { + let cookie = Unmanaged.passRetained(self) + let writable = mode.count > 0 && (mode.first! != "r" || mode.last! == "+") + let append = mode.count > 0 && mode.first! == "a" + #if os(macOS) || canImport(UIKit) || os(Android) + let result = writable + ? funopen(cookie.toOpaque(), readStub, writeStub, seekStub, closeStub) + : funopen(cookie.toOpaque(), readStub, nil, seekStub, closeStub) + #else + let stubs = cookie_io_functions_t(read: readStub, write: writeStub, seek: seekStub, close: closeStub) + let result = fopencookie(cookie.toOpaque(), mode, stubs) + #endif + if append { + fseeko(result, 0, SEEK_END) + } + return result + } + + // MARK: Private + + private var offset = 0 + +} + +extension MemoryFile { + fileprivate func readData(buffer: UnsafeMutableRawBufferPointer) -> Int { + let size = min(buffer.count, data.count - offset) + let start = data.startIndex + data.copyBytes(to: buffer.bindMemory(to: UInt8.self), from: start + offset.. Int { + let start = data.startIndex + if offset < data.count, offset + buffer.count > data.count { + data.removeSubrange(start + offset.. data.count { + data.append(Data(count: offset - data.count)) + } + if offset == data.count { + data.append(buffer.bindMemory(to: UInt8.self)) + } else { + let start = data.startIndex // May have changed in earlier mutation + data.replaceSubrange(start + offset.. Int { + var result = -1 + if whence == SEEK_SET { + result = offset + } else if whence == SEEK_CUR { + result = self.offset + offset + } else if whence == SEEK_END { + result = data.count + offset + } + self.offset = result + return self.offset + } +} + +private func fileFromCookie(cookie: UnsafeRawPointer) -> MemoryFile { + Unmanaged.fromOpaque(cookie).takeUnretainedValue() +} + +private func closeStub(_ cookie: UnsafeMutableRawPointer?) -> Int32 { + if let cookie { + Unmanaged.fromOpaque(cookie).release() + } + return 0 +} + +#if os(macOS) || canImport(UIKit) || os(Android) +private func readStub( + _ cookie: UnsafeMutableRawPointer?, + _ bytePtr: UnsafeMutablePointer?, + _ count: Int32) + -> Int32 +{ + guard let cookie, let bytePtr else { return 0 } + return Int32(fileFromCookie(cookie: cookie).readData( + buffer: UnsafeMutableRawBufferPointer(start: bytePtr, count: Int(count)))) +} + +private func writeStub( + _ cookie: UnsafeMutableRawPointer?, + _ bytePtr: UnsafePointer?, + _ count: Int32) + -> Int32 +{ + guard let cookie, let bytePtr else { return 0 } + return Int32(fileFromCookie(cookie: cookie).writeData( + buffer: UnsafeRawBufferPointer(start: bytePtr, count: Int(count)))) +} + +private func seekStub( + _ cookie: UnsafeMutableRawPointer?, + _ offset: fpos_t, + _ whence: Int32) + -> fpos_t +{ + guard let cookie else { return 0 } + return fpos_t(fileFromCookie(cookie: cookie).seek(offset: Int(offset), whence: whence)) +} + +#else +private func readStub( + _ cookie: UnsafeMutableRawPointer?, + _ bytePtr: UnsafeMutablePointer?, + _ count: Int) + -> Int +{ + guard let cookie, let bytePtr else { return 0 } + return fileFromCookie(cookie: cookie).readData( + buffer: UnsafeMutableRawBufferPointer(start: bytePtr, count: count)) +} + +private func writeStub( + _ cookie: UnsafeMutableRawPointer?, + _ bytePtr: UnsafePointer?, + _ count: Int) + -> Int +{ + guard let cookie, let bytePtr else { return 0 } + return fileFromCookie(cookie: cookie).writeData( + buffer: UnsafeRawBufferPointer(start: bytePtr, count: count)) +} + +private func seekStub( + _ cookie: UnsafeMutableRawPointer?, + _ offset: UnsafeMutablePointer?, + _ whence: Int32) + -> Int32 +{ + guard let cookie, let offset else { return 0 } + let result = fileFromCookie(cookie: cookie).seek(offset: Int(offset.pointee), whence: whence) + if result >= 0 { + offset.pointee = result + return 0 + } else { + return -1 + } +} +#endif +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Progress.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Progress.swift new file mode 100644 index 0000000000000000000000000000000000000000..a55d5d18f879071e75f68c662ac3e0e8a75df149 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Progress.swift @@ -0,0 +1,66 @@ +// +// Archive+Progress.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + /// The number of the work units that have to be performed when + /// removing `entry` from the receiver. + /// + /// - Parameter entry: The entry that will be removed. + /// - Returns: The number of the work units. + func totalUnitCountForRemoving(_ entry: Entry) -> Int64 { + Int64(offsetToStartOfCentralDirectory - entry.localSize) + } + + func makeProgressForRemoving(_ entry: Entry) -> Progress { + Progress(totalUnitCount: totalUnitCountForRemoving(entry)) + } + + /// The number of the work units that have to be performed when + /// reading `entry` from the receiver. + /// + /// - Parameter entry: The entry that will be read. + /// - Returns: The number of the work units. + func totalUnitCountForReading(_ entry: Entry) -> Int64 { + switch entry.type { + case .file, .symlink: + Int64(entry.uncompressedSize) + case .directory: + defaultDirectoryUnitCount + } + } + + func makeProgressForReading(_ entry: Entry) -> Progress { + Progress(totalUnitCount: totalUnitCountForReading(entry)) + } + + /// The number of the work units that have to be performed when + /// adding the file at `url` to the receiver. + /// - Parameter entry: The entry that will be removed. + /// - Returns: The number of the work units. + func totalUnitCountForAddingItem(at url: URL) -> Int64 { + var count = Int64(0) + do { + let type = try FileManager.typeForItem(at: url) + switch type { + case .file, .symlink: + count = Int64(try FileManager.fileSizeForItem(at: url)) + case .directory: + count = defaultDirectoryUnitCount + } + } catch { count = -1 } + return count + } + + func makeProgressForAddingItem(at url: URL) -> Progress { + Progress(totalUnitCount: totalUnitCountForAddingItem(at: url)) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Reading.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Reading.swift new file mode 100644 index 0000000000000000000000000000000000000000..1d00eba8e80708b6461b86d4551982d3fcdd06c7 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Reading.swift @@ -0,0 +1,149 @@ +// +// Archive+Reading.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + /// Read a ZIP `Entry` from the receiver and write it to `url`. + /// + /// - Parameters: + /// - entry: The ZIP `Entry` to read. + /// - url: The destination file URL. + /// - bufferSize: The maximum size of the read buffer and the decompression buffer (if needed). + /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance. + /// - progress: A progress object that can be used to track or cancel the extract operation. + /// - Returns: The checksum of the processed content or 0 if the `skipCRC32` flag was set to `true`. + /// - Throws: An error if the destination file cannot be written or the entry contains malformed content. + func extract( + _ entry: Entry, + to url: URL, + bufferSize: Int = defaultReadChunkSize, + skipCRC32: Bool = false, + progress: Progress? = nil) + throws -> CRC32 + { + guard bufferSize > 0 else { + throw ArchiveError.invalidBufferSize + } + let fileManager = FileManager() + var checksum = CRC32(0) + switch entry.type { + case .file: + guard !fileManager.itemExists(at: url) else { + throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path]) + } + try fileManager.createParentDirectoryStructure(for: url) + let destinationRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + guard let destinationFile: FILEPointer = fopen(destinationRepresentation, "wb+") else { + throw CocoaError(.fileNoSuchFile) + } + defer { fclose(destinationFile) } + let consumer = { _ = try Data.write(chunk: $0, to: destinationFile) } + checksum = try extract( + entry, + bufferSize: bufferSize, + skipCRC32: skipCRC32, + progress: progress, + consumer: consumer) + + case .directory: + let consumer = { (_: Data) in + try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + } + checksum = try extract( + entry, + bufferSize: bufferSize, + skipCRC32: skipCRC32, + progress: progress, + consumer: consumer) + + case .symlink: + guard !fileManager.itemExists(at: url) else { + throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: url.path]) + } + let consumer = { (data: Data) in + guard let linkPath = String(data: data, encoding: .utf8) else { throw ArchiveError.invalidEntryPath } + try fileManager.createParentDirectoryStructure(for: url) + try fileManager.createSymbolicLink(atPath: url.path, withDestinationPath: linkPath) + } + checksum = try extract( + entry, + bufferSize: bufferSize, + skipCRC32: skipCRC32, + progress: progress, + consumer: consumer) + } + let attributes = FileManager.attributes(from: entry) + try fileManager.setAttributes(attributes, ofItemAtPath: url.path) + return checksum + } + + /// Read a ZIP `Entry` from the receiver and forward its contents to a `Consumer` closure. + /// + /// - Parameters: + /// - entry: The ZIP `Entry` to read. + /// - bufferSize: The maximum size of the read buffer and the decompression buffer (if needed). + /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance. + /// - progress: A progress object that can be used to track or cancel the extract operation. + /// - consumer: A closure that consumes contents of `Entry` as `Data` chunks. + /// - Returns: The checksum of the processed content or 0 if the `skipCRC32` flag was set to `true`.. + /// - Throws: An error if the destination file cannot be written or the entry contains malformed content. + func extract( + _ entry: Entry, + bufferSize: Int = defaultReadChunkSize, + skipCRC32: Bool = false, + progress: Progress? = nil, + consumer: Consumer) + throws -> CRC32 + { + guard bufferSize > 0 else { + throw ArchiveError.invalidBufferSize + } + var checksum = CRC32(0) + let localFileHeader = entry.localFileHeader + guard entry.dataOffset <= .max else { throw ArchiveError.invalidLocalHeaderDataOffset } + fseeko(archiveFile, off_t(entry.dataOffset), SEEK_SET) + progress?.totalUnitCount = totalUnitCountForReading(entry) + switch entry.type { + case .file: + guard let compressionMethod = CompressionMethod(rawValue: localFileHeader.compressionMethod) else { + throw ArchiveError.invalidCompressionMethod + } + switch compressionMethod { + case .none: checksum = try readUncompressed( + entry: entry, + bufferSize: bufferSize, + skipCRC32: skipCRC32, + progress: progress, + with: consumer) + + case .deflate: checksum = try readCompressed( + entry: entry, + bufferSize: bufferSize, + skipCRC32: skipCRC32, + progress: progress, + with: consumer) + } + + case .directory: + try consumer(Data()) + progress?.completedUnitCount = totalUnitCountForReading(entry) + + case .symlink: + let localFileHeader = entry.localFileHeader + let size = Int(localFileHeader.compressedSize) + let data = try Data.readChunk(of: size, from: archiveFile) + checksum = data.crc32(checksum: 0) + try consumer(data) + progress?.completedUnitCount = totalUnitCountForReading(entry) + } + return checksum + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+ReadingDeprecated.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+ReadingDeprecated.swift new file mode 100644 index 0000000000000000000000000000000000000000..1e30365456b51b5f63edd18af9349641b10a6141 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+ReadingDeprecated.swift @@ -0,0 +1,49 @@ +// +// Archive+ReadingDeprecated.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + + @available( + *, + deprecated, + message: "Please use `Int` for `bufferSize`.") + func extract( + _ entry: Entry, + to url: URL, + bufferSize: UInt32, + skipCRC32: Bool = false, + progress: Progress? = nil) + throws -> CRC32 + { + try extract(entry, to: url, bufferSize: Int(bufferSize), skipCRC32: skipCRC32, progress: progress) + } + + @available( + *, + deprecated, + message: "Please use `Int` for `bufferSize`.") + func extract( + _ entry: Entry, + bufferSize: UInt32, + skipCRC32: Bool = false, + progress: Progress? = nil, + consumer: Consumer) + throws -> CRC32 + { + try extract( + entry, + bufferSize: Int(bufferSize), + skipCRC32: skipCRC32, + progress: progress, + consumer: consumer) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Writing.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Writing.swift new file mode 100644 index 0000000000000000000000000000000000000000..dd5bef5a7a3031bbf9416313621063c1477947cf --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+Writing.swift @@ -0,0 +1,387 @@ +// +// Archive+Writing.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + enum ModifyOperation: Int { + case remove = -1 + case add = 1 + } + + typealias EndOfCentralDirectoryStructure = (EndOfCentralDirectoryRecord, ZIP64EndOfCentralDirectory?) + + /// Write files, directories or symlinks to the receiver. + /// + /// - Parameters: + /// - path: The path that is used to identify an `Entry` within the `Archive` file. + /// - baseURL: The base URL of the resource to add. + /// The `baseURL` combined with `path` must form a fully qualified file URL. + /// - compressionMethod: Indicates the `CompressionMethod` that should be applied to `Entry`. + /// By default, no compression will be applied. + /// - bufferSize: The maximum size of the write buffer and the compression buffer (if needed). + /// - progress: A progress object that can be used to track or cancel the add operation. + /// - Throws: An error if the source file cannot be read or the receiver is not writable. + func addEntry( + with path: String, + relativeTo baseURL: URL, + compressionMethod: CompressionMethod = .none, + bufferSize: Int = defaultWriteChunkSize, + progress: Progress? = nil) + throws + { + let fileURL = baseURL.appendingPathComponent(path) + + try addEntry( + with: path, + fileURL: fileURL, + compressionMethod: compressionMethod, + bufferSize: bufferSize, + progress: progress) + } + + /// Write files, directories or symlinks to the receiver. + /// + /// - Parameters: + /// - path: The path that is used to identify an `Entry` within the `Archive` file. + /// - fileURL: An absolute file URL referring to the resource to add. + /// - compressionMethod: Indicates the `CompressionMethod` that should be applied to `Entry`. + /// By default, no compression will be applied. + /// - bufferSize: The maximum size of the write buffer and the compression buffer (if needed). + /// - progress: A progress object that can be used to track or cancel the add operation. + /// - Throws: An error if the source file cannot be read or the receiver is not writable. + func addEntry( + with path: String, + fileURL: URL, + compressionMethod: CompressionMethod = .none, + bufferSize: Int = defaultWriteChunkSize, + progress: Progress? = nil) + throws + { + let fileManager = FileManager() + guard fileManager.itemExists(at: fileURL) else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: fileURL.path]) + } + let type = try FileManager.typeForItem(at: fileURL) + // symlinks do not need to be readable + guard type == .symlink || fileManager.isReadableFile(atPath: fileURL.path) else { + throw CocoaError(.fileReadNoPermission, userInfo: [NSFilePathErrorKey: url.path]) + } + let modDate = try FileManager.fileModificationDateTimeForItem(at: fileURL) + let uncompressedSize = type == .directory ? 0 : try FileManager.fileSizeForItem(at: fileURL) + let permissions = try FileManager.permissionsForItem(at: fileURL) + var provider: Provider + switch type { + case .file: + let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: fileURL.path) + guard let entryFile: FILEPointer = fopen(entryFileSystemRepresentation, "rb") else { + throw CocoaError(.fileNoSuchFile) + } + defer { fclose(entryFile) } + provider = { _, _ in try Data.readChunk(of: bufferSize, from: entryFile) } + try addEntry( + with: path, + type: type, + uncompressedSize: uncompressedSize, + modificationDate: modDate, + permissions: permissions, + compressionMethod: compressionMethod, + bufferSize: bufferSize, + progress: progress, + provider: provider) + + case .directory: + provider = { _, _ in Data() } + try addEntry( + with: path.hasSuffix("/") ? path : path + "/", + type: type, + uncompressedSize: uncompressedSize, + modificationDate: modDate, + permissions: permissions, + compressionMethod: compressionMethod, + bufferSize: bufferSize, + progress: progress, + provider: provider) + + case .symlink: + provider = { _, _ -> Data in + let linkDestination = try fileManager.destinationOfSymbolicLink(atPath: fileURL.path) + let linkFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: linkDestination) + let linkLength = Int(strlen(linkFileSystemRepresentation)) + let linkBuffer = UnsafeBufferPointer(start: linkFileSystemRepresentation, count: linkLength) + return Data(buffer: linkBuffer) + } + try addEntry( + with: path, + type: type, + uncompressedSize: uncompressedSize, + modificationDate: modDate, + permissions: permissions, + compressionMethod: compressionMethod, + bufferSize: bufferSize, + progress: progress, + provider: provider) + } + } + + /// Write files, directories or symlinks to the receiver. + /// + /// - Parameters: + /// - path: The path that is used to identify an `Entry` within the `Archive` file. + /// - type: Indicates the `Entry.EntryType` of the added content. + /// - uncompressedSize: The uncompressed size of the data that is going to be added with `provider`. + /// - modificationDate: A `Date` describing the file modification date of the `Entry`. + /// Default is the current `Date`. + /// - permissions: POSIX file permissions for the `Entry`. + /// Default is `0`o`644` for files and symlinks and `0`o`755` for directories. + /// - compressionMethod: Indicates the `CompressionMethod` that should be applied to `Entry`. + /// By default, no compression will be applied. + /// - bufferSize: The maximum size of the write buffer and the compression buffer (if needed). + /// - progress: A progress object that can be used to track or cancel the add operation. + /// - provider: A closure that accepts a position and a chunk size. Returns a `Data` chunk. + /// - Throws: An error if the source data is invalid or the receiver is not writable. + func addEntry( + with path: String, + type: Entry.EntryType, + uncompressedSize: Int64, + modificationDate: Date = Date(), + permissions: UInt16? = nil, + compressionMethod: CompressionMethod = .none, + bufferSize: Int = defaultWriteChunkSize, + progress: Progress? = nil, + provider: Provider) + throws + { + guard accessMode != .read else { throw ArchiveError.unwritableArchive } + // Directories and symlinks cannot be compressed + let compressionMethod = type == .file ? compressionMethod : .none + progress?.totalUnitCount = type == .directory ? defaultDirectoryUnitCount : uncompressedSize + let (eocdRecord, zip64EOCD) = (endOfCentralDirectoryRecord, zip64EndOfCentralDirectory) + guard offsetToStartOfCentralDirectory <= .max else { throw ArchiveError.invalidCentralDirectoryOffset } + var startOfCD = Int64(offsetToStartOfCentralDirectory) + fseeko(archiveFile, off_t(startOfCD), SEEK_SET) + let existingSize = sizeOfCentralDirectory + let existingData = try Data.readChunk(of: Int(existingSize), from: archiveFile) + fseeko(archiveFile, off_t(startOfCD), SEEK_SET) + let fileHeaderStart = Int64(ftello(archiveFile)) + let modDateTime = modificationDate.fileModificationDateTime + defer { fflush(self.archiveFile) } + do { + // Local File Header + var localFileHeader = try writeLocalFileHeader( + path: path, + compressionMethod: compressionMethod, + size: (UInt64(uncompressedSize), 0), + checksum: 0, + modificationDateTime: modDateTime) + // File Data + let (written, checksum) = try writeEntry( + uncompressedSize: uncompressedSize, + type: type, + compressionMethod: compressionMethod, + bufferSize: bufferSize, + progress: progress, + provider: provider) + startOfCD = Int64(ftello(archiveFile)) + // Write the local file header a second time. Now with compressedSize (if applicable) and a valid checksum. + fseeko(archiveFile, off_t(fileHeaderStart), SEEK_SET) + localFileHeader = try writeLocalFileHeader( + path: path, + compressionMethod: compressionMethod, + size: (UInt64(uncompressedSize), UInt64(written)), + checksum: checksum, + modificationDateTime: modDateTime) + // Central Directory + fseeko(archiveFile, off_t(startOfCD), SEEK_SET) + _ = try Data.writeLargeChunk(existingData, size: existingSize, bufferSize: bufferSize, to: archiveFile) + let permissions = permissions ?? (type == .directory ? defaultDirectoryPermissions : defaultFilePermissions) + let externalAttributes = FileManager.externalFileAttributesForEntry(of: type, permissions: permissions) + let centralDir = try writeCentralDirectoryStructure( + localFileHeader: localFileHeader, + relativeOffset: UInt64(fileHeaderStart), + externalFileAttributes: externalAttributes) + // End of Central Directory Record (including ZIP64 End of Central Directory Record/Locator) + let startOfEOCD = UInt64(ftello(archiveFile)) + let eocd = try writeEndOfCentralDirectory( + centralDirectoryStructure: centralDir, + startOfCentralDirectory: UInt64(startOfCD), + startOfEndOfCentralDirectory: startOfEOCD, + operation: .add) + (endOfCentralDirectoryRecord, zip64EndOfCentralDirectory) = eocd + } catch ArchiveError.cancelledOperation { + try rollback(UInt64(fileHeaderStart), (existingData, existingSize), bufferSize, eocdRecord, zip64EOCD) + throw ArchiveError.cancelledOperation + } + } + + /// Remove a ZIP `Entry` from the receiver. + /// + /// - Parameters: + /// - entry: The `Entry` to remove. + /// - bufferSize: The maximum size for the read and write buffers used during removal. + /// - progress: A progress object that can be used to track or cancel the remove operation. + /// - Throws: An error if the `Entry` is malformed or the receiver is not writable. + func remove(_ entry: Entry, bufferSize: Int = defaultReadChunkSize, progress: Progress? = nil) throws { + guard accessMode != .read else { throw ArchiveError.unwritableArchive } + let (tempArchive, tempDir) = try makeTempArchive() + defer { tempDir.map { try? FileManager().removeItem(at: $0) } } + progress?.totalUnitCount = totalUnitCountForRemoving(entry) + var centralDirectoryData = Data() + var offset: UInt64 = 0 + for currentEntry in self { + let cds = currentEntry.centralDirectoryStructure + if currentEntry != entry { + let entryStart = cds.effectiveRelativeOffsetOfLocalHeader + fseeko(archiveFile, off_t(entryStart), SEEK_SET) + let provider: Provider = { _, chunkSize -> Data in + try Data.readChunk(of: chunkSize, from: self.archiveFile) + } + let consumer: Consumer = { + if progress?.isCancelled == true { throw ArchiveError.cancelledOperation } + _ = try Data.write(chunk: $0, to: tempArchive.archiveFile) + progress?.completedUnitCount += Int64($0.count) + } + guard currentEntry.localSize <= .max else { throw ArchiveError.invalidLocalHeaderSize } + _ = try Data.consumePart( + of: Int64(currentEntry.localSize), + chunkSize: bufferSize, + provider: provider, + consumer: consumer) + let updatedCentralDirectory = updateOffsetInCentralDirectory( + centralDirectoryStructure: cds, + updatedOffset: entryStart - offset) + centralDirectoryData.append(updatedCentralDirectory.data) + } else { offset = currentEntry.localSize } + } + let startOfCentralDirectory = UInt64(ftello(tempArchive.archiveFile)) + _ = try Data.write(chunk: centralDirectoryData, to: tempArchive.archiveFile) + let startOfEndOfCentralDirectory = UInt64(ftello(tempArchive.archiveFile)) + tempArchive.endOfCentralDirectoryRecord = endOfCentralDirectoryRecord + tempArchive.zip64EndOfCentralDirectory = zip64EndOfCentralDirectory + let ecodStructure = try + tempArchive.writeEndOfCentralDirectory( + centralDirectoryStructure: entry.centralDirectoryStructure, + startOfCentralDirectory: startOfCentralDirectory, + startOfEndOfCentralDirectory: startOfEndOfCentralDirectory, + operation: .remove) + (tempArchive.endOfCentralDirectoryRecord, tempArchive.zip64EndOfCentralDirectory) = ecodStructure + (endOfCentralDirectoryRecord, zip64EndOfCentralDirectory) = ecodStructure + fflush(tempArchive.archiveFile) + try replaceCurrentArchive(with: tempArchive) + } + + func replaceCurrentArchive(with archive: Archive) throws { + fclose(archiveFile) + if isMemoryArchive { + #if swift(>=5.0) + guard + let data = archive.data, + let config = Archive.makeBackingConfiguration(for: data, mode: .update) + else { + throw ArchiveError.unwritableArchive + } + archiveFile = config.file + memoryFile = config.memoryFile + endOfCentralDirectoryRecord = config.endOfCentralDirectoryRecord + zip64EndOfCentralDirectory = config.zip64EndOfCentralDirectory + #endif + } else { + let fileManager = FileManager() + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + do { + _ = try fileManager.replaceItemAt(url, withItemAt: archive.url) + } catch { + _ = try fileManager.removeItem(at: url) + _ = try fileManager.moveItem(at: archive.url, to: url) + } + #else + _ = try fileManager.removeItem(at: url) + _ = try fileManager.moveItem(at: archive.url, to: url) + #endif + let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + guard let file = fopen(fileSystemRepresentation, "rb+") else { throw ArchiveError.unreadableArchive } + archiveFile = file + } + } +} + +// MARK: - Private + +extension Archive { + + private func updateOffsetInCentralDirectory( + centralDirectoryStructure: CentralDirectoryStructure, + updatedOffset: UInt64) + -> CentralDirectoryStructure + { + let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation( + zip64ExtendedInformation: centralDirectoryStructure.zip64ExtendedInformation, offset: updatedOffset) + let offsetInCD = updatedOffset < maxOffsetOfLocalFileHeader ? UInt32(updatedOffset) : UInt32.max + return CentralDirectoryStructure( + centralDirectoryStructure: centralDirectoryStructure, + zip64ExtendedInformation: zip64ExtendedInformation, + relativeOffset: offsetInCD) + } + + private func rollback( + _ localFileHeaderStart: UInt64, + _ existingCentralDirectory: (data: Data, size: UInt64), + _ bufferSize: Int, + _ endOfCentralDirRecord: EndOfCentralDirectoryRecord, + _ zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory?) + throws + { + fflush(archiveFile) + ftruncate(fileno(archiveFile), off_t(localFileHeaderStart)) + fseeko(archiveFile, off_t(localFileHeaderStart), SEEK_SET) + _ = try Data.writeLargeChunk( + existingCentralDirectory.data, + size: existingCentralDirectory.size, + bufferSize: bufferSize, + to: archiveFile) + _ = try Data.write(chunk: existingCentralDirectory.data, to: archiveFile) + if let zip64EOCD = zip64EndOfCentralDirectory { + _ = try Data.write(chunk: zip64EOCD.data, to: archiveFile) + } + _ = try Data.write(chunk: endOfCentralDirRecord.data, to: archiveFile) + } + + private func makeTempArchive() throws -> (Archive, URL?) { + var archive: Archive + var url: URL? + if isMemoryArchive { + #if swift(>=5.0) + guard + let tempArchive = Archive( + data: Data(), + accessMode: .create, + preferredEncoding: preferredEncoding) + else { + throw ArchiveError.unwritableArchive + } + archive = tempArchive + #else + fatalError("Memory archives are unsupported.") + #endif + } else { + let manager = FileManager() + let tempDir = URL.temporaryReplacementDirectoryURL(for: self) + let uniqueString = ProcessInfo.processInfo.globallyUniqueString + let tempArchiveURL = tempDir.appendingPathComponent(uniqueString) + try manager.createParentDirectoryStructure(for: tempArchiveURL) + guard let tempArchive = Archive(url: tempArchiveURL, accessMode: .create) else { + throw ArchiveError.unwritableArchive + } + archive = tempArchive + url = tempDir + } + return (archive, url) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+WritingDeprecated.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+WritingDeprecated.swift new file mode 100644 index 0000000000000000000000000000000000000000..311c6a81b505a04d771c12c931cce40d114bd647 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+WritingDeprecated.swift @@ -0,0 +1,91 @@ +// +// Archive+WritingDeprecated.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Archive { + + @available( + *, + deprecated, + message: "Please use `Int` for `bufferSize`.") + func addEntry( + with path: String, + relativeTo baseURL: URL, + compressionMethod: CompressionMethod = .none, + bufferSize: UInt32, + progress: Progress? = nil) + throws + { + try addEntry( + with: path, + relativeTo: baseURL, + compressionMethod: compressionMethod, + bufferSize: Int(bufferSize), + progress: progress) + } + + @available( + *, + deprecated, + message: "Please use `Int` for `bufferSize`.") + func addEntry( + with path: String, + fileURL: URL, + compressionMethod: CompressionMethod = .none, + bufferSize: UInt32, + progress: Progress? = nil) + throws + { + try addEntry( + with: path, + fileURL: fileURL, + compressionMethod: compressionMethod, + bufferSize: Int(bufferSize), + progress: progress) + } + + @available( + *, + deprecated, + message: "Please use `Int64` for `uncompressedSize` and provider `position`. `Int` for `bufferSize`.") + func addEntry( + with path: String, + type: Entry.EntryType, + uncompressedSize: UInt32, + modificationDate: Date = Date(), + permissions: UInt16? = nil, + compressionMethod: CompressionMethod = .none, + bufferSize: Int = defaultWriteChunkSize, + progress: Progress? = nil, + provider: (_ position: Int, _ size: Int) throws -> Data) + throws + { + let newProvider: Provider = { try provider(Int($0), $1) } + try addEntry( + with: path, + type: type, + uncompressedSize: Int64(uncompressedSize), + modificationDate: modificationDate, + permissions: permissions, + compressionMethod: compressionMethod, + bufferSize: bufferSize, + progress: progress, + provider: newProvider) + } + + @available( + *, + deprecated, + message: "Please use `Int` for `bufferSize`.") + func remove(_ entry: Entry, bufferSize: UInt32, progress: Progress? = nil) throws { + try remove(entry, bufferSize: Int(bufferSize), progress: progress) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+ZIP64.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+ZIP64.swift new file mode 100644 index 0000000000000000000000000000000000000000..2b301f7d131c742d0332a959a6514b4ce852bbc7 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive+ZIP64.swift @@ -0,0 +1,170 @@ +// +// Archive+ZIP64.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +let zip64EOCDRecordStructSignature = 0x06064b50 +let zip64EOCDLocatorStructSignature = 0x07064b50 + +// MARK: - ExtraFieldHeaderID + +enum ExtraFieldHeaderID: UInt16 { + case zip64ExtendedInformation = 0x0001 +} + +extension Archive { + struct ZIP64EndOfCentralDirectory { + let record: ZIP64EndOfCentralDirectoryRecord + let locator: ZIP64EndOfCentralDirectoryLocator + } + + struct ZIP64EndOfCentralDirectoryRecord: DataSerializable { + let zip64EOCDRecordSignature = UInt32(zip64EOCDRecordStructSignature) + let sizeOfZIP64EndOfCentralDirectoryRecord: UInt64 + let versionMadeBy: UInt16 + let versionNeededToExtract: UInt16 + let numberOfDisk: UInt32 + let numberOfDiskStart: UInt32 + let totalNumberOfEntriesOnDisk: UInt64 + let totalNumberOfEntriesInCentralDirectory: UInt64 + let sizeOfCentralDirectory: UInt64 + let offsetToStartOfCentralDirectory: UInt64 + let zip64ExtensibleDataSector: Data + static let size = 56 + } + + struct ZIP64EndOfCentralDirectoryLocator: DataSerializable { + let zip64EOCDLocatorSignature = UInt32(zip64EOCDLocatorStructSignature) + let numberOfDiskWithZIP64EOCDRecordStart: UInt32 + let relativeOffsetOfZIP64EOCDRecord: UInt64 + let totalNumberOfDisk: UInt32 + static let size = 20 + } +} + +extension Archive.ZIP64EndOfCentralDirectoryRecord { + + // MARK: Lifecycle + + init?(data: Data, additionalDataProvider _: (Int) throws -> Data) { + guard data.count == Archive.ZIP64EndOfCentralDirectoryRecord.size else { return nil } + guard data.scanValue(start: 0) == zip64EOCDRecordSignature else { return nil } + sizeOfZIP64EndOfCentralDirectoryRecord = data.scanValue(start: 4) + versionMadeBy = data.scanValue(start: 12) + versionNeededToExtract = data.scanValue(start: 14) + // Version Needed to Extract: 4.5 - File uses ZIP64 format extensions + guard versionNeededToExtract >= Archive.Version.v45.rawValue else { return nil } + numberOfDisk = data.scanValue(start: 16) + numberOfDiskStart = data.scanValue(start: 20) + totalNumberOfEntriesOnDisk = data.scanValue(start: 24) + totalNumberOfEntriesInCentralDirectory = data.scanValue(start: 32) + sizeOfCentralDirectory = data.scanValue(start: 40) + offsetToStartOfCentralDirectory = data.scanValue(start: 48) + zip64ExtensibleDataSector = Data() + } + + init( + record: Archive.ZIP64EndOfCentralDirectoryRecord, + numberOfEntriesOnDisk: UInt64, + numberOfEntriesInCD: UInt64, + sizeOfCentralDirectory: UInt64, + offsetToStartOfCD: UInt64) + { + sizeOfZIP64EndOfCentralDirectoryRecord = record.sizeOfZIP64EndOfCentralDirectoryRecord + versionMadeBy = record.versionMadeBy + versionNeededToExtract = record.versionNeededToExtract + numberOfDisk = record.numberOfDisk + numberOfDiskStart = record.numberOfDiskStart + totalNumberOfEntriesOnDisk = numberOfEntriesOnDisk + totalNumberOfEntriesInCentralDirectory = numberOfEntriesInCD + self.sizeOfCentralDirectory = sizeOfCentralDirectory + offsetToStartOfCentralDirectory = offsetToStartOfCD + zip64ExtensibleDataSector = record.zip64ExtensibleDataSector + } + + // MARK: Internal + + var data: Data { + var zip64EOCDRecordSignature = zip64EOCDRecordSignature + var sizeOfZIP64EOCDRecord = sizeOfZIP64EndOfCentralDirectoryRecord + var versionMadeBy = versionMadeBy + var versionNeededToExtract = versionNeededToExtract + var numberOfDisk = numberOfDisk + var numberOfDiskStart = numberOfDiskStart + var totalNumberOfEntriesOnDisk = totalNumberOfEntriesOnDisk + var totalNumberOfEntriesInCD = totalNumberOfEntriesInCentralDirectory + var sizeOfCD = sizeOfCentralDirectory + var offsetToStartOfCD = offsetToStartOfCentralDirectory + var data = Data() + withUnsafePointer(to: &zip64EOCDRecordSignature) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &sizeOfZIP64EOCDRecord) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &versionMadeBy) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &versionNeededToExtract) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &numberOfDisk) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &numberOfDiskStart) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &totalNumberOfEntriesOnDisk) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &totalNumberOfEntriesInCD) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &sizeOfCD) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &offsetToStartOfCD) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + data.append(zip64ExtensibleDataSector) + return data + } + +} + +extension Archive.ZIP64EndOfCentralDirectoryLocator { + + // MARK: Lifecycle + + init?(data: Data, additionalDataProvider _: (Int) throws -> Data) { + guard data.count == Archive.ZIP64EndOfCentralDirectoryLocator.size else { return nil } + guard data.scanValue(start: 0) == zip64EOCDLocatorSignature else { return nil } + numberOfDiskWithZIP64EOCDRecordStart = data.scanValue(start: 4) + relativeOffsetOfZIP64EOCDRecord = data.scanValue(start: 8) + totalNumberOfDisk = data.scanValue(start: 16) + } + + init(locator: Archive.ZIP64EndOfCentralDirectoryLocator, offsetOfZIP64EOCDRecord: UInt64) { + numberOfDiskWithZIP64EOCDRecordStart = locator.numberOfDiskWithZIP64EOCDRecordStart + relativeOffsetOfZIP64EOCDRecord = offsetOfZIP64EOCDRecord + totalNumberOfDisk = locator.totalNumberOfDisk + } + + // MARK: Internal + + var data: Data { + var zip64EOCDLocatorSignature = zip64EOCDLocatorSignature + var numberOfDiskWithZIP64EOCD = numberOfDiskWithZIP64EOCDRecordStart + var offsetOfZIP64EOCDRecord = relativeOffsetOfZIP64EOCDRecord + var totalNumberOfDisk = totalNumberOfDisk + var data = Data() + withUnsafePointer(to: &zip64EOCDLocatorSignature) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &numberOfDiskWithZIP64EOCD) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &offsetOfZIP64EOCDRecord) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &totalNumberOfDisk) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + return data + } + +} + +extension Archive.ZIP64EndOfCentralDirectory { + var data: Data { record.data + locator.data } +} + +/// Properties that represent the maximum value of each field +var maxUInt32 = UInt32.max +var maxUInt16 = UInt16.max + +var maxCompressedSize: UInt32 { maxUInt32 } +var maxUncompressedSize: UInt32 { maxUInt32 } +var maxOffsetOfLocalFileHeader: UInt32 { maxUInt32 } +var maxOffsetOfCentralDirectory: UInt32 { maxUInt32 } +var maxSizeOfCentralDirectory: UInt32 { maxUInt32 } +var maxTotalNumberOfEntries: UInt16 { maxUInt16 } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive.swift new file mode 100644 index 0000000000000000000000000000000000000000..bb46a7c9b3bbbc871bae09520d7ef27f6e5bcaf6 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Archive.swift @@ -0,0 +1,398 @@ +// +// Archive.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +/// The default chunk size when reading entry data from an archive. +let defaultReadChunkSize = Int(16 * 1024) +/// The default chunk size when writing entry data to an archive. +let defaultWriteChunkSize = defaultReadChunkSize +/// The default permissions for newly added entries. +let defaultFilePermissions = UInt16(0o644) +/// The default permissions for newly added directories. +let defaultDirectoryPermissions = UInt16(0o755) +let defaultPOSIXBufferSize = defaultReadChunkSize +let defaultDirectoryUnitCount = Int64(1) +let minEndOfCentralDirectoryOffset = Int64(22) +let endOfCentralDirectoryStructSignature = 0x06054b50 +let localFileHeaderStructSignature = 0x04034b50 +let dataDescriptorStructSignature = 0x08074b50 +let centralDirectoryStructSignature = 0x02014b50 +let memoryURLScheme = "memory" + +// MARK: - Archive + +/// A sequence of uncompressed or compressed ZIP entries. +/// +/// You use an `Archive` to create, read or update ZIP files. +/// To read an existing ZIP file, you have to pass in an existing file `URL` and `AccessMode.read`: +/// +/// var archiveURL = URL(fileURLWithPath: "/path/file.zip") +/// var archive = Archive(url: archiveURL, accessMode: .read) +/// +/// An `Archive` is a sequence of entries. You can +/// iterate over an archive using a `for`-`in` loop to get access to individual `Entry` objects: +/// +/// for entry in archive { +/// print(entry.path) +/// } +/// +/// Each `Entry` in an `Archive` is represented by its `path`. You can +/// use `path` to retrieve the corresponding `Entry` from an `Archive` via subscripting: +/// +/// let entry = archive['/path/file.txt'] +/// +/// To create a new `Archive`, pass in a non-existing file URL and `AccessMode.create`. To modify an +/// existing `Archive` use `AccessMode.update`: +/// +/// var archiveURL = URL(fileURLWithPath: "/path/file.zip") +/// var archive = Archive(url: archiveURL, accessMode: .update) +/// try archive?.addEntry("test.txt", relativeTo: baseURL, compressionMethod: .deflate) +final class Archive: Sequence { + + // MARK: Lifecycle + + /// Initializes a new ZIP `Archive`. + /// + /// You can use this initalizer to create new archive files or to read and update existing ones. + /// The `mode` parameter indicates the intended usage of the archive: `.read`, `.create` or `.update`. + /// - Parameters: + /// - url: File URL to the receivers backing file. + /// - mode: Access mode of the receiver. + /// - preferredEncoding: Encoding for entry paths. Overrides the encoding specified in the archive. + /// This encoding is only used when _decoding_ paths from the receiver. + /// Paths of entries added with `addEntry` are always UTF-8 encoded. + /// - Returns: An archive initialized with a backing file at the passed in file URL and the given access mode + /// or `nil` if the following criteria are not met: + /// - Note: + /// - The file URL _must_ point to an existing file for `AccessMode.read`. + /// - The file URL _must_ point to a non-existing file for `AccessMode.create`. + /// - The file URL _must_ point to an existing file for `AccessMode.update`. + init?(url: URL, accessMode mode: AccessMode, preferredEncoding: String.Encoding? = nil) { + self.url = url + accessMode = mode + self.preferredEncoding = preferredEncoding + guard let config = Archive.makeBackingConfiguration(for: url, mode: mode) else { + return nil + } + archiveFile = config.file + endOfCentralDirectoryRecord = config.endOfCentralDirectoryRecord + zip64EndOfCentralDirectory = config.zip64EndOfCentralDirectory + setvbuf(archiveFile, nil, _IOFBF, Int(defaultPOSIXBufferSize)) + } + + deinit { + fclose(self.archiveFile) + } + + // MARK: Internal + + typealias LocalFileHeader = Entry.LocalFileHeader + typealias DataDescriptor = Entry.DefaultDataDescriptor + typealias ZIP64DataDescriptor = Entry.ZIP64DataDescriptor + typealias CentralDirectoryStructure = Entry.CentralDirectoryStructure + + /// An error that occurs during reading, creating or updating a ZIP file. + enum ArchiveError: Error { + /// Thrown when an archive file is either damaged or inaccessible. + case unreadableArchive + /// Thrown when an archive is either opened with AccessMode.read or the destination file is unwritable. + case unwritableArchive + /// Thrown when the path of an `Entry` cannot be stored in an archive. + case invalidEntryPath + /// Thrown when an `Entry` can't be stored in the archive with the proposed compression method. + case invalidCompressionMethod + /// Thrown when the stored checksum of an `Entry` doesn't match the checksum during reading. + case invalidCRC32 + /// Thrown when an extract, add or remove operation was canceled. + case cancelledOperation + /// Thrown when an extract operation was called with zero or negative `bufferSize` parameter. + case invalidBufferSize + /// Thrown when uncompressedSize/compressedSize exceeds `Int64.max` (Imposed by file API). + case invalidEntrySize + /// Thrown when the offset of local header data exceeds `Int64.max` (Imposed by file API). + case invalidLocalHeaderDataOffset + /// Thrown when the size of local header exceeds `Int64.max` (Imposed by file API). + case invalidLocalHeaderSize + /// Thrown when the offset of central directory exceeds `Int64.max` (Imposed by file API). + case invalidCentralDirectoryOffset + /// Thrown when the size of central directory exceeds `UInt64.max` (Imposed by ZIP specification). + case invalidCentralDirectorySize + /// Thrown when number of entries in central directory exceeds `UInt64.max` (Imposed by ZIP specification). + case invalidCentralDirectoryEntryCount + /// Thrown when an archive does not contain the required End of Central Directory Record. + case missingEndOfCentralDirectoryRecord + } + + /// The access mode for an `Archive`. + enum AccessMode: UInt { + /// Indicates that a newly instantiated `Archive` should create its backing file. + case create + /// Indicates that a newly instantiated `Archive` should read from an existing backing file. + case read + /// Indicates that a newly instantiated `Archive` should update an existing backing file. + case update + } + + /// The version of an `Archive` + enum Version: UInt16 { + /// The minimum version for deflate compressed archives + case v20 = 20 + /// The minimum version for archives making use of ZIP64 extensions + case v45 = 45 + } + + struct EndOfCentralDirectoryRecord: DataSerializable { + let endOfCentralDirectorySignature = UInt32(endOfCentralDirectoryStructSignature) + let numberOfDisk: UInt16 + let numberOfDiskStart: UInt16 + let totalNumberOfEntriesOnDisk: UInt16 + let totalNumberOfEntriesInCentralDirectory: UInt16 + let sizeOfCentralDirectory: UInt32 + let offsetToStartOfCentralDirectory: UInt32 + let zipFileCommentLength: UInt16 + let zipFileCommentData: Data + static let size = 22 + } + + /// URL of an Archive's backing file. + let url: URL + /// Access mode for an archive file. + let accessMode: AccessMode + var archiveFile: FILEPointer + var endOfCentralDirectoryRecord: EndOfCentralDirectoryRecord + var zip64EndOfCentralDirectory: ZIP64EndOfCentralDirectory? + var preferredEncoding: String.Encoding? + + var totalNumberOfEntriesInCentralDirectory: UInt64 { + zip64EndOfCentralDirectory?.record.totalNumberOfEntriesInCentralDirectory + ?? UInt64(endOfCentralDirectoryRecord.totalNumberOfEntriesInCentralDirectory) + } + + var sizeOfCentralDirectory: UInt64 { + zip64EndOfCentralDirectory?.record.sizeOfCentralDirectory + ?? UInt64(endOfCentralDirectoryRecord.sizeOfCentralDirectory) + } + + var offsetToStartOfCentralDirectory: UInt64 { + zip64EndOfCentralDirectory?.record.offsetToStartOfCentralDirectory + ?? UInt64(endOfCentralDirectoryRecord.offsetToStartOfCentralDirectory) + } + + #if swift(>=5.0) + var memoryFile: MemoryFile? + + /// Initializes a new in-memory ZIP `Archive`. + /// + /// You can use this initalizer to create new in-memory archive files or to read and update existing ones. + /// + /// - Parameters: + /// - data: `Data` object used as backing for in-memory archives. + /// - mode: Access mode of the receiver. + /// - preferredEncoding: Encoding for entry paths. Overrides the encoding specified in the archive. + /// This encoding is only used when _decoding_ paths from the receiver. + /// Paths of entries added with `addEntry` are always UTF-8 encoded. + /// - Returns: An in-memory archive initialized with passed in backing data. + /// - Note: + /// - The backing `data` _must_ contain a valid ZIP archive for `AccessMode.read` and `AccessMode.update`. + /// - The backing `data` _must_ be empty (or omitted) for `AccessMode.create`. + init?(data: Data = Data(), accessMode mode: AccessMode, preferredEncoding: String.Encoding? = nil) { + guard + let url = URL(string: "\(memoryURLScheme)://"), + let config = Archive.makeBackingConfiguration(for: data, mode: mode) + else { + return nil + } + + self.url = url + accessMode = mode + self.preferredEncoding = preferredEncoding + archiveFile = config.file + memoryFile = config.memoryFile + endOfCentralDirectoryRecord = config.endOfCentralDirectoryRecord + zip64EndOfCentralDirectory = config.zip64EndOfCentralDirectory + } + #endif + + // MARK: - Helpers + + static func scanForEndOfCentralDirectoryRecord(in file: FILEPointer) + -> EndOfCentralDirectoryStructure? + { + var eocdOffset: UInt64 = 0 + var index = minEndOfCentralDirectoryOffset + fseeko(file, 0, SEEK_END) + let archiveLength = Int64(ftello(file)) + while eocdOffset == 0, index <= archiveLength { + fseeko(file, off_t(archiveLength - index), SEEK_SET) + var potentialDirectoryEndTag = UInt32() + fread(&potentialDirectoryEndTag, 1, MemoryLayout.size, file) + if potentialDirectoryEndTag == UInt32(endOfCentralDirectoryStructSignature) { + eocdOffset = UInt64(archiveLength - index) + guard let eocd: EndOfCentralDirectoryRecord = Data.readStruct(from: file, at: eocdOffset) else { + return nil + } + let zip64EOCD = scanForZIP64EndOfCentralDirectory(in: file, eocdOffset: eocdOffset) + return (eocd, zip64EOCD) + } + index += 1 + } + return nil + } + + func makeIterator() -> AnyIterator { + let totalNumberOfEntriesInCD = totalNumberOfEntriesInCentralDirectory + var directoryIndex = offsetToStartOfCentralDirectory + var index = 0 + return AnyIterator { + guard index < totalNumberOfEntriesInCD else { return nil } + guard + let centralDirStruct: CentralDirectoryStructure = Data.readStruct( + from: self.archiveFile, + at: directoryIndex) + else { + return nil + } + let offset = UInt64(centralDirStruct.effectiveRelativeOffsetOfLocalHeader) + guard + let localFileHeader: LocalFileHeader = Data.readStruct( + from: self.archiveFile, + at: offset) + else { return nil } + var dataDescriptor: DataDescriptor? + var zip64DataDescriptor: ZIP64DataDescriptor? + if centralDirStruct.usesDataDescriptor { + let additionalSize = UInt64(localFileHeader.fileNameLength) + UInt64(localFileHeader.extraFieldLength) + let isCompressed = centralDirStruct.compressionMethod != CompressionMethod.none.rawValue + let dataSize = isCompressed + ? centralDirStruct.effectiveCompressedSize + : centralDirStruct.effectiveUncompressedSize + let descriptorPosition = offset + UInt64(LocalFileHeader.size) + additionalSize + dataSize + if centralDirStruct.isZIP64 { + zip64DataDescriptor = Data.readStruct(from: self.archiveFile, at: descriptorPosition) + } else { + dataDescriptor = Data.readStruct(from: self.archiveFile, at: descriptorPosition) + } + } + defer { + directoryIndex += UInt64(CentralDirectoryStructure.size) + directoryIndex += UInt64(centralDirStruct.fileNameLength) + directoryIndex += UInt64(centralDirStruct.extraFieldLength) + directoryIndex += UInt64(centralDirStruct.fileCommentLength) + index += 1 + } + return Entry( + centralDirectoryStructure: centralDirStruct, + localFileHeader: localFileHeader, + dataDescriptor: dataDescriptor, + zip64DataDescriptor: zip64DataDescriptor) + } + } + + /// Retrieve the ZIP `Entry` with the given `path` from the receiver. + /// + /// - Note: The ZIP file format specification does not enforce unique paths for entries. + /// Therefore an archive can contain multiple entries with the same path. This method + /// always returns the first `Entry` with the given `path`. + /// + /// - Parameter path: A relative file path identifying the corresponding `Entry`. + /// - Returns: An `Entry` with the given `path`. Otherwise, `nil`. + subscript(path: String) -> Entry? { + if let encoding = preferredEncoding { + return first { $0.path(using: encoding) == path } + } + return first { $0.path == path } + } + + // MARK: Private + + private static func scanForZIP64EndOfCentralDirectory(in file: FILEPointer, eocdOffset: UInt64) + -> ZIP64EndOfCentralDirectory? + { + guard UInt64(ZIP64EndOfCentralDirectoryLocator.size) < eocdOffset else { + return nil + } + let locatorOffset = eocdOffset - UInt64(ZIP64EndOfCentralDirectoryLocator.size) + + guard UInt64(ZIP64EndOfCentralDirectoryRecord.size) < locatorOffset else { + return nil + } + let recordOffset = locatorOffset - UInt64(ZIP64EndOfCentralDirectoryRecord.size) + guard + let locator: ZIP64EndOfCentralDirectoryLocator = Data.readStruct(from: file, at: locatorOffset), + let record: ZIP64EndOfCentralDirectoryRecord = Data.readStruct(from: file, at: recordOffset) + else { + return nil + } + return ZIP64EndOfCentralDirectory(record: record, locator: locator) + } +} + +extension Archive.EndOfCentralDirectoryRecord { + + // MARK: Lifecycle + + init?(data: Data, additionalDataProvider provider: (Int) throws -> Data) { + guard data.count == Archive.EndOfCentralDirectoryRecord.size else { return nil } + guard data.scanValue(start: 0) == endOfCentralDirectorySignature else { return nil } + numberOfDisk = data.scanValue(start: 4) + numberOfDiskStart = data.scanValue(start: 6) + totalNumberOfEntriesOnDisk = data.scanValue(start: 8) + totalNumberOfEntriesInCentralDirectory = data.scanValue(start: 10) + sizeOfCentralDirectory = data.scanValue(start: 12) + offsetToStartOfCentralDirectory = data.scanValue(start: 16) + zipFileCommentLength = data.scanValue(start: 20) + guard let commentData = try? provider(Int(zipFileCommentLength)) else { return nil } + guard commentData.count == Int(zipFileCommentLength) else { return nil } + zipFileCommentData = commentData + } + + init( + record: Archive.EndOfCentralDirectoryRecord, + numberOfEntriesOnDisk: UInt16, + numberOfEntriesInCentralDirectory: UInt16, + updatedSizeOfCentralDirectory: UInt32, + startOfCentralDirectory: UInt32) + { + numberOfDisk = record.numberOfDisk + numberOfDiskStart = record.numberOfDiskStart + totalNumberOfEntriesOnDisk = numberOfEntriesOnDisk + totalNumberOfEntriesInCentralDirectory = numberOfEntriesInCentralDirectory + sizeOfCentralDirectory = updatedSizeOfCentralDirectory + offsetToStartOfCentralDirectory = startOfCentralDirectory + zipFileCommentLength = record.zipFileCommentLength + zipFileCommentData = record.zipFileCommentData + } + + // MARK: Internal + + var data: Data { + var endOfCDSignature = endOfCentralDirectorySignature + var numberOfDisk = numberOfDisk + var numberOfDiskStart = numberOfDiskStart + var totalNumberOfEntriesOnDisk = totalNumberOfEntriesOnDisk + var totalNumberOfEntriesInCD = totalNumberOfEntriesInCentralDirectory + var sizeOfCentralDirectory = sizeOfCentralDirectory + var offsetToStartOfCD = offsetToStartOfCentralDirectory + var zipFileCommentLength = zipFileCommentLength + var data = Data() + withUnsafePointer(to: &endOfCDSignature) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &numberOfDisk) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &numberOfDiskStart) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &totalNumberOfEntriesOnDisk) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &totalNumberOfEntriesInCD) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &sizeOfCentralDirectory) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &offsetToStartOfCD) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &zipFileCommentLength) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + data.append(zipFileCommentData) + return data + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+Compression.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+Compression.swift new file mode 100644 index 0000000000000000000000000000000000000000..db9b73dd1c7e49fc74f2342d00d43a81f9305a4b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+Compression.swift @@ -0,0 +1,404 @@ +// +// Data+Compression.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +#if canImport(zlib) +import zlib +#endif + +// MARK: - CompressionMethod + +/// The compression method of an `Entry` in a ZIP `Archive`. +enum CompressionMethod: UInt16 { + /// Indicates that an `Entry` has no compression applied to its contents. + case none = 0 + /// Indicates that contents of an `Entry` have been compressed with a zlib compatible Deflate algorithm. + case deflate = 8 +} + +/// An unsigned 32-Bit Integer representing a checksum. +typealias CRC32 = UInt32 +/// A custom handler that consumes a `Data` object containing partial entry data. +/// - Parameters: +/// - data: A chunk of `Data` to consume. +/// - Throws: Can throw to indicate errors during data consumption. +typealias Consumer = (_ data: Data) throws -> Void +/// A custom handler that receives a position and a size that can be used to provide data from an arbitrary source. +/// - Parameters: +/// - position: The current read position. +/// - size: The size of the chunk to provide. +/// - Returns: A chunk of `Data`. +/// - Throws: Can throw to indicate errors in the data source. +typealias Provider = (_ position: Int64, _ size: Int) throws -> Data + +extension Data { + enum CompressionError: Error { + case invalidStream + case corruptedData + } + + /// Compress the output of `provider` and pass it to `consumer`. + /// - Parameters: + /// - size: The uncompressed size of the data to be compressed. + /// - bufferSize: The maximum size of the compression buffer. + /// - provider: A closure that accepts a position and a chunk size. Returns a `Data` chunk. + /// - consumer: A closure that processes the result of the compress operation. + /// - Returns: The checksum of the processed content. + static func compress(size: Int64, bufferSize: Int, provider: Provider, consumer: Consumer) throws -> CRC32 { + #if os(macOS) || canImport(UIKit) + return try process( + operation: COMPRESSION_STREAM_ENCODE, + size: size, + bufferSize: bufferSize, + provider: provider, + consumer: consumer) + #else + return try encode(size: size, bufferSize: bufferSize, provider: provider, consumer: consumer) + #endif + } + + /// Decompress the output of `provider` and pass it to `consumer`. + /// - Parameters: + /// - size: The compressed size of the data to be decompressed. + /// - bufferSize: The maximum size of the decompression buffer. + /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance. + /// - provider: A closure that accepts a position and a chunk size. Returns a `Data` chunk. + /// - consumer: A closure that processes the result of the decompress operation. + /// - Returns: The checksum of the processed content. + static func decompress( + size: Int64, + bufferSize: Int, + skipCRC32: Bool, + provider: Provider, + consumer: Consumer) + throws -> CRC32 + { + #if os(macOS) || canImport(UIKit) + return try process( + operation: COMPRESSION_STREAM_DECODE, + size: size, + bufferSize: bufferSize, + skipCRC32: skipCRC32, + provider: provider, + consumer: consumer) + #else + return try decode(bufferSize: bufferSize, skipCRC32: skipCRC32, provider: provider, consumer: consumer) + #endif + } + + /// Calculate the `CRC32` checksum of the receiver. + /// + /// - Parameter checksum: The starting seed. + /// - Returns: The checksum calculated from the bytes of the receiver and the starting seed. + func crc32(checksum: CRC32) -> CRC32 { + #if canImport(zlib) + return withUnsafeBytes { bufferPointer in + let length = UInt32(count) + return CRC32(zlib.crc32(UInt(checksum), bufferPointer.bindMemory(to: UInt8.self).baseAddress, length)) + } + #else + return builtInCRC32(checksum: checksum) + #endif + } + +} + +// MARK: - Apple Platforms + +#if os(macOS) || canImport(UIKit) +import Compression + +extension Data { + + static func process( + operation: compression_stream_operation, + size: Int64, + bufferSize: Int, + skipCRC32: Bool = false, + provider: Provider, + consumer: Consumer) + throws -> CRC32 + { + var crc32 = CRC32(0) + let destPointer = UnsafeMutablePointer.allocate(capacity: bufferSize) + defer { destPointer.deallocate() } + let streamPointer = UnsafeMutablePointer.allocate(capacity: 1) + defer { streamPointer.deallocate() } + var stream = streamPointer.pointee + var status = compression_stream_init(&stream, operation, COMPRESSION_ZLIB) + guard status != COMPRESSION_STATUS_ERROR else { throw CompressionError.invalidStream } + defer { compression_stream_destroy(&stream) } + stream.src_size = 0 + stream.dst_ptr = destPointer + stream.dst_size = bufferSize + var position: Int64 = 0 + var sourceData: Data? + repeat { + let isExhausted = stream.src_size == 0 + if isExhausted { + do { + sourceData = try provider(position, Int(Swift.min(size - position, Int64(bufferSize)))) + position += Int64(stream.prepare(for: sourceData)) + } catch { throw error } + } + if let sourceData { + sourceData.withUnsafeBytes { rawBufferPointer in + if let baseAddress = rawBufferPointer.baseAddress { + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + stream.src_ptr = pointer.advanced(by: sourceData.count - stream.src_size) + let flags = sourceData.count < bufferSize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0 + status = compression_stream_process(&stream, flags) + } + } + if + operation == COMPRESSION_STREAM_ENCODE, + isExhausted, skipCRC32 == false { crc32 = sourceData.crc32(checksum: crc32) } + } + switch status { + case COMPRESSION_STATUS_OK, COMPRESSION_STATUS_END: + let outputData = Data(bytesNoCopy: destPointer, count: bufferSize - stream.dst_size, deallocator: .none) + try consumer(outputData) + if operation == COMPRESSION_STREAM_DECODE, !skipCRC32 { crc32 = outputData.crc32(checksum: crc32) } + stream.dst_ptr = destPointer + stream.dst_size = bufferSize + + default: throw CompressionError.corruptedData + } + } while status == COMPRESSION_STATUS_OK + return crc32 + } +} + +extension compression_stream { + + fileprivate mutating func prepare(for sourceData: Data?) -> Int { + guard let sourceData else { return 0 } + + src_size = sourceData.count + return sourceData.count + } +} + +// MARK: - Linux + +#else +import CZlib + +extension Data { + static func encode(size: Int64, bufferSize: Int, provider: Provider, consumer: Consumer) throws -> CRC32 { + var stream = z_stream() + let streamSize = Int32(MemoryLayout.size) + var result = deflateInit2_( + &stream, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + -MAX_WBITS, + 9, + Z_DEFAULT_STRATEGY, + ZLIB_VERSION, + streamSize) + defer { deflateEnd(&stream) } + guard result == Z_OK else { throw CompressionError.invalidStream } + var flush = Z_NO_FLUSH + var position: Int64 = 0 + var zipCRC32 = CRC32(0) + repeat { + let readSize = Int(Swift.min(size - position, Int64(bufferSize))) + var inputChunk = try provider(position, readSize) + zipCRC32 = inputChunk.crc32(checksum: zipCRC32) + stream.avail_in = UInt32(inputChunk.count) + try inputChunk.withUnsafeMutableBytes { rawBufferPointer in + if let baseAddress = rawBufferPointer.baseAddress { + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + stream.next_in = pointer + flush = position + Int64(bufferSize) >= size ? Z_FINISH : Z_NO_FLUSH + } else if rawBufferPointer.count > 0 { + throw CompressionError.corruptedData + } else { + stream.next_in = nil + flush = Z_FINISH + } + var outputChunk = Data(count: bufferSize) + repeat { + stream.avail_out = UInt32(bufferSize) + try outputChunk.withUnsafeMutableBytes { rawBufferPointer in + guard let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 else { + throw CompressionError.corruptedData + } + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + stream.next_out = pointer + result = deflate(&stream, flush) + } + guard result >= Z_OK else { throw CompressionError.corruptedData } + + outputChunk.count = bufferSize - Int(stream.avail_out) + try consumer(outputChunk) + } while stream.avail_out == 0 + } + position += Int64(readSize) + } while flush != Z_FINISH + return zipCRC32 + } + + static func decode(bufferSize: Int, skipCRC32: Bool, provider: Provider, consumer: Consumer) throws -> CRC32 { + var stream = z_stream() + let streamSize = Int32(MemoryLayout.size) + var result = inflateInit2_(&stream, -MAX_WBITS, ZLIB_VERSION, streamSize) + defer { inflateEnd(&stream) } + guard result == Z_OK else { throw CompressionError.invalidStream } + var unzipCRC32 = CRC32(0) + var position: Int64 = 0 + repeat { + stream.avail_in = UInt32(bufferSize) + var chunk = try provider(position, bufferSize) + position += Int64(chunk.count) + try chunk.withUnsafeMutableBytes { rawBufferPointer in + if let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 { + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + stream.next_in = pointer + repeat { + var outputData = Data(count: bufferSize) + stream.avail_out = UInt32(bufferSize) + try outputData.withUnsafeMutableBytes { rawBufferPointer in + if let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 { + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + stream.next_out = pointer + } else { + throw CompressionError.corruptedData + } + result = inflate(&stream, Z_NO_FLUSH) + guard + result != Z_NEED_DICT, + result != Z_DATA_ERROR, + result != Z_MEM_ERROR + else { + throw CompressionError.corruptedData + } + } + let remainingLength = UInt32(bufferSize) - stream.avail_out + outputData.count = Int(remainingLength) + try consumer(outputData) + if !skipCRC32 { unzipCRC32 = outputData.crc32(checksum: unzipCRC32) } + } while stream.avail_out == 0 + } + } + } while result != Z_STREAM_END + return unzipCRC32 + } +} + +#endif + +/// The lookup table used to calculate `CRC32` checksums when using the built-in +/// CRC32 implementation. +private let crcTable: [CRC32] = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, + 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, + 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, + 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, + 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, + 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, + 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, + 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, + 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, + 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +] + +extension Data { + + /// Lookup table-based CRC32 implenetation that is used + /// if `zlib` isn't available. + /// - Parameter checksum: Running checksum or `0` for the initial run. + /// - Returns: The calculated checksum of the receiver. + func builtInCRC32(checksum: CRC32) -> CRC32 { + // The typecast is necessary on 32-bit platforms because of + // https://bugs.swift.org/browse/SR-1774 + let mask = 0xffffffff as CRC32 + var result = checksum ^ mask + #if swift(>=5.0) + crcTable.withUnsafeBufferPointer { crcTablePointer in + self.withUnsafeBytes { bufferPointer in + var bufferIndex = 0 + while bufferIndex < self.count { + let byte = bufferPointer[bufferIndex] + let index = Int((result ^ CRC32(byte)) & 0xff) + result = (result >> 8) ^ crcTablePointer[index] + bufferIndex += 1 + } + } + } + #else + withUnsafeBytes { bytes in + let bins = stride(from: 0, to: self.count, by: 256) + for bin in bins { + for binIndex in 0..<256 { + let byteIndex = bin + binIndex + guard byteIndex < self.count else { break } + + let byte = bytes[byteIndex] + let index = Int((result ^ CRC32(byte)) & 0xff) + result = (result >> 8) ^ crcTable[index] + } + } + } + #endif + return result ^ mask + } +} + +#if !swift(>=5.0) + +// Since Swift 5.0, `Data.withUnsafeBytes()` passes an `UnsafeRawBufferPointer` instead of an `UnsafePointer` +// into `body`. +// We provide a compatible method for targets that use Swift 4.x so that we can use the new version +// across all language versions. + +extension Data { + func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T { + let count = count + return try withUnsafeBytes { (pointer: UnsafePointer) throws -> T in + try body(UnsafeRawBufferPointer(start: pointer, count: count)) + } + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + #else + mutating func withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T { + let count = count + guard count > 0 else { + return try body(UnsafeMutableRawBufferPointer(start: nil, count: count)) + } + return try withUnsafeMutableBytes { (pointer: UnsafeMutablePointer) throws -> T in + try body(UnsafeMutableRawBufferPointer(start: pointer, count: count)) + } + } + #endif +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+CompressionDeprecated.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+CompressionDeprecated.swift new file mode 100644 index 0000000000000000000000000000000000000000..43844b32a2518ff8b5035c38bda5580dbe450653 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+CompressionDeprecated.swift @@ -0,0 +1,44 @@ +// +// Data+CompressionDeprecated.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Data { + + @available(*, deprecated, message: "Please use `Int64` for `size` and provider `position`.") + static func compress( + size: Int, + bufferSize: Int, + provider: (_ position: Int, _ size: Int) throws -> Data, + consumer: Consumer) + throws -> CRC32 + { + let newProvider: Provider = { try provider(Int($0), $1) } + return try compress(size: Int64(size), bufferSize: bufferSize, provider: newProvider, consumer: consumer) + } + + @available(*, deprecated, message: "Please use `Int64` for `size` and provider `position`.") + static func decompress( + size: Int, + bufferSize: Int, + skipCRC32: Bool, + provider: (_ position: Int, _ size: Int) throws -> Data, + consumer: Consumer) + throws -> CRC32 + { + let newProvider: Provider = { try provider(Int($0), $1) } + return try decompress( + size: Int64(size), + bufferSize: bufferSize, + skipCRC32: skipCRC32, + provider: newProvider, + consumer: consumer) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+Serialization.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+Serialization.swift new file mode 100644 index 0000000000000000000000000000000000000000..94479228f4f7e9bf01a6c3c33fe641e00c929886 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Data+Serialization.swift @@ -0,0 +1,149 @@ +// +// Data+Serialization.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +#if os(Android) +typealias FILEPointer = OpaquePointer +#else +typealias FILEPointer = UnsafeMutablePointer +#endif + +// MARK: - DataSerializable + +protocol DataSerializable { + static var size: Int { get } + init?(data: Data, additionalDataProvider: (Int) throws -> Data) + var data: Data { get } +} + +extension Data { + enum DataError: Error { + case unreadableFile + case unwritableFile + } + + static func readStruct(from file: FILEPointer, at offset: UInt64) + -> T? where T: DataSerializable + { + guard offset <= .max else { return nil } + fseeko(file, off_t(offset), SEEK_SET) + guard let data = try? readChunk(of: T.size, from: file) else { + return nil + } + let structure = T(data: data, additionalDataProvider: { additionalDataSize -> Data in + try self.readChunk(of: additionalDataSize, from: file) + }) + return structure + } + + static func consumePart( + of size: Int64, + chunkSize: Int, + skipCRC32: Bool = false, + provider: Provider, + consumer: Consumer) + throws -> CRC32 + { + var checksum = CRC32(0) + guard size > 0 else { + try consumer(Data()) + return checksum + } + + let readInOneChunk = (size < chunkSize) + var chunkSize = readInOneChunk ? Int(size) : chunkSize + var bytesRead: Int64 = 0 + while bytesRead < size { + let remainingSize = size - bytesRead + chunkSize = remainingSize < chunkSize ? Int(remainingSize) : chunkSize + let data = try provider(bytesRead, chunkSize) + try consumer(data) + if !skipCRC32 { + checksum = data.crc32(checksum: checksum) + } + bytesRead += Int64(chunkSize) + } + return checksum + } + + static func readChunk(of size: Int, from file: FILEPointer) throws -> Data { + let alignment = MemoryLayout.alignment + #if swift(>=4.1) + let bytes = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment) + #else + let bytes = UnsafeMutableRawPointer.allocate(bytes: size, alignedTo: alignment) + #endif + let bytesRead = fread(bytes, 1, size, file) + let error = ferror(file) + if error > 0 { + throw DataError.unreadableFile + } + #if swift(>=4.1) + return Data(bytesNoCopy: bytes, count: bytesRead, deallocator: .custom { buf, _ in buf.deallocate() }) + #else + let deallocator = Deallocator.custom { buf, _ in buf.deallocate(bytes: size, alignedTo: 1) } + return Data(bytesNoCopy: bytes, count: bytesRead, deallocator: deallocator) + #endif + } + + static func write(chunk: Data, to file: FILEPointer) throws -> Int { + var sizeWritten = 0 + chunk.withUnsafeBytes { rawBufferPointer in + if let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 { + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + sizeWritten = fwrite(pointer, 1, chunk.count, file) + } + } + let error = ferror(file) + if error > 0 { + throw DataError.unwritableFile + } + return sizeWritten + } + + static func writeLargeChunk( + _ chunk: Data, + size: UInt64, + bufferSize: Int, + to file: FILEPointer) + throws -> UInt64 + { + var sizeWritten: UInt64 = 0 + chunk.withUnsafeBytes { rawBufferPointer in + if let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 { + let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) + + while sizeWritten < size { + let remainingSize = size - sizeWritten + let chunkSize = Swift.min(Int(remainingSize), bufferSize) + let curPointer = pointer.advanced(by: Int(sizeWritten)) + fwrite(curPointer, 1, chunkSize, file) + sizeWritten += UInt64(chunkSize) + } + } + } + let error = ferror(file) + if error > 0 { + throw DataError.unwritableFile + } + return sizeWritten + } + + func scanValue(start: Int) -> T { + let subdata = subdata(in: start...size) + #if swift(>=5.0) + return subdata.withUnsafeBytes { $0.load(as: T.self) } + #else + return subdata.withUnsafeBytes { $0.pointee } + #endif + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Entry+Serialization.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Entry+Serialization.swift new file mode 100644 index 0000000000000000000000000000000000000000..8c22c1324783d746e3722be0f55bf0cf88d6abbd --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Entry+Serialization.swift @@ -0,0 +1,189 @@ +// +// Entry+Serialization.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension Entry.LocalFileHeader { + + // MARK: Lifecycle + + init?(data: Data, additionalDataProvider provider: (Int) throws -> Data) { + guard data.count == Entry.LocalFileHeader.size else { return nil } + guard data.scanValue(start: 0) == localFileHeaderSignature else { return nil } + versionNeededToExtract = data.scanValue(start: 4) + generalPurposeBitFlag = data.scanValue(start: 6) + compressionMethod = data.scanValue(start: 8) + lastModFileTime = data.scanValue(start: 10) + lastModFileDate = data.scanValue(start: 12) + crc32 = data.scanValue(start: 14) + compressedSize = data.scanValue(start: 18) + uncompressedSize = data.scanValue(start: 22) + fileNameLength = data.scanValue(start: 26) + extraFieldLength = data.scanValue(start: 28) + let additionalDataLength = Int(fileNameLength) + Int(extraFieldLength) + guard let additionalData = try? provider(additionalDataLength) else { return nil } + guard additionalData.count == additionalDataLength else { return nil } + var subRangeStart = 0 + var subRangeEnd = Int(fileNameLength) + fileNameData = additionalData.subdata(in: subRangeStart.. Data) { + guard data.count == Entry.CentralDirectoryStructure.size else { return nil } + guard data.scanValue(start: 0) == centralDirectorySignature else { return nil } + versionMadeBy = data.scanValue(start: 4) + versionNeededToExtract = data.scanValue(start: 6) + generalPurposeBitFlag = data.scanValue(start: 8) + compressionMethod = data.scanValue(start: 10) + lastModFileTime = data.scanValue(start: 12) + lastModFileDate = data.scanValue(start: 14) + crc32 = data.scanValue(start: 16) + compressedSize = data.scanValue(start: 20) + uncompressedSize = data.scanValue(start: 24) + fileNameLength = data.scanValue(start: 28) + extraFieldLength = data.scanValue(start: 30) + fileCommentLength = data.scanValue(start: 32) + diskNumberStart = data.scanValue(start: 34) + internalFileAttributes = data.scanValue(start: 36) + externalFileAttributes = data.scanValue(start: 38) + relativeOffsetOfLocalHeader = data.scanValue(start: 42) + let additionalDataLength = Int(fileNameLength) + Int(extraFieldLength) + Int(fileCommentLength) + guard let additionalData = try? provider(additionalDataLength) else { return nil } + guard additionalData.count == additionalDataLength else { return nil } + var subRangeStart = 0 + var subRangeEnd = Int(fileNameLength) + fileNameData = additionalData.subdata(in: subRangeStart.. Data) { + guard data.count == Self.size else { return nil } + let signature: UInt32 = data.scanValue(start: 0) + // The DataDescriptor signature is not mandatory so we have to re-arrange the input data if it is missing. + var readOffset = 0 + if signature == dataDescriptorSignature { readOffset = 4 } + crc32 = data.scanValue(start: readOffset) + readOffset += MemoryLayout.size + compressedSize = data.scanValue(start: readOffset) + readOffset += Self.memoryLengthOfSize + uncompressedSize = data.scanValue(start: readOffset) + // Our add(_ entry:) methods always maintain compressed & uncompressed + // sizes and so we don't need a data descriptor for newly added entries. + // Data descriptors of already existing entries are manually preserved + // when copying those entries to the tempArchive during remove(_ entry:). + self.data = Data() + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Entry+ZIP64.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Entry+ZIP64.swift new file mode 100644 index 0000000000000000000000000000000000000000..bbebc91f82b006ef5fd82677a6b5ccc2d7522c3f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/Entry+ZIP64.swift @@ -0,0 +1,173 @@ +// +// Entry+ZIP64.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +// MARK: - ExtensibleDataField + +protocol ExtensibleDataField { + var headerID: UInt16 { get } + var dataSize: UInt16 { get } +} + +extension Entry { + enum EntryError: Error { + case invalidDataError + } + + struct ZIP64ExtendedInformation: ExtensibleDataField { + let headerID: UInt16 = ExtraFieldHeaderID.zip64ExtendedInformation.rawValue + let dataSize: UInt16 + static let headerSize: UInt16 = 4 + let uncompressedSize: UInt64 + let compressedSize: UInt64 + let relativeOffsetOfLocalHeader: UInt64 + let diskNumberStart: UInt32 + } + + var zip64ExtendedInformation: ZIP64ExtendedInformation? { + centralDirectoryStructure.zip64ExtendedInformation + } +} + +typealias Field = Entry.ZIP64ExtendedInformation.Field + +extension Entry.LocalFileHeader { + var validFields: [Field] { + var fields: [Field] = [] + if uncompressedSize == .max { fields.append(.uncompressedSize) } + if compressedSize == .max { fields.append(.compressedSize) } + return fields + } +} + +extension Entry.CentralDirectoryStructure { + var validFields: [Field] { + var fields: [Field] = [] + if uncompressedSize == .max { fields.append(.uncompressedSize) } + if compressedSize == .max { fields.append(.compressedSize) } + if relativeOffsetOfLocalHeader == .max { fields.append(.relativeOffsetOfLocalHeader) } + if diskNumberStart == .max { fields.append(.diskNumberStart) } + return fields + } + + var zip64ExtendedInformation: Entry.ZIP64ExtendedInformation? { + extraFields?.compactMap { $0 as? Entry.ZIP64ExtendedInformation }.first + } +} + +extension Entry.ZIP64ExtendedInformation { + + // MARK: Lifecycle + + init?(data: Data, fields: [Field]) { + let headerLength = 4 + guard fields.reduce(0, { $0 + $1.size }) + headerLength == data.count else { return nil } + var readOffset = headerLength + func value(of field: Field) throws -> T where T: BinaryInteger { + if fields.contains(field) { + defer { + readOffset += MemoryLayout.size + } + guard readOffset + field.size <= data.count else { + throw Entry.EntryError.invalidDataError + } + return data.scanValue(start: readOffset) + } else { + return 0 + } + } + do { + dataSize = data.scanValue(start: 2) + uncompressedSize = try value(of: .uncompressedSize) + compressedSize = try value(of: .compressedSize) + relativeOffsetOfLocalHeader = try value(of: .relativeOffsetOfLocalHeader) + diskNumberStart = try value(of: .diskNumberStart) + } catch { + return nil + } + } + + init?(zip64ExtendedInformation: Entry.ZIP64ExtendedInformation?, offset: UInt64) { + // Only used when removing entry, if no ZIP64 extended information exists, + // then this information will not be newly added either + guard let existingInfo = zip64ExtendedInformation else { return nil } + relativeOffsetOfLocalHeader = offset >= maxOffsetOfLocalFileHeader ? offset : 0 + uncompressedSize = existingInfo.uncompressedSize + compressedSize = existingInfo.compressedSize + diskNumberStart = existingInfo.diskNumberStart + let tempDataSize = [relativeOffsetOfLocalHeader, uncompressedSize, compressedSize] + .filter { $0 != 0 } + .reduce(UInt16(0)) { $0 + UInt16(MemoryLayout.size(ofValue: $1)) } + dataSize = tempDataSize + (diskNumberStart > 0 ? UInt16(MemoryLayout.size(ofValue: diskNumberStart)) : 0) + if dataSize == 0 { return nil } + } + + // MARK: Internal + + enum Field { + case uncompressedSize + case compressedSize + case relativeOffsetOfLocalHeader + case diskNumberStart + + var size: Int { + switch self { + case .uncompressedSize, .compressedSize, .relativeOffsetOfLocalHeader: + 8 + case .diskNumberStart: + 4 + } + } + } + + var data: Data { + var headerID = headerID + var dataSize = dataSize + var uncompressedSize = uncompressedSize + var compressedSize = compressedSize + var relativeOffsetOfLFH = relativeOffsetOfLocalHeader + var diskNumberStart = diskNumberStart + var data = Data() + withUnsafePointer(to: &headerID) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &dataSize) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + if uncompressedSize != 0 || compressedSize != 0 { + withUnsafePointer(to: &uncompressedSize) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + withUnsafePointer(to: &compressedSize) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + } + if relativeOffsetOfLocalHeader != 0 { + withUnsafePointer(to: &relativeOffsetOfLFH) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + } + if diskNumberStart != 0 { + withUnsafePointer(to: &diskNumberStart) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } + } + return data + } + + static func scanForZIP64Field(in data: Data, fields: [Field]) -> Entry.ZIP64ExtendedInformation? { + guard data.isEmpty == false else { return nil } + var offset = 0 + var headerID: UInt16 + var dataSize: UInt16 + let extraFieldLength = data.count + let headerSize = Int(Entry.ZIP64ExtendedInformation.headerSize) + while offset < extraFieldLength - headerSize { + headerID = data.scanValue(start: offset) + dataSize = data.scanValue(start: offset + 2) + let nextOffset = offset + headerSize + Int(dataSize) + guard nextOffset <= extraFieldLength else { return nil } + if headerID == ExtraFieldHeaderID.zip64ExtendedInformation.rawValue { + return Entry.ZIP64ExtendedInformation(data: data.subdata(in: offset..: DataSerializable { + let data: Data + let dataDescriptorSignature = UInt32(dataDescriptorStructSignature) + let crc32: UInt32 + // For normal archives, the compressed and uncompressed sizes are 4 bytes each. + // For ZIP64 format archives, the compressed and uncompressed sizes are 8 bytes each. + let compressedSize: T + let uncompressedSize: T + static var memoryLengthOfSize: Int { MemoryLayout.size } + static var size: Int { memoryLengthOfSize * 2 + 8 } + } + + typealias DefaultDataDescriptor = DataDescriptor + typealias ZIP64DataDescriptor = DataDescriptor + + struct CentralDirectoryStructure: DataSerializable { + static let size = 46 + + let centralDirectorySignature = UInt32(centralDirectoryStructSignature) + let versionMadeBy: UInt16 + let versionNeededToExtract: UInt16 + let generalPurposeBitFlag: UInt16 + let compressionMethod: UInt16 + let lastModFileTime: UInt16 + let lastModFileDate: UInt16 + let crc32: UInt32 + let compressedSize: UInt32 + let uncompressedSize: UInt32 + let fileNameLength: UInt16 + let extraFieldLength: UInt16 + let fileCommentLength: UInt16 + let diskNumberStart: UInt16 + let internalFileAttributes: UInt16 + let externalFileAttributes: UInt32 + let relativeOffsetOfLocalHeader: UInt32 + let fileNameData: Data + let extraFieldData: Data + let fileCommentData: Data + + var extraFields: [ExtensibleDataField]? + + var usesDataDescriptor: Bool { (generalPurposeBitFlag & (1 << 3)) != 0 } + var usesUTF8PathEncoding: Bool { (generalPurposeBitFlag & (1 << 11)) != 0 } + var isEncrypted: Bool { (generalPurposeBitFlag & (1 << 0)) != 0 } + var isZIP64: Bool { + // If ZIP64 extended information is existing, try to treat cd as ZIP64 format + // even if the version needed to extract is lower than 4.5 + UInt8(truncatingIfNeeded: versionNeededToExtract) >= 45 || zip64ExtendedInformation != nil + } + } + + let centralDirectoryStructure: CentralDirectoryStructure + let localFileHeader: LocalFileHeader + let dataDescriptor: DefaultDataDescriptor? + let zip64DataDescriptor: ZIP64DataDescriptor? + + /// The `path` of the receiver within a ZIP `Archive`. + var path: String { + let dosLatinUS = 0x400 + let dosLatinUSEncoding = CFStringEncoding(dosLatinUS) + let dosLatinUSStringEncoding = CFStringConvertEncodingToNSStringEncoding(dosLatinUSEncoding) + let codepage437 = String.Encoding(rawValue: dosLatinUSStringEncoding) + let encoding = centralDirectoryStructure.usesUTF8PathEncoding ? .utf8 : codepage437 + return self.path(using: encoding) + } + + /// The file attributes of the receiver as key/value pairs. + /// + /// Contains the modification date and file permissions. + var fileAttributes: [FileAttributeKey: Any] { + FileManager.attributes(from: self) + } + + /// The `CRC32` checksum of the receiver. + /// + /// - Note: Always returns `0` for entries of type `EntryType.directory`. + var checksum: CRC32 { + if centralDirectoryStructure.usesDataDescriptor { + return zip64DataDescriptor?.crc32 ?? dataDescriptor?.crc32 ?? 0 + } + return centralDirectoryStructure.crc32 + } + + /// The `EntryType` of the receiver. + var type: EntryType { + // OS Type is stored in the upper byte of versionMadeBy + let osTypeRaw = centralDirectoryStructure.versionMadeBy >> 8 + let osType = OSType(rawValue: UInt(osTypeRaw)) ?? .unused + var isDirectory = path.hasSuffix("/") + switch osType { + case .unix, .osx: + let mode = mode_t(centralDirectoryStructure.externalFileAttributes >> 16) & S_IFMT + switch mode { + case S_IFREG: + return .file + case S_IFDIR: + return .directory + case S_IFLNK: + return .symlink + default: + return isDirectory ? .directory : .file + } + + case .msdos: + isDirectory = isDirectory || ((centralDirectoryStructure.externalFileAttributes >> 4) == 0x01) + fallthrough // For all other OSes we can only guess based on the directory suffix char + default: return isDirectory ? .directory : .file + } + } + + /// Indicates whether or not the receiver is compressed. + var isCompressed: Bool { + localFileHeader.compressionMethod != CompressionMethod.none.rawValue + } + + /// The size of the receiver's compressed data. + var compressedSize: UInt64 { + if centralDirectoryStructure.isZIP64 { + return zip64DataDescriptor?.compressedSize ?? centralDirectoryStructure.effectiveCompressedSize + } + return UInt64(dataDescriptor?.compressedSize ?? centralDirectoryStructure.compressedSize) + } + + /// The size of the receiver's uncompressed data. + var uncompressedSize: UInt64 { + if centralDirectoryStructure.isZIP64 { + return zip64DataDescriptor?.uncompressedSize ?? centralDirectoryStructure.effectiveUncompressedSize + } + return UInt64(dataDescriptor?.uncompressedSize ?? centralDirectoryStructure.uncompressedSize) + } + + /// The combined size of the local header, the data and the optional data descriptor. + var localSize: UInt64 { + let localFileHeader = localFileHeader + var extraDataLength = Int(localFileHeader.fileNameLength) + extraDataLength += Int(localFileHeader.extraFieldLength) + var size = UInt64(LocalFileHeader.size + extraDataLength) + size += isCompressed ? compressedSize : uncompressedSize + if centralDirectoryStructure.isZIP64 { + size += zip64DataDescriptor != nil ? UInt64(ZIP64DataDescriptor.size) : 0 + } else { + size += dataDescriptor != nil ? UInt64(DefaultDataDescriptor.size) : 0 + } + return size + } + + var dataOffset: UInt64 { + var dataOffset = centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader + dataOffset += UInt64(LocalFileHeader.size) + dataOffset += UInt64(localFileHeader.fileNameLength) + dataOffset += UInt64(localFileHeader.extraFieldLength) + return dataOffset + } + + static func == (lhs: Entry, rhs: Entry) -> Bool { + lhs.path == rhs.path + && lhs.localFileHeader.crc32 == rhs.localFileHeader.crc32 + && lhs.centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader + == rhs.centralDirectoryStructure.effectiveRelativeOffsetOfLocalHeader + } + + /// Returns the `path` of the receiver within a ZIP `Archive` using a given encoding. + /// + /// - Parameters: + /// - encoding: `String.Encoding` + func path(using encoding: String.Encoding) -> String { + String(data: centralDirectoryStructure.fileNameData, encoding: encoding) ?? "" + } + +} + +extension Entry.CentralDirectoryStructure { + + init( + localFileHeader: Entry.LocalFileHeader, + fileAttributes: UInt32, + relativeOffset: UInt32, + extraField: (length: UInt16, data: Data)) + { + versionMadeBy = UInt16(789) + versionNeededToExtract = localFileHeader.versionNeededToExtract + generalPurposeBitFlag = localFileHeader.generalPurposeBitFlag + compressionMethod = localFileHeader.compressionMethod + lastModFileTime = localFileHeader.lastModFileTime + lastModFileDate = localFileHeader.lastModFileDate + crc32 = localFileHeader.crc32 + compressedSize = localFileHeader.compressedSize + uncompressedSize = localFileHeader.uncompressedSize + fileNameLength = localFileHeader.fileNameLength + extraFieldLength = extraField.length + fileCommentLength = UInt16(0) + diskNumberStart = UInt16(0) + internalFileAttributes = UInt16(0) + externalFileAttributes = fileAttributes + relativeOffsetOfLocalHeader = relativeOffset + fileNameData = localFileHeader.fileNameData + extraFieldData = extraField.data + fileCommentData = Data() + if + let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field( + in: extraFieldData, + fields: validFields) + { + extraFields = [zip64ExtendedInformation] + } + } + + init( + centralDirectoryStructure: Entry.CentralDirectoryStructure, + zip64ExtendedInformation: Entry.ZIP64ExtendedInformation?, + relativeOffset: UInt32) + { + if let existingInfo = zip64ExtendedInformation { + extraFieldData = existingInfo.data + versionNeededToExtract = max( + centralDirectoryStructure.versionNeededToExtract, + Archive.Version.v45.rawValue) + } else { + extraFieldData = centralDirectoryStructure.extraFieldData + let existingVersion = centralDirectoryStructure.versionNeededToExtract + versionNeededToExtract = existingVersion < Archive.Version.v45.rawValue + ? centralDirectoryStructure.versionNeededToExtract + : Archive.Version.v20.rawValue + } + extraFieldLength = UInt16(extraFieldData.count) + relativeOffsetOfLocalHeader = relativeOffset + versionMadeBy = centralDirectoryStructure.versionMadeBy + generalPurposeBitFlag = centralDirectoryStructure.generalPurposeBitFlag + compressionMethod = centralDirectoryStructure.compressionMethod + lastModFileTime = centralDirectoryStructure.lastModFileTime + lastModFileDate = centralDirectoryStructure.lastModFileDate + crc32 = centralDirectoryStructure.crc32 + compressedSize = centralDirectoryStructure.compressedSize + uncompressedSize = centralDirectoryStructure.uncompressedSize + fileNameLength = centralDirectoryStructure.fileNameLength + fileCommentLength = centralDirectoryStructure.fileCommentLength + diskNumberStart = centralDirectoryStructure.diskNumberStart + internalFileAttributes = centralDirectoryStructure.internalFileAttributes + externalFileAttributes = centralDirectoryStructure.externalFileAttributes + fileNameData = centralDirectoryStructure.fileNameData + fileCommentData = centralDirectoryStructure.fileCommentData + if + let zip64ExtendedInformation = Entry.ZIP64ExtendedInformation.scanForZIP64Field( + in: extraFieldData, + fields: validFields) + { + extraFields = [zip64ExtendedInformation] + } + } +} + +extension Entry.CentralDirectoryStructure { + + var effectiveCompressedSize: UInt64 { + if isZIP64, let compressedSize = zip64ExtendedInformation?.compressedSize, compressedSize > 0 { + return compressedSize + } + return UInt64(compressedSize) + } + + var effectiveUncompressedSize: UInt64 { + if isZIP64, let uncompressedSize = zip64ExtendedInformation?.uncompressedSize, uncompressedSize > 0 { + return uncompressedSize + } + return UInt64(uncompressedSize) + } + + var effectiveRelativeOffsetOfLocalHeader: UInt64 { + if isZIP64, let offset = zip64ExtendedInformation?.relativeOffsetOfLocalHeader, offset > 0 { + return offset + } + return UInt64(relativeOffsetOfLocalHeader) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/FileManager+ZIP.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/FileManager+ZIP.swift new file mode 100644 index 0000000000000000000000000000000000000000..e297da7f9c31299f9e4dd5ff9422eb3cf5acc32a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/FileManager+ZIP.swift @@ -0,0 +1,369 @@ +// +// FileManager+ZIP.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension FileManager { + typealias CentralDirectoryStructure = Entry.CentralDirectoryStructure + + class func attributes(from entry: Entry) -> [FileAttributeKey: Any] { + let centralDirectoryStructure = entry.centralDirectoryStructure + let entryType = entry.type + let fileTime = centralDirectoryStructure.lastModFileTime + let fileDate = centralDirectoryStructure.lastModFileDate + let defaultPermissions = entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions + var attributes = [.posixPermissions: defaultPermissions] as [FileAttributeKey: Any] + // Certain keys are not yet supported in swift-corelibs + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + attributes[.modificationDate] = Date(dateTime: (fileDate, fileTime)) + #endif + let versionMadeBy = centralDirectoryStructure.versionMadeBy + guard let osType = Entry.OSType(rawValue: UInt(versionMadeBy >> 8)) else { return attributes } + + let externalFileAttributes = centralDirectoryStructure.externalFileAttributes + let permissions = permissions(for: externalFileAttributes, osType: osType, entryType: entryType) + attributes[.posixPermissions] = NSNumber(value: permissions) + return attributes + } + + class func permissions( + for externalFileAttributes: UInt32, + osType: Entry.OSType, + entryType: Entry.EntryType) + -> UInt16 + { + switch osType { + case .unix, .osx: + let permissions = mode_t(externalFileAttributes >> 16) & ~S_IFMT + let defaultPermissions = entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions + return permissions == 0 ? defaultPermissions : UInt16(permissions) + + default: + return entryType == .directory ? defaultDirectoryPermissions : defaultFilePermissions + } + } + + class func externalFileAttributesForEntry(of type: Entry.EntryType, permissions: UInt16) -> UInt32 { + let typeInt = + switch type { + case .file: + UInt16(S_IFREG) + case .directory: + UInt16(S_IFDIR) + case .symlink: + UInt16(S_IFLNK) + } + var externalFileAttributes = UInt32(typeInt | UInt16(permissions)) + externalFileAttributes = (externalFileAttributes << 16) + return externalFileAttributes + } + + class func permissionsForItem(at URL: URL) throws -> UInt16 { + let fileManager = FileManager() + let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: URL.path) + var fileStat = stat() + lstat(entryFileSystemRepresentation, &fileStat) + let permissions = fileStat.st_mode + return UInt16(permissions) + } + + class func fileModificationDateTimeForItem(at url: URL) throws -> Date { + let fileManager = FileManager() + guard fileManager.itemExists(at: url) else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path]) + } + let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + var fileStat = stat() + lstat(entryFileSystemRepresentation, &fileStat) + #if os(macOS) || canImport(UIKit) + let modTimeSpec = fileStat.st_mtimespec + #else + let modTimeSpec = fileStat.st_mtim + #endif + + let timeStamp = TimeInterval(modTimeSpec.tv_sec) + TimeInterval(modTimeSpec.tv_nsec) / 1000000000.0 + let modDate = Date(timeIntervalSince1970: timeStamp) + return modDate + } + + class func fileSizeForItem(at url: URL) throws -> Int64 { + let fileManager = FileManager() + guard fileManager.itemExists(at: url) else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path]) + } + let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + var fileStat = stat() + lstat(entryFileSystemRepresentation, &fileStat) + guard fileStat.st_size >= 0 else { + throw CocoaError(.fileReadTooLarge, userInfo: [NSFilePathErrorKey: url.path]) + } + // `st_size` is a signed int value + return Int64(fileStat.st_size) + } + + class func typeForItem(at url: URL) throws -> Entry.EntryType { + let fileManager = FileManager() + guard url.isFileURL, fileManager.itemExists(at: url) else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path]) + } + let entryFileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: url.path) + var fileStat = stat() + lstat(entryFileSystemRepresentation, &fileStat) + return Entry.EntryType(mode: mode_t(fileStat.st_mode)) + } + + /// Zips the file or directory contents at the specified source URL to the destination URL. + /// + /// If the item at the source URL is a directory, the directory itself will be + /// represented within the ZIP `Archive`. Calling this method with a directory URL + /// `file:///path/directory/` will create an archive with a `directory/` entry at the root level. + /// You can override this behavior by passing `false` for `shouldKeepParent`. In that case, the contents + /// of the source directory will be placed at the root of the archive. + /// - Parameters: + /// - sourceURL: The file URL pointing to an existing file or directory. + /// - destinationURL: The file URL that identifies the destination of the zip operation. + /// - shouldKeepParent: Indicates that the directory name of a source item should be used as root element + /// within the archive. Default is `true`. + /// - compressionMethod: Indicates the `CompressionMethod` that should be applied. + /// By default, `zipItem` will create uncompressed archives. + /// - progress: A progress object that can be used to track or cancel the zip operation. + /// - Throws: Throws an error if the source item does not exist or the destination URL is not writable. + func zipItem( + at sourceURL: URL, + to destinationURL: URL, + shouldKeepParent: Bool = true, + compressionMethod: CompressionMethod = .none, + progress: Progress? = nil) + throws + { + let fileManager = FileManager() + guard fileManager.itemExists(at: sourceURL) else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: sourceURL.path]) + } + guard !fileManager.itemExists(at: destinationURL) else { + throw CocoaError(.fileWriteFileExists, userInfo: [NSFilePathErrorKey: destinationURL.path]) + } + guard let archive = Archive(url: destinationURL, accessMode: .create) else { + throw Archive.ArchiveError.unwritableArchive + } + let isDirectory = try FileManager.typeForItem(at: sourceURL) == .directory + if isDirectory { + let subPaths = try subpathsOfDirectory(atPath: sourceURL.path) + var totalUnitCount = Int64(0) + if let progress { + totalUnitCount = subPaths.reduce(Int64(0)) { + let itemURL = sourceURL.appendingPathComponent($1) + let itemSize = archive.totalUnitCountForAddingItem(at: itemURL) + return $0 + itemSize + } + progress.totalUnitCount = totalUnitCount + } + + // If the caller wants to keep the parent directory, we use the lastPathComponent of the source URL + // as common base for all entries (similar to macOS' Archive Utility.app) + let directoryPrefix = sourceURL.lastPathComponent + for entryPath in subPaths { + let finalEntryPath = shouldKeepParent ? directoryPrefix + "/" + entryPath : entryPath + let finalBaseURL = shouldKeepParent ? sourceURL.deletingLastPathComponent() : sourceURL + if let progress { + let itemURL = sourceURL.appendingPathComponent(entryPath) + let entryProgress = archive.makeProgressForAddingItem(at: itemURL) + progress.addChild(entryProgress, withPendingUnitCount: entryProgress.totalUnitCount) + try archive.addEntry( + with: finalEntryPath, + relativeTo: finalBaseURL, + compressionMethod: compressionMethod, + progress: entryProgress) + } else { + try archive.addEntry( + with: finalEntryPath, + relativeTo: finalBaseURL, + compressionMethod: compressionMethod) + } + } + } else { + progress?.totalUnitCount = archive.totalUnitCountForAddingItem(at: sourceURL) + let baseURL = sourceURL.deletingLastPathComponent() + try archive.addEntry( + with: sourceURL.lastPathComponent, + relativeTo: baseURL, + compressionMethod: compressionMethod, + progress: progress) + } + } + + /// Unzips the contents at the specified source URL to the destination URL. + /// + /// - Parameters: + /// - sourceURL: The file URL pointing to an existing ZIP file. + /// - destinationURL: The file URL that identifies the destination directory of the unzip operation. + /// - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance. + /// - progress: A progress object that can be used to track or cancel the unzip operation. + /// - preferredEncoding: Encoding for entry paths. Overrides the encoding specified in the archive. + /// - Throws: Throws an error if the source item does not exist or the destination URL is not writable. + func unzipItem( + at sourceURL: URL, + to destinationURL: URL, + skipCRC32: Bool = false, + progress: Progress? = nil, + preferredEncoding: String.Encoding? = nil) + throws + { + let fileManager = FileManager() + guard fileManager.itemExists(at: sourceURL) else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: sourceURL.path]) + } + guard let archive = Archive(url: sourceURL, accessMode: .read, preferredEncoding: preferredEncoding) else { + throw Archive.ArchiveError.unreadableArchive + } + // Defer extraction of symlinks until all files & directories have been created. + // This is necessary because we can't create links to files that haven't been created yet. + let sortedEntries = archive.sorted { left, right -> Bool in + switch (left.type, right.type) { + case (.directory, .file): return true + case (.directory, .symlink): return true + case (.file, .symlink): return true + default: return false + } + } + var totalUnitCount = Int64(0) + if let progress { + totalUnitCount = sortedEntries.reduce(0) { $0 + archive.totalUnitCountForReading($1) } + progress.totalUnitCount = totalUnitCount + } + + for entry in sortedEntries { + let path = preferredEncoding == nil ? entry.path : entry.path(using: preferredEncoding!) + let entryURL = destinationURL.appendingPathComponent(path) + guard entryURL.isContained(in: destinationURL) else { + throw CocoaError( + .fileReadInvalidFileName, + userInfo: [NSFilePathErrorKey: entryURL.path]) + } + let crc32: CRC32 + if let progress { + let entryProgress = archive.makeProgressForReading(entry) + progress.addChild(entryProgress, withPendingUnitCount: entryProgress.totalUnitCount) + crc32 = try archive.extract(entry, to: entryURL, skipCRC32: skipCRC32, progress: entryProgress) + } else { + crc32 = try archive.extract(entry, to: entryURL, skipCRC32: skipCRC32) + } + + func verifyChecksumIfNecessary() throws { + if skipCRC32 == false, crc32 != entry.checksum { + throw Archive.ArchiveError.invalidCRC32 + } + } + try verifyChecksumIfNecessary() + } + } + + // MARK: - Helpers + + func itemExists(at url: URL) -> Bool { + // Use `URL.checkResourceIsReachable()` instead of `FileManager.fileExists()` here + // because we don't want implicit symlink resolution. + // As per documentation, `FileManager.fileExists()` traverses symlinks and therefore a broken symlink + // would throw a `.fileReadNoSuchFile` false positive error. + // For ZIP files it may be intended to archive "broken" symlinks because they might be + // resolvable again when extracting the archive to a different destination. + (try? url.checkResourceIsReachable()) == true + } + + func createParentDirectoryStructure(for url: URL) throws { + let parentDirectoryURL = url.deletingLastPathComponent() + try createDirectory(at: parentDirectoryURL, withIntermediateDirectories: true, attributes: nil) + } + +} + +extension Date { + + // MARK: Lifecycle + + init(dateTime: (UInt16, UInt16)) { + var msdosDateTime = Int(dateTime.0) + msdosDateTime <<= 16 + msdosDateTime |= Int(dateTime.1) + var unixTime = tm() + unixTime.tm_sec = Int32((msdosDateTime & 31) * 2) + unixTime.tm_min = Int32((msdosDateTime >> 5) & 63) + unixTime.tm_hour = Int32((Int(dateTime.1) >> 11) & 31) + unixTime.tm_mday = Int32((msdosDateTime >> 16) & 31) + unixTime.tm_mon = Int32((msdosDateTime >> 21) & 15) + unixTime.tm_mon -= 1 // UNIX time struct month entries are zero based. + unixTime.tm_year = Int32(1980 + (msdosDateTime >> 25)) + unixTime.tm_year -= 1900 // UNIX time structs count in "years since 1900". + let time = timegm(&unixTime) + self = Date(timeIntervalSince1970: TimeInterval(time)) + } + + // MARK: Internal + + var fileModificationDateTime: (UInt16, UInt16) { + (self.fileModificationDate, self.fileModificationTime) + } + + var fileModificationDate: UInt16 { + var time = time_t(timeIntervalSince1970) + guard let unixTime = gmtime(&time) else { + return 0 + } + var year = unixTime.pointee.tm_year + 1900 // UNIX time structs count in "years since 1900". + // ZIP uses the MSDOS date format which has a valid range of 1980 - 2099. + year = year >= 1980 ? year : 1980 + year = year <= 2099 ? year : 2099 + let month = unixTime.pointee.tm_mon + 1 // UNIX time struct month entries are zero based. + let day = unixTime.pointee.tm_mday + return UInt16(day + (month * 32) + ((year - 1980) * 512)) + } + + var fileModificationTime: UInt16 { + var time = time_t(timeIntervalSince1970) + guard let unixTime = gmtime(&time) else { + return 0 + } + let hour = unixTime.pointee.tm_hour + let minute = unixTime.pointee.tm_min + let second = unixTime.pointee.tm_sec + return UInt16((second / 2) + (minute * 32) + (hour * 2048)) + } +} + +#if swift(>=4.2) +#else + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#else + +// The swift-corelibs-foundation version of NSError.swift was missing a convenience method to create +// error objects from error codes. (https://github.com/apple/swift-corelibs-foundation/pull/1420) +// We have to provide an implementation for non-Darwin platforms using Swift versions < 4.2. + +extension CocoaError { + static func error(_ code: CocoaError.Code, userInfo: [AnyHashable: Any]? = nil, url: URL? = nil) -> Error { + var info: [String: Any] = userInfo as? [String: Any] ?? [:] + if let url { + info[NSURLErrorKey] = url + } + return NSError(domain: NSCocoaErrorDomain, code: code.rawValue, userInfo: info) + } +} + +#endif +#endif + +extension URL { + func isContained(in parentDirectoryURL: URL) -> Bool { + // Ensure this URL is contained in the passed in URL + let parentDirectoryURL = URL(fileURLWithPath: parentDirectoryURL.path, isDirectory: true).standardized + return standardized.absoluteString.hasPrefix(parentDirectoryURL.absoluteString) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/README.md b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..24d5c63877b7ae864adfac5f6e2134522668031c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/README.md @@ -0,0 +1,24 @@ +## ZipFoundation + +This directory includes the source code of the ZIPFoundation library, from the following release: +https://github.com/weichsel/ZIPFoundation/releases/tag/0.9.16 + +Lottie is distributed via multiple package managers (SPM, Cocoapods, Carthage, and NPM), +each with different packaging and compilation requirements. + +Due to limitations of these package managers, we can't depend on / import +a separate ZIPFoundation module / library. Instead, we include the source +directly within the Lottie library and compile everything as a single unit. + +### Update instructions + +From time to time we may need to update to a more recent version of ZIPFoundation. +When doing this, follow these steps: + + 1. Download the latest release from https://github.com/weichsel/ZIPFoundation + and replace the source code in this directory with the updated code. + + 2. Update the URL at the top of this file to indicate what release is being used. + + 3. Change all of the `public` symbols defined in this module to instead be `internal` + to prevent Lottie from exposing any ZIPFoundation APIs. diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/URL+ZIP.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/URL+ZIP.swift new file mode 100644 index 0000000000000000000000000000000000000000..add5aa649c3340bd06d6025e1f58d97fe645e35d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/EmbeddedLibraries/ZipFoundation/URL+ZIP.swift @@ -0,0 +1,32 @@ +// +// URL+ZIP.swift +// ZIPFoundation +// +// Copyright © 2017-2021 Thomas Zoechling, https://www.peakstep.com and the ZIP Foundation project authors. +// Released under the MIT License. +// +// See https://github.com/weichsel/ZIPFoundation/blob/master/LICENSE for license information. +// + +import Foundation + +extension URL { + + static func temporaryReplacementDirectoryURL(for archive: Archive) -> URL { + #if swift(>=5.0) || os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if + archive.url.isFileURL, + let tempDir = try? FileManager().url( + for: .itemReplacementDirectory, + in: .userDomainMask, + appropriateFor: archive.url, + create: true) + { + return tempDir + } + #endif + + return URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent( + ProcessInfo.processInfo.globallyUniqueString) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..41ad7364960fc798637d132b16fe39b87478b2bc --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift @@ -0,0 +1,179 @@ +// +// LayerContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import QuartzCore + +// MARK: - CompositionLayer + +/// The base class for a child layer of CompositionContainer +class CompositionLayer: CALayer, KeypathSearchable { + + // MARK: Lifecycle + + init(layer: LayerModel, size: CGSize) { + transformNode = LayerTransformNode(transform: layer.transform) + if let masks = layer.masks?.filter({ $0.mode != .none }), !masks.isEmpty { + maskLayer = MaskContainerLayer(masks: masks) + } else { + maskLayer = nil + } + matteType = layer.matte + inFrame = layer.inFrame.cgFloat + outFrame = layer.outFrame.cgFloat + timeStretch = layer.timeStretch.cgFloat + startFrame = layer.startTime.cgFloat + keypathName = layer.name + childKeypaths = [transformNode.transformProperties] + super.init() + anchorPoint = .zero + actions = [ + "opacity" : NSNull(), + "transform" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "sublayerTransform" : NSNull(), + "shadowOpacity" : NSNull(), + "shadowOffset" : NSNull(), + "shadowColor" : NSNull(), + "shadowRadius" : NSNull(), + ] + + contentsLayer.anchorPoint = .zero + contentsLayer.bounds = CGRect(origin: .zero, size: size) + contentsLayer.actions = [ + "opacity" : NSNull(), + "transform" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "sublayerTransform" : NSNull(), + "hidden" : NSNull(), + ] + compositingFilter = layer.blendMode.filterName + addSublayer(contentsLayer) + + if let maskLayer { + contentsLayer.mask = maskLayer + } + + // There are two different drop shadow schemas, either using `DropShadowEffect` or `DropShadowStyle`. + // If both happen to be present, prefer the `DropShadowEffect` (which is the drop shadow schema + // supported on other platforms). + let dropShadowEffect = layer.effects.first(where: { $0 is DropShadowEffect }) as? DropShadowModel + let dropShadowStyle = layer.styles.first(where: { $0 is DropShadowStyle }) as? DropShadowModel + if let dropShadowModel = dropShadowEffect ?? dropShadowStyle { + layerEffectNodes.append(DropShadowNode(model: dropShadowModel)) + } + name = layer.name + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? CompositionLayer else { + fatalError("Wrong Layer Class") + } + transformNode = layer.transformNode + matteType = layer.matteType + inFrame = layer.inFrame + outFrame = layer.outFrame + timeStretch = layer.timeStretch + startFrame = layer.startFrame + keypathName = layer.keypathName + childKeypaths = [transformNode.transformProperties] + maskLayer = nil + layerEffectNodes = layer.layerEffectNodes + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + weak var layerDelegate: CompositionLayerDelegate? + + let transformNode: LayerTransformNode + + let contentsLayer = CALayer() + + let maskLayer: MaskContainerLayer? + + let matteType: MatteType? + + let inFrame: CGFloat + let outFrame: CGFloat + let startFrame: CGFloat + let timeStretch: CGFloat + + var layerEffectNodes: [LayerEffectNode] = [] + + // MARK: Keypath Searchable + + let keypathName: String + + final var childKeypaths: [KeypathSearchable] + + var renderScale: CGFloat = 1 { + didSet { + updateRenderScale() + } + } + + var matteLayer: CompositionLayer? { + didSet { + if let matte = matteLayer { + if let type = matteType, type == .invert { + mask = InvertedMatteLayer(inputMatte: matte) + } else { + mask = matte + } + } else { + mask = nil + } + } + } + + var keypathProperties: [String: AnyNodeProperty] { + [:] + } + + var keypathLayer: CALayer? { + contentsLayer + } + + final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) { + transformNode.updateTree(frame, forceUpdates: forceUpdates) + let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame) + /// Only update contents if current time is within the layers time bounds. + if layerVisible { + displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates) + maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates) + } + contentsLayer.transform = transformNode.globalTransform + contentsLayer.opacity = transformNode.opacity + contentsLayer.isHidden = !layerVisible + layerDelegate?.frameUpdated(frame: frame) + + for layerEffectNode in layerEffectNodes { + layerEffectNode.updateWithFrame(layer: self, frame: frame) + } + } + + func displayContentsWithFrame(frame _: CGFloat, forceUpdates _: Bool) { + /// To be overridden by subclass + } + + func updateRenderScale() { + contentsScale = renderScale + } +} + +// MARK: - CompositionLayerDelegate + +protocol CompositionLayerDelegate: AnyObject { + func frameUpdated(frame: CGFloat) +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..708b8a14bef6053f0e7c8d8715208c5ddab17a0a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift @@ -0,0 +1,54 @@ +// +// ImageCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import QuartzCore + +final class ImageCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(imageLayer: ImageLayerModel, size: CGSize) { + imageReferenceID = imageLayer.referenceID + super.init(layer: imageLayer, size: size) + contentsLayer.masksToBounds = true + contentsLayer.contentsGravity = CALayerContentsGravity.resize + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? ImageCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + imageReferenceID = layer.imageReferenceID + image = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let imageReferenceID: String + + var image: CGImage? = nil { + didSet { + if let image { + contentsLayer.contents = image + } else { + contentsLayer.contents = nil + } + } + } + + var imageContentsGravity: CALayerContentsGravity = .resize { + didSet { + contentsLayer.contentsGravity = imageContentsGravity + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..bfab6ea50c80fcb76963d5ea4faea5cd1bddfe9e --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift @@ -0,0 +1,190 @@ +// +// MaskContainerLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import QuartzCore + +extension MaskMode { + var usableMode: MaskMode { + switch self { + case .add: + .add + case .subtract: + .subtract + case .intersect: + .intersect + case .lighten: + .add + case .darken: + .darken + case .difference: + .intersect + case .none: + .none + } + } +} + +// MARK: - MaskContainerLayer + +final class MaskContainerLayer: CALayer { + + // MARK: Lifecycle + + init(masks: [Mask]) { + super.init() + anchorPoint = .zero + var containerLayer = CALayer() + var firstObject = true + for mask in masks { + let maskLayer = MaskLayer(mask: mask) + maskLayers.append(maskLayer) + if mask.mode.usableMode == .none { + continue + } else if mask.mode.usableMode == .add || firstObject { + firstObject = false + containerLayer.addSublayer(maskLayer) + } else { + containerLayer.mask = maskLayer + let newContainer = CALayer() + newContainer.addSublayer(containerLayer) + containerLayer = newContainer + } + } + addSublayer(containerLayer) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? MaskContainerLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { + for maskLayer in maskLayers { + maskLayer.updateWithFrame(frame: frame, forceUpdates: forceUpdates) + } + } + + // MARK: Fileprivate + + fileprivate var maskLayers: [MaskLayer] = [] +} + +extension CGRect { + static var veryLargeRect: CGRect { + CGRect( + x: -10_000_000, + y: -10_000_000, + width: 20_000_000, + height: 20_000_000) + } +} + +// MARK: - MaskLayer + +private class MaskLayer: CALayer { + + // MARK: Lifecycle + + init(mask: Mask) { + properties = MaskNodeProperties(mask: mask) + super.init() + addSublayer(maskLayer) + anchorPoint = .zero + maskLayer.fillColor = mask.mode == .add + ? .rgb(1, 0, 0) + : .rgb(0, 1, 0) + maskLayer.fillRule = CAShapeLayerFillRule.evenOdd + actions = [ + "opacity" : NSNull(), + ] + } + + override init(layer: Any) { + properties = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let properties: MaskNodeProperties? + + let maskLayer = CAShapeLayer() + + func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let properties else { return } + if properties.opacity.needsUpdate(frame: frame) || forceUpdates { + properties.opacity.update(frame: frame) + opacity = Float(properties.opacity.value.cgFloatValue) + } + + if properties.shape.needsUpdate(frame: frame) || forceUpdates { + properties.shape.update(frame: frame) + properties.expansion.update(frame: frame) + + let shapePath = properties.shape.value.cgPath() + var path = shapePath + if + properties.mode.usableMode == .subtract && !properties.inverted || + (properties.mode.usableMode == .add && properties.inverted) + { + /// Add a bounds rect to invert the mask + let newPath = CGMutablePath() + newPath.addRect(CGRect.veryLargeRect) + newPath.addPath(shapePath) + path = newPath + } + maskLayer.path = path + } + } +} + +// MARK: - MaskNodeProperties + +private class MaskNodeProperties: NodePropertyMap { + + // MARK: Lifecycle + + init(mask: Mask) { + mode = mask.mode + inverted = mask.inverted + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes)) + shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes)) + expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes)) + propertyMap = [ + PropertyName.opacity.rawValue : opacity, + "Shape" : shape, + "Expansion" : expansion, + ] + properties = Array(propertyMap.values) + } + + // MARK: Internal + + var propertyMap: [String: AnyNodeProperty] + + var properties: [AnyNodeProperty] + + let mode: MaskMode + let inverted: Bool + + let opacity: NodeProperty + let shape: NodeProperty + let expansion: NodeProperty +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..3fdf163760618522289ad8dda69853364ce50932 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.swift @@ -0,0 +1,28 @@ +// +// NullCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation + +final class NullCompositionLayer: CompositionLayer { + + init(layer: LayerModel) { + super.init(layer: layer, size: .zero) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? NullCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + super.init(layer: layer) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..44d6a394fba11df2551ddd64771242a4fe7853a7 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift @@ -0,0 +1,136 @@ +// +// PreCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import QuartzCore + +final class PreCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init( + precomp: PreCompLayerModel, + asset: PrecompAsset, + layerImageProvider: LayerImageProvider, + layerTextProvider: LayerTextProvider, + layerFontProvider: LayerFontProvider, + textProvider: AnimationKeypathTextProvider, + fontProvider: AnimationFontProvider, + assetLibrary: AssetLibrary?, + frameRate: CGFloat, + rootAnimationLayer: MainThreadAnimationLayer?) + { + animationLayers = [] + if let keyframes = precomp.timeRemapping?.keyframes { + remappingNode = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframes)) + } else { + remappingNode = nil + } + self.frameRate = frameRate + super.init(layer: precomp, size: CGSize(width: precomp.width, height: precomp.height)) + bounds = CGRect(origin: .zero, size: CGSize(width: precomp.width, height: precomp.height)) + contentsLayer.masksToBounds = true + contentsLayer.bounds = bounds + + let layers = asset.layers.initializeCompositionLayers( + assetLibrary: assetLibrary, + layerImageProvider: layerImageProvider, + layerTextProvider: layerTextProvider, + layerFontProvider: layerFontProvider, + textProvider: textProvider, + fontProvider: fontProvider, + frameRate: frameRate, + rootAnimationLayer: rootAnimationLayer) + + var imageLayers = [ImageCompositionLayer]() + var textLayers = [TextCompositionLayer]() + + var mattedLayer: CompositionLayer? = nil + + for layer in layers.reversed() { + layer.bounds = bounds + animationLayers.append(layer) + if let imageLayer = layer as? ImageCompositionLayer { + imageLayers.append(imageLayer) + } + if let textLayer = layer as? TextCompositionLayer { + textLayers.append(textLayer) + } + if let matte = mattedLayer { + /// The previous layer requires this layer to be its matte + matte.matteLayer = layer + mattedLayer = nil + continue + } + if + let matte = layer.matteType, + matte == .add || matte == .invert + { + /// We have a layer that requires a matte. + mattedLayer = layer + } + contentsLayer.addSublayer(layer) + } + + childKeypaths.append(contentsOf: layers) + + layerImageProvider.addImageLayers(imageLayers) + layerTextProvider.addTextLayers(textLayers) + layerFontProvider.addTextLayers(textLayers) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? PreCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + frameRate = layer.frameRate + remappingNode = nil + animationLayers = [] + + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let frameRate: CGFloat + let remappingNode: NodeProperty? + + override var keypathProperties: [String: AnyNodeProperty] { + guard let remappingNode else { + return super.keypathProperties + } + return ["Time Remap" : remappingNode] + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + let localFrame: CGFloat + if let remappingNode { + remappingNode.update(frame: frame) + localFrame = remappingNode.value.cgFloatValue * frameRate + } else { + localFrame = (frame - startFrame) / timeStretch + } + for animationLayer in animationLayers { + animationLayer.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) + } + } + + override func updateRenderScale() { + super.updateRenderScale() + for animationLayer in animationLayers { + animationLayer.renderScale = renderScale + } + } + + // MARK: Fileprivate + + fileprivate var animationLayers: [CompositionLayer] +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..a10189f22979ae3725e7a7fc2d4b74802ee38843 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.swift @@ -0,0 +1,58 @@ +// +// ShapeLayerContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import CoreGraphics +import Foundation + +/// A CompositionLayer responsible for initializing and rendering shapes +final class ShapeCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(shapeLayer: ShapeLayerModel) { + let results = shapeLayer.items.initializeNodeTree() + let renderContainer = ShapeContainerLayer() + self.renderContainer = renderContainer + rootNode = results.rootNode + super.init(layer: shapeLayer, size: .zero) + contentsLayer.addSublayer(renderContainer) + for container in results.renderContainers { + renderContainer.insertRenderLayer(container) + } + rootNode?.updateTree(0, forceUpdates: true) + childKeypaths.append(contentsOf: results.childrenNodes) + } + + override init(layer: Any) { + guard let layer = layer as? ShapeCompositionLayer else { + fatalError("init(layer:) wrong class.") + } + rootNode = nil + renderContainer = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let rootNode: AnimatorNode? + let renderContainer: ShapeContainerLayer? + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + rootNode?.updateTree(frame, forceUpdates: forceUpdates) + renderContainer?.markRenderUpdates(forFrame: frame) + } + + override func updateRenderScale() { + super.updateRenderScale() + renderContainer?.renderScale = renderScale + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..7902770b560e3eb2f0ff67883f5ef918d115f2a6 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift @@ -0,0 +1,56 @@ +// +// SolidCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import QuartzCore + +final class SolidCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init(solid: SolidLayerModel) { + let components = solid.colorHex.hexColorComponents() + colorProperty = + NodeProperty(provider: SingleValueProvider(LottieColor( + r: Double(components.red), + g: Double(components.green), + b: Double(components.blue), + a: 1))) + + super.init(layer: solid, size: .zero) + solidShape.path = CGPath(rect: CGRect(x: 0, y: 0, width: solid.width, height: solid.height), transform: nil) + contentsLayer.addSublayer(solidShape) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? SolidCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + colorProperty = layer.colorProperty + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let colorProperty: NodeProperty? + let solidShape = CAShapeLayer() + + override var keypathProperties: [String: AnyNodeProperty] { + guard let colorProperty else { return super.keypathProperties } + return [PropertyName.color.rawValue : colorProperty] + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates _: Bool) { + guard let colorProperty else { return } + colorProperty.update(frame: frame) + solidShape.fillColor = colorProperty.value.cgColorValue + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..059d17d4e217d91fd09e14d97ee5590ba835535b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift @@ -0,0 +1,180 @@ +// +// TextCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +/// Needed for NSMutableParagraphStyle... +#if os(OSX) +import AppKit +#else +import UIKit +#endif + +extension TextJustification { + var textAlignment: NSTextAlignment { + switch self { + case .left: + .left + case .right: + .right + case .center: + .center + } + } + + var caTextAlignement: CATextLayerAlignmentMode { + switch self { + case .left: + .left + case .right: + .right + case .center: + .center + } + } +} + +// MARK: - TextCompositionLayer + +final class TextCompositionLayer: CompositionLayer { + + // MARK: Lifecycle + + init( + textLayer: TextLayerModel, + textProvider: AnimationKeypathTextProvider, + fontProvider: AnimationFontProvider, + rootAnimationLayer: MainThreadAnimationLayer?) + { + var rootNode: TextAnimatorNode? + for animator in textLayer.animators { + rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator) + } + self.rootNode = rootNode + textDocument = KeyframeInterpolator(keyframes: textLayer.text.keyframes) + + self.textProvider = textProvider + self.fontProvider = fontProvider + self.rootAnimationLayer = rootAnimationLayer + + super.init(layer: textLayer, size: .zero) + contentsLayer.addSublayer(self.textLayer) + self.textLayer.masksToBounds = false + self.textLayer.isGeometryFlipped = true + + if let rootNode { + childKeypaths.append(rootNode) + } + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? TextCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + rootNode = nil + textDocument = nil + + textProvider = DefaultTextProvider() + fontProvider = DefaultFontProvider() + + super.init(layer: layer) + } + + // MARK: Internal + + let rootNode: TextAnimatorNode? + let textDocument: KeyframeInterpolator? + + let textLayer = CoreTextRenderLayer() + var textProvider: AnimationKeypathTextProvider + var fontProvider: AnimationFontProvider + weak var rootAnimationLayer: MainThreadAnimationLayer? + + lazy var fullAnimationKeypath: AnimationKeypath = // Individual layers don't know their full keypaths, so we have to delegate + // to the `MainThreadAnimationLayer` to search the layer hierarchy and find + // the full keypath (which includes this layer's parent layers) + rootAnimationLayer?.keypath(for: self) + // If that failed for some reason, just use the last path component (which we do have here) + ?? AnimationKeypath(keypath: keypathName) + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let textDocument else { return } + + textLayer.contentsScale = renderScale + + let documentUpdate = textDocument.hasUpdate(frame: frame) + let animatorUpdate = rootNode?.updateContents(frame, forceLocalUpdate: forceUpdates) ?? false + guard documentUpdate == true || animatorUpdate == true else { return } + + rootNode?.rebuildOutputs(frame: frame) + + // Get Text Attributes + let text = textDocument.value(frame: frame) as! TextDocument + + // Prior to Lottie 4.3.0 the Main Thread rendering engine always just used `LegacyAnimationTextProvider` + // and called it with the `keypathName` (only the last path component of the full keypath). + // Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider` instead if implemented. + let textString: String = + if let keypathTextValue = textProvider.text(for: fullAnimationKeypath, sourceText: text.text) { + keypathTextValue + } else if let legacyTextProvider = textProvider as? LegacyAnimationTextProvider { + legacyTextProvider.textFor(keypathName: keypathName, sourceText: text.text) + } else { + text.text + } + + let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue + let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0) + let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0 + let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity + let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) + let start = rootNode?.textOutputNode.start.flatMap { Int($0) } + let end = rootNode?.textOutputNode.end.flatMap { Int($0) } + let selectedRangeOpacity = rootNode?.textOutputNode.selectedRangeOpacity + let textRangeUnit = rootNode?.textAnimatorProperties.textRangeUnit + + // Set all of the text layer options + textLayer.text = textString + textLayer.font = ctFont + textLayer.alignment = text.justification.textAlignment + textLayer.lineHeight = CGFloat(text.lineHeight) + textLayer.tracking = tracking + + // Configure the text animators + textLayer.start = start + textLayer.end = end + textLayer.textRangeUnit = textRangeUnit + textLayer.selectedRangeOpacity = selectedRangeOpacity + + if let fillColor = rootNode?.textOutputNode.fillColor { + textLayer.fillColor = fillColor + } else if let fillColor = text.fillColorData?.cgColorValue { + textLayer.fillColor = fillColor + } else { + textLayer.fillColor = nil + } + + textLayer.preferredSize = text.textFrameSize?.sizeValue + textLayer.strokeOnTop = text.strokeOverFill ?? false + textLayer.strokeWidth = strokeWidth + textLayer.strokeColor = strokeColor + textLayer.sizeToFit() + + textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) + textLayer.transform = CATransform3DIdentity + textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero + textLayer.transform = matrix + } + + override func updateRenderScale() { + super.updateRenderScale() + textLayer.contentsScale = renderScale + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..14e45a6fe415861603c370746d4167a25f2ee19f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift @@ -0,0 +1,313 @@ +// +// MainThreadAnimationLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/24/19. +// + +import QuartzCore + +// MARK: - MainThreadAnimationLayer + +/// The base `CALayer` for the Main Thread rendering engine +/// +/// This layer holds a single composition container and allows for animation of +/// the currentFrame property. +final class MainThreadAnimationLayer: CALayer, RootAnimationLayer { + + // MARK: Lifecycle + + init( + animation: LottieAnimation, + imageProvider: AnimationImageProvider, + textProvider: AnimationKeypathTextProvider, + fontProvider: AnimationFontProvider, + maskAnimationToBounds: Bool, + logger: LottieLogger) + { + layerImageProvider = LayerImageProvider(imageProvider: imageProvider, assets: animation.assetLibrary?.imageAssets) + layerTextProvider = LayerTextProvider(textProvider: textProvider) + layerFontProvider = LayerFontProvider(fontProvider: fontProvider) + animationLayers = [] + self.logger = logger + super.init() + masksToBounds = maskAnimationToBounds + bounds = animation.bounds + let layers = animation.layers.initializeCompositionLayers( + assetLibrary: animation.assetLibrary, + layerImageProvider: layerImageProvider, + layerTextProvider: layerTextProvider, + layerFontProvider: layerFontProvider, + textProvider: textProvider, + fontProvider: fontProvider, + frameRate: CGFloat(animation.framerate), + rootAnimationLayer: self) + + var imageLayers = [ImageCompositionLayer]() + var textLayers = [TextCompositionLayer]() + + var mattedLayer: CompositionLayer? = nil + + for layer in layers.reversed() { + layer.bounds = bounds + animationLayers.append(layer) + if let imageLayer = layer as? ImageCompositionLayer { + imageLayers.append(imageLayer) + } + if let textLayer = layer as? TextCompositionLayer { + textLayers.append(textLayer) + } + if let matte = mattedLayer { + /// The previous layer requires this layer to be its matte + matte.matteLayer = layer + mattedLayer = nil + continue + } + if + let matte = layer.matteType, + matte == .add || matte == .invert + { + /// We have a layer that requires a matte. + mattedLayer = layer + } + addSublayer(layer) + } + + layerImageProvider.addImageLayers(imageLayers) + layerImageProvider.reloadImages() + layerTextProvider.addTextLayers(textLayers) + layerTextProvider.reloadTexts() + layerFontProvider.addTextLayers(textLayers) + layerFontProvider.reloadTexts() + setNeedsDisplay() + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + animationLayers = [] + layerImageProvider = LayerImageProvider(imageProvider: BlankImageProvider(), assets: nil) + layerTextProvider = LayerTextProvider(textProvider: DefaultTextProvider()) + layerFontProvider = LayerFontProvider(fontProvider: DefaultFontProvider()) + logger = typedLayer.logger + super.init(layer: layer) + + currentFrame = typedLayer.currentFrame + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Public + + public var respectAnimationFrameRate = false + + // MARK: CALayer Animations + + override public class func needsDisplay(forKey key: String) -> Bool { + if key == "currentFrame" { + return true + } + return super.needsDisplay(forKey: key) + } + + override public func action(forKey event: String) -> CAAction? { + if event == "currentFrame" { + let animation = CABasicAnimation(keyPath: event) + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + animation.fromValue = presentation()?.currentFrame + return animation + } + return super.action(forKey: event) + } + + public override func display() { + guard Thread.isMainThread else { return } + var newFrame: CGFloat = + if + let animationKeys = animationKeys(), + !animationKeys.isEmpty + { + presentation()?.currentFrame ?? currentFrame + } else { + // We ignore the presentation's frame if there's no animation in the layer. + currentFrame + } + if respectAnimationFrameRate { + newFrame = floor(newFrame) + } + for animationLayer in animationLayers { + animationLayer.displayWithFrame(frame: newFrame, forceUpdates: forceDisplayUpdateOnEachFrame) + } + } + + // MARK: Internal + + /// The animatable Current Frame Property + @NSManaged var currentFrame: CGFloat + + /// The parent `LottieAnimationLayer` that manages this layer + weak var lottieAnimationLayer: LottieAnimationLayer? + + /// Whether or not to use `forceDisplayUpdate()` when rendering each individual frame. + /// - The main thread rendering engine implements optimizations to decrease the amount + /// of properties that have to be re-rendered on each frame. There are some cases + /// where this can result in bugs / incorrect behavior, so we allow it to be disabled. + /// - Forcing a full render on every frame will decrease performance, and is not recommended + /// except as a workaround to a bug in the main thread rendering engine. + var forceDisplayUpdateOnEachFrame = false + + var animationLayers: ContiguousArray + + var primaryAnimationKey: AnimationKey { + .managed + } + + var isAnimationPlaying: Bool? { + nil // this state is managed by `LottieAnimationView` + } + + var _animationLayers: [CALayer] { + Array(animationLayers) + } + + var imageProvider: AnimationImageProvider { + get { + layerImageProvider.imageProvider + } + set { + layerImageProvider.imageProvider = newValue + } + } + + var renderScale: CGFloat = 1 { + didSet { + for animationLayer in animationLayers { + animationLayer.renderScale = renderScale + } + } + } + + var textProvider: AnimationKeypathTextProvider { + get { layerTextProvider.textProvider } + set { layerTextProvider.textProvider = newValue } + } + + var fontProvider: AnimationFontProvider { + get { layerFontProvider.fontProvider } + set { layerFontProvider.fontProvider = newValue } + } + + func reloadImages() { + layerImageProvider.reloadImages() + } + + func removeAnimations() { + // no-op, since the primary animation is managed by the `LottieAnimationView`. + } + + /// Forces the view to update its drawing. + func forceDisplayUpdate() { + for animationLayer in animationLayers { + animationLayer.displayWithFrame(frame: currentFrame, forceUpdates: true) + } + } + + func logHierarchyKeypaths() { + logger.info("Lottie: Logging Animation Keypaths") + + for keypath in allHierarchyKeypaths() { + logger.info(keypath) + } + } + + func allHierarchyKeypaths() -> [String] { + animationLayers.flatMap { $0.allKeypaths() } + } + + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + for layer in animationLayers { + if let foundProperties = layer.nodeProperties(for: keypath) { + for property in foundProperties { + property.setProvider(provider: valueProvider) + } + layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true) + } + } + } + + func getValue(for keypath: AnimationKeypath, atFrame: CGFloat?) -> Any? { + for layer in animationLayers { + if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.valueProvider.value(frame: atFrame ?? currentFrame) + } + } + return nil + } + + func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + for layer in animationLayers { + if + let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first + { + return first.originalValueProvider.value(frame: atFrame ?? currentFrame) + } + } + return nil + } + + func layer(for keypath: AnimationKeypath) -> CALayer? { + for layer in animationLayers { + if let foundLayer = layer.layer(for: keypath) { + return foundLayer + } + } + return nil + } + + func keypath(for layerToFind: CALayer) -> AnimationKeypath? { + for layer in animationLayers { + if let foundKeypath = layer.keypath(for: layerToFind) { + return foundKeypath + } + } + return nil + } + + func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? { + var results = [AnimatorNode]() + for layer in animationLayers { + if let nodes = layer.animatorNodes(for: keypath) { + results.append(contentsOf: nodes) + } + } + if results.count == 0 { + return nil + } + return results + } + + // MARK: Fileprivate + + fileprivate let layerImageProvider: LayerImageProvider + fileprivate let layerTextProvider: LayerTextProvider + fileprivate let layerFontProvider: LayerFontProvider + fileprivate let logger: LottieLogger +} + +// MARK: - BlankImageProvider + +private class BlankImageProvider: AnimationImageProvider { + func imageForAsset(asset _: ImageAsset) -> CGImage? { + nil + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..068f3aa7951f29d64fef2d6abf4bc71c0c069b5e --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift @@ -0,0 +1,58 @@ +// Created by Jianjun Wu on 2022/5/12. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - CachedImageProvider + +private final class CachedImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with an image provider + /// + /// - Parameter imageProvider: The provider to load image from asset + /// + public init(imageProvider: AnimationImageProvider) { + self.imageProvider = imageProvider + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + if let image = imageCache.value(forKey: asset.id) { + return image + } + if let image = imageProvider.imageForAsset(asset: asset) { + imageCache.setValue(image, forKey: asset.id) + return image + } + return nil + } + + // MARK: Internal + + func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity { + imageProvider.contentsGravity(for: asset) + } + + // MARK: Private + + /// The underlying storage of this cache. + /// - We use the `LRUCache` library instead of `NSCache`, because `NSCache` + /// clears all cached values when the app is backgrounded instead of + /// only when the app receives a memory warning notification. + private var imageCache = LRUCache() + private let imageProvider: AnimationImageProvider + +} + +extension AnimationImageProvider { + /// Create a cache enabled image provider which will reuse the asset image with the same asset id + /// It wraps the current provider as image loader, and uses `NSCache` to cache the images for resue. + /// The cache will be reset when the `animation` is reset. + var cachedImageProvider: AnimationImageProvider { + guard cacheEligible else { return self } + return CachedImageProvider(imageProvider: self) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift new file mode 100644 index 0000000000000000000000000000000000000000..93116030239474018e752acf7ac7afa07238b622 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift @@ -0,0 +1,101 @@ +// +// CompositionLayersInitializer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation + +extension [LayerModel] { + + func initializeCompositionLayers( + assetLibrary: AssetLibrary?, + layerImageProvider: LayerImageProvider, + layerTextProvider: LayerTextProvider, + layerFontProvider: LayerFontProvider, + textProvider: AnimationKeypathTextProvider, + fontProvider: AnimationFontProvider, + frameRate: CGFloat, + rootAnimationLayer: MainThreadAnimationLayer?) + -> [CompositionLayer] + { + var compositionLayers = [CompositionLayer]() + var layerMap = [Int : CompositionLayer]() + + /// Organize the assets into a dictionary of [ID : ImageAsset] + var childLayers = [LayerModel]() + + for layer in self { + if layer.hidden == true { + let genericLayer = NullCompositionLayer(layer: layer) + compositionLayers.append(genericLayer) + layerMap[layer.index] = genericLayer + } else if let shapeLayer = layer as? ShapeLayerModel { + let shapeContainer = ShapeCompositionLayer(shapeLayer: shapeLayer) + compositionLayers.append(shapeContainer) + layerMap[layer.index] = shapeContainer + } else if let solidLayer = layer as? SolidLayerModel { + let solidContainer = SolidCompositionLayer(solid: solidLayer) + compositionLayers.append(solidContainer) + layerMap[layer.index] = solidContainer + } else if + let precompLayer = layer as? PreCompLayerModel, + let assetLibrary, + let precompAsset = assetLibrary.precompAssets[precompLayer.referenceID] + { + let precompContainer = PreCompositionLayer( + precomp: precompLayer, + asset: precompAsset, + layerImageProvider: layerImageProvider, + layerTextProvider: layerTextProvider, + layerFontProvider: layerFontProvider, + textProvider: textProvider, + fontProvider: fontProvider, + assetLibrary: assetLibrary, + frameRate: frameRate, + rootAnimationLayer: rootAnimationLayer) + compositionLayers.append(precompContainer) + layerMap[layer.index] = precompContainer + } else if + let imageLayer = layer as? ImageLayerModel, + let assetLibrary, + let imageAsset = assetLibrary.imageAssets[imageLayer.referenceID] + { + let imageContainer = ImageCompositionLayer( + imageLayer: imageLayer, + size: CGSize(width: imageAsset.width, height: imageAsset.height)) + compositionLayers.append(imageContainer) + layerMap[layer.index] = imageContainer + } else if let textLayer = layer as? TextLayerModel { + let textContainer = TextCompositionLayer( + textLayer: textLayer, + textProvider: textProvider, + fontProvider: fontProvider, + rootAnimationLayer: rootAnimationLayer) + compositionLayers.append(textContainer) + layerMap[layer.index] = textContainer + } else { + let genericLayer = NullCompositionLayer(layer: layer) + compositionLayers.append(genericLayer) + layerMap[layer.index] = genericLayer + } + if layer.parent != nil { + childLayers.append(layer) + } + } + + /// Now link children with their parents + for layerModel in childLayers { + if let parentID = layerModel.parent { + let childLayer = layerMap[layerModel.index] + let parentLayer = layerMap[parentID] + childLayer?.transformNode.parentNode = parentLayer?.transformNode + } + } + + return compositionLayers + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..5d72f5cfa9d1ce865ee056725477df35235ff78a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift @@ -0,0 +1,433 @@ +// +// TextLayer.swift +// Pods +// +// Created by Brandon Withrow on 8/3/20. +// + +import CoreGraphics +import CoreText +import Foundation +import QuartzCore +/// Needed for NSMutableParagraphStyle... +#if os(OSX) +import AppKit +#else +import UIKit +#endif + +// MARK: - CoreTextRenderLayer + +/// A CALayer subclass that renders text content using CoreText +final class CoreTextRenderLayer: CALayer { + + // MARK: Public + + public var text: String? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var font: CTFont? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var alignment = NSTextAlignment.left { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var lineHeight: CGFloat = 0 { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var tracking: CGFloat = 0 { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var fillColor: CGColor? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var strokeColor: CGColor? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var strokeWidth: CGFloat = 0 { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var strokeOnTop = false { + didSet { + setNeedsLayout() + setNeedsDisplay() + } + } + + public var preferredSize: CGSize? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var start: Int? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public var end: Int? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + /// The type of unit to use when computing the `start` / `end` range within the text string + public var textRangeUnit: TextRangeUnit? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + /// The opacity to apply to the range between `start` and `end` + public var selectedRangeOpacity: CGFloat? { + didSet { + needsContentUpdate = true + setNeedsLayout() + setNeedsDisplay() + } + } + + public func sizeToFit() { + updateTextContent() + bounds = drawingRect + anchorPoint = drawingAnchor + setNeedsLayout() + setNeedsDisplay() + } + + // MARK: Internal + + override func action(forKey _: String) -> CAAction? { + nil + } + + override func draw(in ctx: CGContext) { + guard let attributedString else { return } + updateTextContent() + guard fillFrameSetter != nil || strokeFrameSetter != nil else { return } + + ctx.textMatrix = .identity + ctx.setAllowsAntialiasing(true) + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setAllowsFontSubpixelQuantization(true) + + ctx.setShouldAntialias(true) + ctx.setShouldSubpixelPositionFonts(true) + ctx.setShouldSubpixelQuantizeFonts(true) + + if contentsAreFlipped() { + ctx.translateBy(x: 0, y: drawingRect.height) + ctx.scaleBy(x: 1.0, y: -1.0) + } + + let drawingPath = CGPath(rect: drawingRect, transform: nil) + + let fillFrame: CTFrame? = + if let setter = fillFrameSetter { + CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil) + } else { + nil + } + + let strokeFrame: CTFrame? = + if let setter = strokeFrameSetter { + CTFramesetterCreateFrame(setter, CFRangeMake(0, attributedString.length), drawingPath, nil) + } else { + nil + } + + // This fixes a vertical padding issue that arises when drawing some fonts. + // For some reason some fonts, such as Helvetica draw with and ascender that is greater than the one reported by CTFontGetAscender. + // I suspect this is actually an issue with the Attributed string, but cannot reproduce. + + if let fillFrame { + ctx.adjustWithLineOrigins(in: fillFrame, with: font) + } else if let strokeFrame { + ctx.adjustWithLineOrigins(in: strokeFrame, with: font) + } + + if !strokeOnTop, let strokeFrame { + CTFrameDraw(strokeFrame, ctx) + } + + if let fillFrame { + CTFrameDraw(fillFrame, ctx) + } + + if strokeOnTop, let strokeFrame { + CTFrameDraw(strokeFrame, ctx) + } + } + + // MARK: Private + + private var drawingRect = CGRect.zero + private var drawingAnchor = CGPoint.zero + private var fillFrameSetter: CTFramesetter? + private var attributedString: NSAttributedString? + private var strokeFrameSetter: CTFramesetter? + private var needsContentUpdate = false + + /// Draws Debug colors for the font alignment. + private func drawDebug(_ ctx: CGContext) { + if let font { + let ascent = CTFontGetAscent(font) + let descent = CTFontGetDescent(font) + let capHeight = CTFontGetCapHeight(font) + let leading = CTFontGetLeading(font) + + // Ascent Red + ctx.setFillColor(CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: 0, width: drawingRect.width, height: ascent)) + + // Descent Blue + ctx.setFillColor(CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: ascent, width: drawingRect.width, height: descent)) + + // Leading Yellow + ctx.setFillColor(CGColor(srgbRed: 1, green: 1, blue: 0, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: ascent + descent, width: drawingRect.width, height: leading)) + + // Cap height Green + ctx.setFillColor(CGColor(srgbRed: 0, green: 1, blue: 0, alpha: 0.5)) + ctx.fill(CGRect(x: 0, y: ascent - capHeight, width: drawingRect.width, height: capHeight)) + + if drawingRect.height - ascent + descent + leading > 0 { + // Remainder + ctx.setFillColor(CGColor(srgbRed: 0, green: 1, blue: 1, alpha: 0.5)) + ctx + .fill(CGRect( + x: 0, + y: ascent + descent + leading, + width: drawingRect.width, + height: drawingRect.height - ascent + descent + leading)) + } + } + } + + private func updateTextContent() { + guard needsContentUpdate else { return } + needsContentUpdate = false + guard let font, let text, text.count > 0, fillColor != nil || strokeColor != nil else { + drawingRect = .zero + drawingAnchor = .zero + attributedString = nil + fillFrameSetter = nil + strokeFrameSetter = nil + return + } + + // Get Font properties + let ascent = CTFontGetAscent(font) + let descent = CTFontGetDescent(font) + let capHeight = CTFontGetCapHeight(font) + let leading = CTFontGetLeading(font) + let minLineHeight = -(ascent + descent + leading) + + // Calculate line spacing + let lineSpacing = max(CGFloat(minLineHeight) + lineHeight, CGFloat(minLineHeight)) + // Build Attributes + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = lineSpacing + paragraphStyle.lineHeightMultiple = 1 + paragraphStyle.maximumLineHeight = ascent + descent + leading + paragraphStyle.alignment = alignment + paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping + var attributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.ligature: 0, + NSAttributedString.Key.font: font, + NSAttributedString.Key.kern: tracking, + NSAttributedString.Key.paragraphStyle: paragraphStyle, + ] + + if let fillColor { + attributes[NSAttributedString.Key.foregroundColor] = fillColor + } + + let attrString = NSMutableAttributedString(string: text, attributes: attributes) + + // Apply the text animator within between the `start` and `end` indices + if let selectedRangeOpacity { + // The start and end of a text animator refer to the portions of the text + // where that animator is applies. In the schema these can be represented + // in absolute index value, or as percentages relative to the dynamic string length. + var startIndex: Int + var endIndex: Int + + switch textRangeUnit ?? .percentage { + case .index: + startIndex = start ?? 0 + endIndex = end ?? text.count + + case .percentage: + let startPercentage = Double(start ?? 0) / 100 + let endPercentage = Double(end ?? 100) / 100 + + startIndex = Int(round(Double(attrString.length) * startPercentage)) + endIndex = Int(round(Double(attrString.length) * endPercentage)) + } + + // Carefully cap the indices, since passing invalid indices + // to `NSAttributedString` will crash the app. + startIndex = startIndex.clamp(0, attrString.length) + endIndex = endIndex.clamp(0, attrString.length) + + // Make sure the end index actually comes after the start index + if endIndex < startIndex { + swap(&startIndex, &endIndex) + } + + // Apply the `selectedRangeOpacity` to the current `fillColor` if provided + let textRangeColor: CGColor + if let fillColor { + if let (r, g, b) = fillColor.rgb { + textRangeColor = .rgba(r, g, b, selectedRangeOpacity) + } else { + LottieLogger.shared.warn("Could not convert color \(fillColor) to RGB values.") + textRangeColor = .rgba(0, 0, 0, selectedRangeOpacity) + } + } else { + textRangeColor = .rgba(0, 0, 0, selectedRangeOpacity) + } + + attrString.addAttribute( + NSAttributedString.Key.foregroundColor, + value: textRangeColor, + range: NSRange(location: startIndex, length: endIndex - startIndex)) + } + + attributedString = attrString + + if fillColor != nil { + let setter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString) + fillFrameSetter = setter + } else { + fillFrameSetter = nil + } + + if let strokeColor { + attributes[NSAttributedString.Key.foregroundColor] = nil + attributes[NSAttributedString.Key.strokeWidth] = strokeWidth + attributes[NSAttributedString.Key.strokeColor] = strokeColor + let strokeAttributedString = NSAttributedString(string: text, attributes: attributes) + strokeFrameSetter = CTFramesetterCreateWithAttributedString(strokeAttributedString as CFAttributedString) + } else { + strokeFrameSetter = nil + strokeWidth = 0 + } + + guard let setter = fillFrameSetter ?? strokeFrameSetter else { + return + } + + // Calculate drawing size and anchor offset + let textAnchor: CGPoint + if let preferredSize { + drawingRect = CGRect(origin: .zero, size: preferredSize) + drawingRect.size.height += (ascent - capHeight) + drawingRect.size.height += descent + textAnchor = CGPoint(x: 0, y: ascent - capHeight) + } else { + let size = CTFramesetterSuggestFrameSizeWithConstraints( + setter, + CFRange(location: 0, length: attrString.length), + nil, + CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), + nil) + switch alignment { + case .left: + textAnchor = CGPoint(x: 0, y: ascent) + case .right: + textAnchor = CGPoint(x: size.width, y: ascent) + case .center: + textAnchor = CGPoint(x: size.width * 0.5, y: ascent) + default: + textAnchor = .zero + } + drawingRect = CGRect( + x: 0, + y: 0, + width: ceil(size.width), + height: ceil(size.height)) + } + + // Now Calculate Anchor + drawingAnchor = CGPoint( + x: textAnchor.x.remap(fromLow: 0, fromHigh: drawingRect.size.width, toLow: 0, toHigh: 1), + y: textAnchor.y.remap(fromLow: 0, fromHigh: drawingRect.size.height, toLow: 0, toHigh: 1)) + + if fillFrameSetter != nil, strokeFrameSetter != nil { + drawingRect.size.width += strokeWidth + drawingRect.size.height += strokeWidth + } + } + +} + +extension CGContext { + + fileprivate func adjustWithLineOrigins(in frame: CTFrame, with font: CTFont?) { + guard let font else { return } + + let count = CFArrayGetCount(CTFrameGetLines(frame)) + + guard count > 0 else { return } + + var o = [CGPoint](repeating: .zero, count: 1) + CTFrameGetLineOrigins(frame, CFRange(location: count - 1, length: 1), &o) + + let diff = CTFontGetDescent(font) - o[0].y + if diff > 0 { + translateBy(x: 0, y: diff) + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..c96277c1a24a38099772455fc73da32bce24fe0d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift @@ -0,0 +1,55 @@ +// +// InvertedMatteLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/28/19. +// + +import QuartzCore + +/// A layer that inverses the alpha output of its input layer. +/// +/// WARNING: This is experimental and probably not very performant. +final class InvertedMatteLayer: CALayer, CompositionLayerDelegate { + + // MARK: Lifecycle + + init(inputMatte: CompositionLayer) { + self.inputMatte = inputMatte + super.init() + inputMatte.layerDelegate = self + anchorPoint = .zero + bounds = inputMatte.bounds + setNeedsDisplay() + } + + override init(layer: Any) { + guard let layer = layer as? InvertedMatteLayer else { + fatalError("init(layer:) wrong class.") + } + inputMatte = nil + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + let inputMatte: CompositionLayer? + + func frameUpdated(frame _: CGFloat) { + setNeedsDisplay() + displayIfNeeded() + } + + override func draw(in ctx: CGContext) { + guard let inputMatte else { return } + ctx.setFillColor(.rgb(0, 0, 0)) + ctx.fill(bounds) + ctx.setBlendMode(.destinationOut) + inputMatte.render(in: ctx) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..e054ce254f82ae2e785e47888186c3b857884e61 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift @@ -0,0 +1,39 @@ +// +// LayerFontProvider.swift +// Lottie +// +// Created by Brandon Withrow on 8/5/20. +// Copyright © 2020 YurtvilleProds. All rights reserved. +// + +/// Connects a LottieFontProvider to a group of text layers +final class LayerFontProvider { + + // MARK: Lifecycle + + init(fontProvider: AnimationFontProvider) { + self.fontProvider = fontProvider + textLayers = [] + reloadTexts() + } + + // MARK: Internal + + private(set) var textLayers: [TextCompositionLayer] + + var fontProvider: AnimationFontProvider { + didSet { + reloadTexts() + } + } + + func addTextLayers(_ layers: [TextCompositionLayer]) { + textLayers += layers + } + + func reloadTexts() { + for textLayer in textLayers { + textLayer.fontProvider = fontProvider + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..db531f85c0525fa0cca3177397e202e324ea39ce --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift @@ -0,0 +1,52 @@ +// +// LayerImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +/// Connects a LottieImageProvider to a group of image layers +final class LayerImageProvider { + + // MARK: Lifecycle + + init(imageProvider: AnimationImageProvider, assets: [String: ImageAsset]?) { + self.imageProvider = imageProvider + imageLayers = [ImageCompositionLayer]() + if let assets { + imageAssets = assets + } else { + imageAssets = [:] + } + reloadImages() + } + + // MARK: Internal + + private(set) var imageLayers: [ImageCompositionLayer] + let imageAssets: [String: ImageAsset] + + var imageProvider: AnimationImageProvider { + didSet { + reloadImages() + } + } + + func addImageLayers(_ layers: [ImageCompositionLayer]) { + for layer in layers { + if imageAssets[layer.imageReferenceID] != nil { + /// Found a linking asset in our asset library. Add layer + imageLayers.append(layer) + } + } + } + + func reloadImages() { + for imageLayer in imageLayers { + if let asset = imageAssets[imageLayer.imageReferenceID] { + imageLayer.image = imageProvider.imageForAsset(asset: asset) + imageLayer.imageContentsGravity = imageProvider.contentsGravity(for: asset) + } + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..365ab26ab1dceb7306a75c795c40828f5ddacae2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift @@ -0,0 +1,38 @@ +// +// LayerTextProvider.swift +// lottie-ios-iOS +// +// Created by Alexandr Goncharov on 07/06/2019. +// + +/// Connects a LottieTextProvider to a group of text layers +final class LayerTextProvider { + + // MARK: Lifecycle + + init(textProvider: AnimationKeypathTextProvider) { + self.textProvider = textProvider + textLayers = [] + reloadTexts() + } + + // MARK: Internal + + private(set) var textLayers: [TextCompositionLayer] + + var textProvider: AnimationKeypathTextProvider { + didSet { + reloadTexts() + } + } + + func addTextLayers(_ layers: [TextCompositionLayer]) { + textLayers += layers + } + + func reloadTexts() { + for textLayer in textLayers { + textLayer.textProvider = textProvider + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..fc5d2dae9017d653f1cd25466d5beb5960304ff9 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift @@ -0,0 +1,150 @@ +// +// LayerTransformPropertyMap.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import QuartzCore + +// MARK: - LayerTransformProperties + +final class LayerTransformProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(transform: Transform) { + anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes)) + scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes)) + rotationX = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationX.keyframes)) + rotationY = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationY.keyframes)) + rotationZ = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationZ.keyframes)) + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes)) + + var propertyMap: [String: AnyNodeProperty] = [ + "Anchor Point" : anchor, + PropertyName.scale.rawValue : scale, + PropertyName.rotation.rawValue: rotationZ, + "Rotation X" : rotationX, + "Rotation Y" : rotationY, + "Rotation Z" : rotationZ, + PropertyName.opacity.rawValue : opacity, + ] + + if + let positionKeyframesX = transform.positionX?.keyframes, + let positionKeyframesY = transform.positionY?.keyframes + { + let xPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesX)) + let yPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesY)) + propertyMap["X Position"] = xPosition + propertyMap["Y Position"] = yPosition + positionX = xPosition + positionY = yPosition + position = nil + } else if let positionKeyframes = transform.position?.keyframes { + let position: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframes)) + propertyMap[PropertyName.position.rawValue] = position + self.position = position + positionX = nil + positionY = nil + } else { + position = nil + positionY = nil + positionX = nil + } + + keypathProperties = propertyMap + properties = Array(propertyMap.values) + } + + // MARK: Internal + + let keypathProperties: [String: AnyNodeProperty] + var keypathName = "Transform" + + let properties: [AnyNodeProperty] + + let anchor: NodeProperty + let scale: NodeProperty + let rotationX: NodeProperty + let rotationY: NodeProperty + let rotationZ: NodeProperty + let position: NodeProperty? + let positionX: NodeProperty? + let positionY: NodeProperty? + let opacity: NodeProperty + + var childKeypaths: [KeypathSearchable] { + [] + } +} + +// MARK: - LayerTransformNode + +class LayerTransformNode: AnimatorNode { + + // MARK: Lifecycle + + init(transform: Transform) { + transformProperties = LayerTransformProperties(transform: transform) + } + + // MARK: Internal + + let outputNode: NodeOutput = PassThroughOutputNode(parent: nil) + + let transformProperties: LayerTransformProperties + + var parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + var opacity: Float = 1 + var localTransform: CATransform3D = CATransform3DIdentity + var globalTransform: CATransform3D = CATransform3DIdentity + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + transformProperties + } + + func shouldRebuildOutputs(frame _: CGFloat) -> Bool { + hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame _: CGFloat) { + opacity = Float(transformProperties.opacity.value.cgFloatValue) * 0.01 + + let position: CGPoint = + if let point = transformProperties.position?.value.pointValue { + point + } else if + let xPos = transformProperties.positionX?.value.cgFloatValue, + let yPos = transformProperties.positionY?.value.cgFloatValue + { + CGPoint(x: xPos, y: yPos) + } else { + .zero + } + + localTransform = CATransform3D.makeTransform( + anchor: transformProperties.anchor.value.pointValue, + position: position, + scale: transformProperties.scale.value.sizeValue, + rotationX: transformProperties.rotationX.value.cgFloatValue, + rotationY: transformProperties.rotationY.value.cgFloatValue, + rotationZ: transformProperties.rotationZ.value.cgFloatValue, + skew: nil, + skewAxis: nil) + + if let parentNode = parentNode as? LayerTransformNode { + globalTransform = CATransform3DConcat(localTransform, parentNode.globalTransform) + } else { + globalTransform = localTransform + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift new file mode 100644 index 0000000000000000000000000000000000000000..53b9d3a770925aa1453b2ac6a20f6b5b405a0a6a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift @@ -0,0 +1,107 @@ +// +// ItemsExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +// MARK: - NodeTree + +final class NodeTree { + var rootNode: AnimatorNode? = nil + var transform: ShapeTransform? = nil + var renderContainers: [ShapeContainerLayer] = [] + var paths: [PathOutputNode] = [] + var childrenNodes: [AnimatorNode] = [] +} + +extension [ShapeItem] { + func initializeNodeTree() -> NodeTree { + let nodeTree = NodeTree() + + for item in self { + guard item.hidden == false, item.type != .unknown else { continue } + if let fill = item as? Fill { + let node = FillNode(parentNode: nodeTree.rootNode, fill: fill) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let stroke = item as? Stroke { + let node = StrokeNode(parentNode: nodeTree.rootNode, stroke: stroke) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let gradientFill = item as? GradientFill { + let node = GradientFillNode(parentNode: nodeTree.rootNode, gradientFill: gradientFill) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let gradientStroke = item as? GradientStroke { + let node = GradientStrokeNode(parentNode: nodeTree.rootNode, gradientStroke: gradientStroke) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let ellipse = item as? Ellipse { + let node = EllipseNode(parentNode: nodeTree.rootNode, ellipse: ellipse) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let rect = item as? Rectangle { + let node = RectangleNode(parentNode: nodeTree.rootNode, rectangle: rect) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let star = item as? Star { + switch star.starType { + case .none: + continue + + case .polygon: + let node = PolygonNode(parentNode: nodeTree.rootNode, star: star) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + + case .star: + let node = StarNode(parentNode: nodeTree.rootNode, star: star) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } + } else if let shape = item as? Shape { + let node = ShapeNode(parentNode: nodeTree.rootNode, shape: shape) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let trim = item as? Trim { + let node = TrimPathNode(parentNode: nodeTree.rootNode, trim: trim, upstreamPaths: nodeTree.paths) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let roundedCorners = item as? RoundedCorners { + let node = RoundedCornersNode( + parentNode: nodeTree.rootNode, + roundedCorners: roundedCorners, + upstreamPaths: nodeTree.paths) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let xform = item as? ShapeTransform { + nodeTree.transform = xform + continue + } else if let group = item as? Group { + let tree = group.items.initializeNodeTree() + let node = GroupNode(name: group.name, parentNode: nodeTree.rootNode, tree: tree) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + /// Now add all child paths to current tree + nodeTree.paths.append(contentsOf: tree.paths) + nodeTree.renderContainers.append(node.container) + } else if item is Repeater { + LottieLogger.shared.warn(""" + The Main Thread rendering engine doesn't currently support repeaters. + To play an animation with repeaters, you can use the Core Animation rendering engine instead. + """) + } + + if let pathNode = nodeTree.rootNode as? PathNode { + //// Add path container to the node tree + nodeTree.paths.append(pathNode.pathOutput) + } + + if let renderNode = nodeTree.rootNode as? RenderNode { + nodeTree.renderContainers.append(ShapeRenderLayer(renderer: renderNode.renderer)) + } + } + return nodeTree + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift new file mode 100644 index 0000000000000000000000000000000000000000..8702f2c59c1e4d4f7b0c93bc2d48ddcc23bd6691 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift @@ -0,0 +1,55 @@ +// +// NodeProperty.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. +class NodeProperty: AnyNodeProperty { + + // MARK: Lifecycle + + init(provider: AnyValueProvider) { + valueProvider = provider + originalValueProvider = valueProvider + typedContainer = ValueContainer(provider.value(frame: 0) as! T) + typedContainer.setNeedsUpdate() + } + + // MARK: Internal + + var valueProvider: AnyValueProvider + var originalValueProvider: AnyValueProvider + + var valueType: Any.Type { T.self } + + var value: T { + typedContainer.outputValue + } + + var valueContainer: AnyValueContainer { + typedContainer + } + + func needsUpdate(frame: CGFloat) -> Bool { + valueContainer.needsUpdate || valueProvider.hasUpdate(frame: frame) + } + + func setProvider(provider: AnyValueProvider) { + guard provider.valueType == valueType else { return } + valueProvider = provider + valueContainer.setNeedsUpdate() + } + + func update(frame: CGFloat) { + typedContainer.setValue(valueProvider.value(frame: frame), forFrame: frame) + } + + // MARK: Fileprivate + + fileprivate var typedContainer: ValueContainer +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift new file mode 100644 index 0000000000000000000000000000000000000000..132d96a8940dc497e3a71b0e23ca097b5405a3a4 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift @@ -0,0 +1,50 @@ +// +// AnyNodeProperty.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +// MARK: - AnyNodeProperty + +/// A property of a node. The node property holds a provider and a container +protocol AnyNodeProperty { + + /// Returns true if the property needs to recompute its stored value + func needsUpdate(frame: CGFloat) -> Bool + + /// Updates the property for the frame + func update(frame: CGFloat) + + /// The stored value container for the property + var valueContainer: AnyValueContainer { get } + + /// The value provider for the property + var valueProvider: AnyValueProvider { get } + + /// The original value provider for the property + var originalValueProvider: AnyValueProvider { get } + + /// The Type of the value provider + var valueType: Any.Type { get } + + /// Sets the value provider for the property. + func setProvider(provider: AnyValueProvider) +} + +extension AnyNodeProperty { + + /// Returns the most recently computed value for the keypath, returns nil if property wasn't found + func getValueOfType() -> T? { + valueContainer.value as? T + } + + /// Returns the most recently computed value for the keypath, returns nil if property wasn't found + func getValue() -> Any? { + valueContainer.value + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift new file mode 100644 index 0000000000000000000000000000000000000000..769d51cb1f19a4e04b25bd4c0071c1ac1a426745 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift @@ -0,0 +1,26 @@ +// +// AnyValueContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// The container for the value of a property. +protocol AnyValueContainer: AnyObject { + + /// The stored value of the container + var value: Any { get } + + /// Notifies the provider that it should update its container + func setNeedsUpdate() + + /// When true the container needs to have its value updated by its provider + var needsUpdate: Bool { get } + + /// The frame time of the last provided update + var lastUpdateFrame: CGFloat { get } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift new file mode 100644 index 0000000000000000000000000000000000000000..c91c99aa5baf8ea6ea8f75dd7a9499ffb96c265b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift @@ -0,0 +1,23 @@ +// +// KeypathSettable.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import QuartzCore + +/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath. +protocol KeypathSearchable { + + /// The name of the Keypath + var keypathName: String { get } + + /// A list of properties belonging to the keypath. + var keypathProperties: [String: AnyNodeProperty] { get } + + /// Children Keypaths + var childKeypaths: [KeypathSearchable] { get } + + var keypathLayer: CALayer? { get } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift new file mode 100644 index 0000000000000000000000000000000000000000..c3813d7b42179afe045a27c57366006509f7aac7 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift @@ -0,0 +1,43 @@ +// +// NodePropertyMap.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import QuartzCore + +// MARK: - NodePropertyMap + +protocol NodePropertyMap { + var properties: [AnyNodeProperty] { get } +} + +extension NodePropertyMap { + + var childKeypaths: [KeypathSearchable] { + [] + } + + var keypathLayer: CALayer? { + nil + } + + /// Checks if the node's local contents need to be rebuilt. + func needsLocalUpdate(frame: CGFloat) -> Bool { + for property in properties { + if property.needsUpdate(frame: frame) { + return true + } + } + return false + } + + /// Rebuilds only the local nodes that have an update for the frame + func updateNodeProperties(frame: CGFloat) { + for property in properties { + property.update(frame: frame) + } + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.swift new file mode 100644 index 0000000000000000000000000000000000000000..7717090966cbf4b0e4a83f6edc700e8baf061be5 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.swift @@ -0,0 +1,47 @@ +// +// ValueContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics +import Foundation + +/// A container for a node value that is Typed to T. +class ValueContainer: AnyValueContainer { + + // MARK: Lifecycle + + init(_ value: T) { + outputValue = value + } + + // MARK: Internal + + private(set) var lastUpdateFrame = CGFloat.infinity + + fileprivate(set) var needsUpdate = true + + var value: Any { + outputValue as Any + } + + var outputValue: T { + didSet { + needsUpdate = false + } + } + + func setValue(_ value: Any, forFrame: CGFloat) { + if let typedValue = value as? T { + needsUpdate = false + lastUpdateFrame = forFrame + outputValue = typedValue + } + } + + func setNeedsUpdate() { + needsUpdate = true + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift new file mode 100644 index 0000000000000000000000000000000000000000..53c95b834fb62bc4dd4e3280c6726f18768a8cd2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift @@ -0,0 +1,39 @@ +// +// KeyframeGroupInterpolator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import CoreGraphics +import Foundation + +/// A value provider that produces an array of values from an array of Keyframe Interpolators +final class GroupInterpolator: ValueProvider where ValueType: Interpolatable { + + // MARK: Lifecycle + + /// Initialize with an array of array of keyframes. + init(keyframeGroups: ContiguousArray>>) { + keyframeInterpolators = ContiguousArray(keyframeGroups.map { KeyframeInterpolator(keyframes: $0) }) + } + + // MARK: Internal + + let keyframeInterpolators: ContiguousArray> + + var valueType: Any.Type { + [ValueType].self + } + + var storage: ValueProviderStorage<[ValueType]> { + .closure { frame in + self.keyframeInterpolators.map { $0.value(frame: frame) as! ValueType } + } + } + + func hasUpdate(frame: CGFloat) -> Bool { + let updated = keyframeInterpolators.first(where: { $0.hasUpdate(frame: frame) }) + return updated != nil + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..b4bff3aac3c446a1fc0cd91b17f02a0a4dca0223 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift @@ -0,0 +1,43 @@ +// +// SingleValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/// Returns a value for every frame. +final class SingleValueProvider: ValueProvider { + + // MARK: Lifecycle + + init(_ value: ValueType) { + self.value = value + } + + // MARK: Internal + + var value: ValueType { + didSet { + hasUpdate = true + } + } + + var storage: ValueProviderStorage { + .singleValue(value) + } + + var valueType: Any.Type { + ValueType.self + } + + func hasUpdate(frame _: CGFloat) -> Bool { + hasUpdate + } + + // MARK: Private + + private var hasUpdate = true +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/DropShadowNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/DropShadowNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..7434f0acd9fcfe6a74a6b17cdb5752feb3dd47e4 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/DropShadowNode.swift @@ -0,0 +1,102 @@ +// Created by Lan Xu on 2024/6/7. +// Copyright © 2024 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - DropShadowNode + +final class DropShadowNode: LayerEffectNode { + + // MARK: Lifecycle + + init(model: DropShadowModel) { + properties = DropShadowNodeProperties(model: model) + } + + // MARK: Internal + + var properties: DropShadowNodeProperties + + var propertyMap: any NodePropertyMap { + properties + } + + func applyEffect(to layer: CALayer) { + if let opacity = properties.opacity?.value.cgFloatValue { + // Lottie animation files express opacity as a numerical percentage value + // (e.g. 0%, 50%, 100%) so we divide by 100 to get the decimal values + // expected by Core Animation (e.g. 0.0, 0.5, 1.0). + layer.shadowOpacity = Float(opacity / 100) + } + if + let angleDegrees = properties.angle?.value.cgFloatValue, + let distance = properties.distance?.value.cgFloatValue + { + // Lottie animation files express rotation in degrees + // (e.g. 90º, 180º, 360º) so we convert to radians to get the + // values expected by Core Animation (e.g. π/2, π, 2π) + let angleRadians = (angleDegrees * .pi) / 180 + + // Lottie animation files express the `shadowOffset` as (angle, distance) pair, + // which we convert to the expected x / y offset values: + let offsetX = distance * cos(angleRadians) + let offsetY = distance * sin(angleRadians) + layer.shadowOffset = .init(width: offsetX, height: offsetY) + } + layer.shadowColor = properties.color?.value.cgColorValue + layer.shadowRadius = properties.radius?.value.cgFloatValue ?? 0 + } + +} + +// MARK: - DropShadowNodeProperties + +final class DropShadowNodeProperties: NodePropertyMap { + + // MARK: Lifecycle + + init(model: DropShadowModel) { + if let opacityKeyframes = model._opacity?.keyframes { + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: opacityKeyframes)) + propertyMap[PropertyName.opacity.rawValue] = opacity + } else { + opacity = nil + } + if let radiusKeyframes = model._radius?.keyframes { + radius = NodeProperty(provider: KeyframeInterpolator(keyframes: radiusKeyframes)) + propertyMap["Radius"] = radius + } else { + radius = nil + } + if let colorKeyFrames = model._color?.keyframes { + color = NodeProperty(provider: KeyframeInterpolator(keyframes: colorKeyFrames)) + propertyMap[PropertyName.color.rawValue] = color + } else { + color = nil + } + if let angleKeyFrames = model._angle?.keyframes { + angle = NodeProperty(provider: KeyframeInterpolator(keyframes: angleKeyFrames)) + propertyMap["Angle"] = angle + } else { + angle = nil + } + if let distanceKeyFrame = model._distance?.keyframes { + distance = NodeProperty(provider: KeyframeInterpolator(keyframes: distanceKeyFrame)) + propertyMap["Distance"] = distance + } else { + distance = nil + } + properties = Array(propertyMap.values) + } + + // MARK: Internal + + var propertyMap: [String: AnyNodeProperty] = [:] + var properties: [AnyNodeProperty] + + let opacity: NodeProperty? + let radius: NodeProperty? + let color: NodeProperty? + let angle: NodeProperty? + let distance: NodeProperty? +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/LayerEffectNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/LayerEffectNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..69686cf196b6fdd2fb6374f63dd632be273b6d2f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/LayerEffectNode.swift @@ -0,0 +1,24 @@ +// Created by Lan Xu on 2024/6/8. +// Copyright © 2024 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - LayerEffectNode + +protocol LayerEffectNode { + func applyEffect(to layer: CALayer) + var propertyMap: NodePropertyMap { get } +} + +extension LayerEffectNode { + + func updateWithFrame(layer: CALayer, frame: CGFloat) { + for property in propertyMap.properties { + if property.needsUpdate(frame: frame) { + property.update(frame: frame) + } + } + applyEffect(to: layer) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/RoundedCornersNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/RoundedCornersNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..67be54b5cf3ffad16d10ce8f6deeabc490831668 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/RoundedCornersNode.swift @@ -0,0 +1,85 @@ +// +// RoundedCornersNode.swift +// Lottie +// +// Created by Duolingo on 10/31/22. +// + +import Foundation +import QuartzCore + +// MARK: - RoundedCornersProperties + +final class RoundedCornersProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(roundedCorners: RoundedCorners) { + keypathName = roundedCorners.name + radius = NodeProperty(provider: KeyframeInterpolator(keyframes: roundedCorners.radius.keyframes)) + keypathProperties = ["Radius" : radius] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + let keypathName: String + + let radius: NodeProperty +} + +// MARK: - RoundedCornersNode + +final class RoundedCornersNode: AnimatorNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, roundedCorners: RoundedCorners, upstreamPaths: [PathOutputNode]) { + outputNode = PassThroughOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + properties = RoundedCornersProperties(roundedCorners: roundedCorners) + self.upstreamPaths = upstreamPaths + } + + // MARK: Internal + + let properties: RoundedCornersProperties + + let parentNode: AnimatorNode? + let outputNode: NodeOutput + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + func forceUpstreamOutputUpdates() -> Bool { + hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame: CGFloat) { + for pathContainer in upstreamPaths { + let pathObjects = pathContainer.removePaths(updateFrame: frame) + for path in pathObjects { + let cornerRadius = properties.radius.value.cgFloatValue + if cornerRadius != 0 { + pathContainer.appendPath( + path.roundCorners(radius: cornerRadius), + updateFrame: frame) + } else { + pathContainer.appendPath(path, updateFrame: frame) + } + } + } + } + + // MARK: Fileprivate + + fileprivate let upstreamPaths: [PathOutputNode] +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..13000dc332ff411727f78d03d1ee8d19810482e1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift @@ -0,0 +1,280 @@ +// +// TrimPathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import Foundation +import QuartzCore + +// MARK: - TrimPathProperties + +final class TrimPathProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(trim: Trim) { + keypathName = trim.name + start = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.start.keyframes)) + end = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.end.keyframes)) + offset = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.offset.keyframes)) + type = trim.trimType + keypathProperties = [ + "Start" : start, + "End" : end, + "Offset" : offset, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + let keypathName: String + + let start: NodeProperty + let end: NodeProperty + let offset: NodeProperty + let type: TrimType +} + +// MARK: - TrimPathNode + +final class TrimPathNode: AnimatorNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, trim: Trim, upstreamPaths: [PathOutputNode]) { + outputNode = PassThroughOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + properties = TrimPathProperties(trim: trim) + self.upstreamPaths = upstreamPaths + } + + // MARK: Internal + + let properties: TrimPathProperties + + let parentNode: AnimatorNode? + let outputNode: NodeOutput + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + func forceUpstreamOutputUpdates() -> Bool { + hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame: CGFloat) { + /// Make sure there is a trim. + let startValue = properties.start.value.cgFloatValue * 0.01 + let endValue = properties.end.value.cgFloatValue * 0.01 + let start = min(startValue, endValue) + let end = max(startValue, endValue) + + let offset = properties.offset.value.cgFloatValue.truncatingRemainder(dividingBy: 360) / 360 + + /// No need to trim, it's a full path + if start == 0, end == 1 { + return + } + + /// All paths are empty. + if start == end { + for pathContainer in upstreamPaths { + pathContainer.removePaths(updateFrame: frame) + } + return + } + + if properties.type == .simultaneously { + /// Just trim each path + for pathContainer in upstreamPaths { + let pathObjects = pathContainer.removePaths(updateFrame: frame) + for path in pathObjects { + // We are treating each compount path as an individual path. Its subpaths are treated as a whole. + pathContainer.appendPath( + path.trim(fromPosition: start, toPosition: end, offset: offset, trimSimultaneously: false), + updateFrame: frame) + } + } + return + } + + /// Individual path trimming. + + /// Brace yourself for the below code. + + /// Normalize lengths with offset. + var startPosition = (start + offset).truncatingRemainder(dividingBy: 1) + var endPosition = (end + offset).truncatingRemainder(dividingBy: 1) + + if startPosition < 0 { + startPosition = 1 + startPosition + } + + if endPosition < 0 { + endPosition = 1 + endPosition + } + if startPosition == 1 { + startPosition = 0 + } + if endPosition == 0 { + endPosition = 1 + } + + /// First get the total length of all paths. + var totalLength: CGFloat = 0 + for upstreamPath in upstreamPaths { + totalLength = totalLength + upstreamPath.totalLength + } + + /// Now determine the start and end cut lengths + let startLength = startPosition * totalLength + let endLength = endPosition * totalLength + var pathStart: CGFloat = 0 + + /// Now loop through all path containers + for pathContainer in upstreamPaths { + let pathEnd = pathStart + pathContainer.totalLength + + if + !startLength.isInRange(pathStart, pathEnd) && + endLength.isInRange(pathStart, pathEnd) + { + // pathStart|=======E----------------------|pathEnd + // Cut path components, removing after end. + + let pathCutLength = endLength - pathStart + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + if pathCutLength < subpathEnd { + /// This is the subpath that needs to be cut. + let cutLength = pathCutLength - subpathStart + let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } else { + /// Add to container and move on + pathContainer.appendPath(path, updateFrame: frame) + } + if pathCutLength == subpathEnd { + /// Right on the end. The next subpath is not included. Break. + break + } + subpathStart = subpathEnd + } + + } else if + !endLength.isInRange(pathStart, pathEnd) && + startLength.isInRange(pathStart, pathEnd) + { + // pathStart|-------S======================|pathEnd + // + + // Cut path components, removing before beginning. + let pathCutLength = startLength - pathStart + // Clear paths from container + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + + if subpathStart < pathCutLength, pathCutLength < subpathEnd { + /// This is the subpath that needs to be cut. + let cutLength = pathCutLength - subpathStart + let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + } else if pathCutLength <= subpathStart { + pathContainer.appendPath(path, updateFrame: frame) + } + subpathStart = subpathEnd + } + } else if + endLength.isInRange(pathStart, pathEnd) && + startLength.isInRange(pathStart, pathEnd) + { + // pathStart|-------S============E---------|endLength + // pathStart|=====E----------------S=======|endLength + // trim from path beginning to endLength. + + // Cut path components, removing before beginnings. + let startCutLength = startLength - pathStart + let endCutLength = endLength - pathStart + // Clear paths from container + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + + if + !startCutLength.isInRange(subpathStart, subpathEnd), + !endCutLength.isInRange(subpathStart, subpathEnd) + { + // The whole path is included. Add + // S|==============================|E + pathContainer.appendPath(path, updateFrame: frame) + + } else if + startCutLength.isInRange(subpathStart, subpathEnd), + !endCutLength.isInRange(subpathStart, subpathEnd) + { + /// The start of the path needs to be trimmed + // |-------S======================|E + let cutLength = startCutLength - subpathStart + let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + } else if + !startCutLength.isInRange(subpathStart, subpathEnd), + endCutLength.isInRange(subpathStart, subpathEnd) + { + // S|=======E----------------------| + let cutLength = endCutLength - subpathStart + let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } else if + startCutLength.isInRange(subpathStart, subpathEnd), + endCutLength.isInRange(subpathStart, subpathEnd) + { + // |-------S============E---------| + let cutFromLength = startCutLength - subpathStart + let cutToLength = endCutLength - subpathStart + let newPath = path.trim( + fromPosition: cutFromLength / path.length, + toPosition: cutToLength / path.length, + offset: 0, + trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } + + subpathStart = subpathEnd + } + } else if + (endLength <= pathStart && pathEnd <= startLength) || + (startLength <= pathStart && endLength <= pathStart) || + (pathEnd <= startLength && pathEnd <= endLength) + { + /// The Path needs to be cleared + pathContainer.removePaths(updateFrame: frame) + } + + pathStart = pathEnd + } + } + + // MARK: Fileprivate + + fileprivate let upstreamPaths: [PathOutputNode] +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..6c54d4919ad81575f221ef31d033bf4e7569ece0 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift @@ -0,0 +1,74 @@ +// +// TransformNodeOutput.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +class GroupOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: NodeOutput?, rootNode: NodeOutput?) { + self.parent = parent + self.rootNode = rootNode + } + + // MARK: Internal + + let parent: NodeOutput? + let rootNode: NodeOutput? + var isEnabled = true + + private(set) var outputPath: CGPath? = nil + private(set) var transform: CATransform3D = CATransform3DIdentity + + func setTransform(_ xform: CATransform3D, forFrame _: CGFloat) { + transform = xform + outputPath = nil + } + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + guard isEnabled else { + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + outputPath = parent?.outputPath + return upstreamUpdates + } + + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + if upstreamUpdates { + outputPath = nil + } + let rootUpdates = rootNode?.hasOutputUpdates(forFrame) ?? false + if rootUpdates { + outputPath = nil + } + + var localUpdates = false + if outputPath == nil { + localUpdates = true + + let newPath = CGMutablePath() + if let parentNode = parent, let parentPath = parentNode.outputPath { + /// First add parent path. + newPath.addPath(parentPath) + } + var xform = CATransform3DGetAffineTransform(transform) + if + let rootNode, + let rootPath = rootNode.outputPath, + let xformedPath = rootPath.copy(using: &xform) + { + /// Now add root path. Note root path is transformed. + newPath.addPath(xformedPath) + } + + outputPath = newPath + } + + return upstreamUpdates || localUpdates + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..f044b7ba4b0adcd1f4838472ed01c09fa4601324 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift @@ -0,0 +1,46 @@ +// +// PassThroughOutputNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics + +class PassThroughOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: NodeOutput?) { + self.parent = parent + } + + // MARK: Internal + + let parent: NodeOutput? + + var hasUpdate = false + var isEnabled = true + + var outputPath: CGPath? { + if let parent { + return parent.outputPath + } + return nil + } + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + /// Changes to this node do not affect downstream nodes. + let parentUpdate = parent?.hasOutputUpdates(forFrame) ?? false + /// Changes to upstream nodes do, however, affect this nodes state. + hasUpdate = hasUpdate || parentUpdate + return parentUpdate + } + + func hasRenderUpdates(_ forFrame: CGFloat) -> Bool { + /// Return true if there are upstream updates or if this node has updates + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + hasUpdate = hasUpdate || upstreamUpdates + return hasUpdate + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..b5c600c20514ee0feb0cff2e90b08b5f201a51be --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift @@ -0,0 +1,89 @@ +// +// PathNodeOutput.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import CoreGraphics + +/// A node that has an output of a BezierPath +class PathOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: NodeOutput?) { + self.parent = parent + } + + // MARK: Internal + + let parent: NodeOutput? + + fileprivate(set) var outputPath: CGPath? = nil + + var lastUpdateFrame: CGFloat? = nil + var lastPathBuildFrame: CGFloat? = nil + var isEnabled = true + fileprivate(set) var totalLength: CGFloat = 0 + fileprivate(set) var pathObjects: [CompoundBezierPath] = [] + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + guard isEnabled else { + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + outputPath = parent?.outputPath + return upstreamUpdates + } + + /// Ask if parent was updated + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + + /// If parent was updated and the path hasn't been built for this frame, clear the path. + if upstreamUpdates && lastPathBuildFrame != forFrame { + outputPath = nil + } + + if outputPath == nil { + /// If the path is clear, build the new path. + lastPathBuildFrame = forFrame + let newPath = CGMutablePath() + if let parentNode = parent, let parentPath = parentNode.outputPath { + newPath.addPath(parentPath) + } + for path in pathObjects { + for subPath in path.paths { + newPath.addPath(subPath.cgPath()) + } + } + outputPath = newPath + } + + /// Return true if there were upstream updates or if this node was updated. + return upstreamUpdates || (lastUpdateFrame == forFrame) + } + + @discardableResult + func removePaths(updateFrame: CGFloat?) -> [CompoundBezierPath] { + lastUpdateFrame = updateFrame + let returnPaths = pathObjects + outputPath = nil + totalLength = 0 + pathObjects = [] + return returnPaths + } + + func setPath(_ path: BezierPath, updateFrame: CGFloat) { + lastUpdateFrame = updateFrame + outputPath = nil + totalLength = path.length + pathObjects = [CompoundBezierPath(path: path)] + } + + func appendPath(_ path: CompoundBezierPath, updateFrame: CGFloat) { + lastUpdateFrame = updateFrame + outputPath = nil + totalLength = totalLength + path.length + pathObjects.append(path) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift new file mode 100644 index 0000000000000000000000000000000000000000..c858909a9dcbb4bf956392f1caf2639e4200a49b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift @@ -0,0 +1,69 @@ +// +// FillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +extension FillRule { + var cgFillRule: CGPathFillRule { + switch self { + case .evenOdd: + .evenOdd + default: + .winding + } + } + + var caFillRule: CAShapeLayerFillRule { + switch self { + case .evenOdd: + CAShapeLayerFillRule.evenOdd + default: + CAShapeLayerFillRule.nonZero + } + } +} + +// MARK: - FillRenderer + +/// A rendered for a Path Fill +final class FillRenderer: PassThroughOutputNode, Renderable { + var shouldRenderInContext = false + + var color: CGColor? { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var fillRule: FillRule = .none { + didSet { + hasUpdate = true + } + } + + func render(_: CGContext) { + // do nothing + } + + func setupSublayers(layer _: CAShapeLayer) { + // do nothing + } + + func updateShapeLayer(layer: CAShapeLayer) { + layer.fillColor = color + layer.opacity = Float(opacity) + layer.fillRule = fillRule.caFillRule + hasUpdate = false + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift new file mode 100644 index 0000000000000000000000000000000000000000..d011ad61d3d52b0fabbb10209a8b27a1a876f690 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift @@ -0,0 +1,246 @@ +// +// GradientFillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +// MARK: - GradientFillLayer + +private final class GradientFillLayer: CALayer { + + var start: CGPoint = .zero { + didSet { + setNeedsDisplay() + } + } + + var numberOfColors = 0 { + didSet { + setNeedsDisplay() + } + } + + var colors: [CGFloat] = [] { + didSet { + setNeedsDisplay() + } + } + + var end: CGPoint = .zero { + didSet { + setNeedsDisplay() + } + } + + var type: GradientType = .none { + didSet { + setNeedsDisplay() + } + } + + override func draw(in ctx: CGContext) { + var alphaColors = [CGColor]() + var alphaLocations = [CGFloat]() + + var gradientColors = [CGColor]() + var colorLocations = [CGFloat]() + let maskColorSpace = CGColorSpaceCreateDeviceGray() + for i in 0.. ix { + let color = CGColor.rgb(colors[ix + 1], colors[ix + 2], colors[ix + 3]) + gradientColors.append(color) + colorLocations.append(colors[ix]) + } + } + + var drawMask = false + for i in stride(from: numberOfColors * 4, to: colors.endIndex, by: 2) { + let alpha = colors[i + 1] + if alpha < 1 { + drawMask = true + } + alphaLocations.append(colors[i]) + alphaColors.append(.gray(alpha)) + } + + /// First draw a mask is necessary. + if drawMask { + guard + let maskGradient = CGGradient( + colorsSpace: maskColorSpace, + colors: alphaColors as CFArray, + locations: alphaLocations), + let maskContext = CGContext( + data: nil, + width: ctx.width, + height: ctx.height, + bitsPerComponent: 8, + bytesPerRow: ctx.width, + space: maskColorSpace, + bitmapInfo: 0) + else { return } + let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height)) + maskContext.concatenate(flipVertical) + maskContext.concatenate(ctx.ctm) + if type == .linear { + maskContext.drawLinearGradient( + maskGradient, + start: start, + end: end, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + maskContext.drawRadialGradient( + maskGradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + /// Clips the gradient + if let alphaMask = maskContext.makeImage() { + ctx.clip(to: ctx.boundingBoxOfClipPath, mask: alphaMask) + } + } + + /// Now draw the gradient + guard + let gradient = CGGradient( + colorsSpace: LottieConfiguration.shared.colorSpace, + colors: gradientColors as CFArray, + locations: colorLocations) + else { return } + + if type == .linear { + ctx.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + ctx.drawRadialGradient( + gradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + } + +} + +// MARK: - GradientFillRenderer + +/// A rendered for a Path Fill +final class GradientFillRenderer: PassThroughOutputNode, Renderable { + + // MARK: Lifecycle + + override init(parent: NodeOutput?) { + super.init(parent: parent) + + maskLayer.fillColor = .rgb(1, 1, 1) + gradientLayer.mask = maskLayer + + maskLayer.actions = [ + "startPoint" : NSNull(), + "endPoint" : NSNull(), + "opacity" : NSNull(), + "locations" : NSNull(), + "colors" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "isRadial" : NSNull(), + "path" : NSNull(), + ] + gradientLayer.actions = maskLayer.actions + } + + // MARK: Internal + + var shouldRenderInContext = false + + var start: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var numberOfColors = 0 { + didSet { + hasUpdate = true + } + } + + var colors: [CGFloat] = [] { + didSet { + hasUpdate = true + } + } + + var end: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var type: GradientType = .none { + didSet { + hasUpdate = true + } + } + + var fillRule: CAShapeLayerFillRule { + get { maskLayer.fillRule } + set { maskLayer.fillRule = newValue } + } + + func render(_: CGContext) { + // do nothing + } + + func setupSublayers(layer: CAShapeLayer) { + layer.addSublayer(gradientLayer) + layer.fillColor = nil + } + + func updateShapeLayer(layer: CAShapeLayer) { + hasUpdate = false + + guard let path = layer.path else { + return + } + + let frame = path.boundingBox + let anchor = CGPoint( + x: -frame.origin.x / frame.size.width, + y: -frame.origin.y / frame.size.height) + maskLayer.path = path + maskLayer.bounds = path.boundingBox + maskLayer.anchorPoint = anchor + + gradientLayer.bounds = maskLayer.bounds + gradientLayer.anchorPoint = anchor + + // setup gradient properties + gradientLayer.start = start + gradientLayer.end = end + gradientLayer.numberOfColors = numberOfColors + gradientLayer.colors = colors + gradientLayer.opacity = Float(opacity) + gradientLayer.type = type + } + + // MARK: Private + + private let gradientLayer = GradientFillLayer() + private let maskLayer = CAShapeLayer() + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift new file mode 100644 index 0000000000000000000000000000000000000000..531b2559d2aeca13d45c5958ec616e0ca4395e82 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift @@ -0,0 +1,64 @@ +// +// GradientStrokeRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +// MARK: - Renderer + +final class GradientStrokeRenderer: PassThroughOutputNode, Renderable { + + // MARK: Lifecycle + + override init(parent: NodeOutput?) { + strokeRender = StrokeRenderer(parent: nil) + gradientRender = LegacyGradientFillRenderer(parent: nil) + strokeRender.color = .rgb(1, 1, 1) + super.init(parent: parent) + } + + // MARK: Internal + + var shouldRenderInContext = true + + let strokeRender: StrokeRenderer + let gradientRender: LegacyGradientFillRenderer + + override func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + let updates = super.hasOutputUpdates(forFrame) + return updates || strokeRender.hasUpdate || gradientRender.hasUpdate + } + + func updateShapeLayer(layer _: CAShapeLayer) { + /// Not Applicable + } + + func setupSublayers(layer _: CAShapeLayer) { + /// Not Applicable + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil, inContext.path!.isEmpty == false else { + return + } + + strokeRender.hasUpdate = false + hasUpdate = false + gradientRender.hasUpdate = false + + strokeRender.setupForStroke(inContext) + + inContext.replacePathWithStrokedPath() + + /// Now draw the gradient. + gradientRender.render(inContext) + } + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + strokeRender.renderBoundsFor(boundingBox) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift new file mode 100644 index 0000000000000000000000000000000000000000..953548bd545fefd53bf331a5b3405accb2ed683d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift @@ -0,0 +1,152 @@ +// +// LegacyGradientFillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +/// A rendered for a Path Fill +final class LegacyGradientFillRenderer: PassThroughOutputNode, Renderable { + + var shouldRenderInContext = true + + var start: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var numberOfColors = 0 { + didSet { + hasUpdate = true + } + } + + var colors: [CGFloat] = [] { + didSet { + hasUpdate = true + } + } + + var end: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var type: GradientType = .none { + didSet { + hasUpdate = true + } + } + + func updateShapeLayer(layer _: CAShapeLayer) { + // Not applicable + } + + func setupSublayers(layer _: CAShapeLayer) { + // Not applicable + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil, inContext.path!.isEmpty == false else { + return + } + hasUpdate = false + var alphaColors = [CGColor]() + var alphaLocations = [CGFloat]() + + var gradientColors = [CGColor]() + var colorLocations = [CGFloat]() + let maskColorSpace = CGColorSpaceCreateDeviceGray() + for i in 0.. ix { + let color = CGColor.rgb(colors[ix + 1], colors[ix + 2], colors[ix + 3]) + gradientColors.append(color) + colorLocations.append(colors[ix]) + } + } + + var drawMask = false + for i in stride(from: numberOfColors * 4, to: colors.endIndex, by: 2) { + let alpha = colors[i + 1] + if alpha < 1 { + drawMask = true + } + alphaLocations.append(colors[i]) + alphaColors.append(.gray(alpha)) + } + + inContext.setAlpha(opacity) + inContext.clip() + + /// First draw a mask is necessary. + if drawMask { + guard + let maskGradient = CGGradient( + colorsSpace: maskColorSpace, + colors: alphaColors as CFArray, + locations: alphaLocations), + let maskContext = CGContext( + data: nil, + width: inContext.width, + height: inContext.height, + bitsPerComponent: 8, + bytesPerRow: inContext.width, + space: maskColorSpace, + bitmapInfo: 0) + else { return } + let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height)) + maskContext.concatenate(flipVertical) + maskContext.concatenate(inContext.ctm) + if type == .linear { + maskContext.drawLinearGradient( + maskGradient, + start: start, + end: end, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + maskContext.drawRadialGradient( + maskGradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + /// Clips the gradient + if let alphaMask = maskContext.makeImage() { + inContext.clip(to: inContext.boundingBoxOfClipPath, mask: alphaMask) + } + } + + /// Now draw the gradient + guard + let gradient = CGGradient( + colorsSpace: LottieConfiguration.shared.colorSpace, + colors: gradientColors as CFArray, + locations: colorLocations) + else { return } + + if type == .linear { + inContext.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + inContext.drawRadialGradient( + gradient, + startCenter: start, + startRadius: 0, + endCenter: start, + endRadius: start.distanceTo(end), + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift new file mode 100644 index 0000000000000000000000000000000000000000..2e400e43e5e52fb4046ac689c569c6cd29872f32 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift @@ -0,0 +1,165 @@ +// +// StrokeRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +extension LineJoin { + var cgLineJoin: CGLineJoin { + switch self { + case .bevel: + .bevel + case .none: + .miter + case .miter: + .miter + case .round: + .round + } + } + + var caLineJoin: CAShapeLayerLineJoin { + switch self { + case .none: + CAShapeLayerLineJoin.miter + case .miter: + CAShapeLayerLineJoin.miter + case .round: + CAShapeLayerLineJoin.round + case .bevel: + CAShapeLayerLineJoin.bevel + } + } +} + +extension LineCap { + var cgLineCap: CGLineCap { + switch self { + case .none: + .butt + case .butt: + .butt + case .round: + .round + case .square: + .square + } + } + + var caLineCap: CAShapeLayerLineCap { + switch self { + case .none: + CAShapeLayerLineCap.butt + case .butt: + CAShapeLayerLineCap.butt + case .round: + CAShapeLayerLineCap.round + case .square: + CAShapeLayerLineCap.square + } + } +} + +// MARK: - StrokeRenderer + +/// A rendered that renders a stroke on a path. +final class StrokeRenderer: PassThroughOutputNode, Renderable { + + var shouldRenderInContext = false + + var color: CGColor? { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var width: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var miterLimit: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var lineCap: LineCap = .none { + didSet { + hasUpdate = true + } + } + + var lineJoin: LineJoin = .none { + didSet { + hasUpdate = true + } + } + + var dashPhase: CGFloat? { + didSet { + hasUpdate = true + } + } + + var dashLengths: [CGFloat]? { + didSet { + hasUpdate = true + } + } + + func setupSublayers(layer _: CAShapeLayer) { + // empty + } + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + boundingBox.insetBy(dx: -width, dy: -width) + } + + func setupForStroke(_ inContext: CGContext) { + inContext.setLineWidth(width) + inContext.setMiterLimit(miterLimit) + inContext.setLineCap(lineCap.cgLineCap) + inContext.setLineJoin(lineJoin.cgLineJoin) + if let dashPhase, let lengths = dashLengths { + inContext.setLineDash(phase: dashPhase, lengths: lengths) + } else { + inContext.setLineDash(phase: 0, lengths: []) + } + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil, inContext.path!.isEmpty == false else { + return + } + guard let color else { return } + hasUpdate = false + setupForStroke(inContext) + inContext.setAlpha(opacity) + inContext.setStrokeColor(color) + inContext.strokePath() + } + + func updateShapeLayer(layer: CAShapeLayer) { + layer.strokeColor = color + layer.opacity = Float(opacity) + layer.lineWidth = width + layer.lineJoin = lineJoin.caLineJoin + layer.lineCap = lineCap.caLineCap + layer.lineDashPhase = dashPhase ?? 0 + layer.fillColor = nil + if let dashPattern = dashLengths { + layer.lineDashPattern = dashPattern.map { NSNumber(value: Double($0)) } + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..a5c40e6b63bd7f93f919b4be167f5a9c2708c6cc --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift @@ -0,0 +1,139 @@ +// +// EllipseNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation +import QuartzCore + +// MARK: - EllipseNodeProperties + +final class EllipseNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(ellipse: Ellipse) { + keypathName = ellipse.name + direction = ellipse.direction + position = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.position.keyframes)) + size = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.size.keyframes)) + keypathProperties = [ + PropertyName.position.rawValue : position, + "Size" : size, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let direction: PathDirection + let position: NodeProperty + let size: NodeProperty + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] +} + +// MARK: - EllipseNode + +final class EllipseNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, ellipse: Ellipse) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = EllipseNodeProperties(ellipse: ellipse) + self.parentNode = parentNode + } + + // MARK: Internal + + static let ControlPointConstant: CGFloat = 0.55228 + + let pathOutput: PathOutputNode + + let properties: EllipseNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath( + .ellipse( + size: properties.size.value.sizeValue, + center: properties.position.value.pointValue, + direction: properties.direction), + updateFrame: frame) + } + +} + +extension BezierPath { + /// Constructs a `BezierPath` in the shape of an ellipse + static func ellipse( + size: CGSize, + center: CGPoint, + direction: PathDirection) + -> BezierPath + { + // Unfortunately we HAVE to manually build out the ellipse. + // Every Apple method constructs an ellipse from the 3 o-clock position + // After effects constructs from the Noon position. + // After effects does clockwise, but also has a flag for reversed. + var half = size * 0.5 + if direction == .counterClockwise { + half.width = half.width * -1 + } + + let q1 = CGPoint(x: center.x, y: center.y - half.height) + let q2 = CGPoint(x: center.x + half.width, y: center.y) + let q3 = CGPoint(x: center.x, y: center.y + half.height) + let q4 = CGPoint(x: center.x - half.width, y: center.y) + + let cp = half * EllipseNode.ControlPointConstant + + var path = BezierPath(startPoint: CurveVertex( + point: q1, + inTangentRelative: CGPoint(x: -cp.width, y: 0), + outTangentRelative: CGPoint(x: cp.width, y: 0))) + path.addVertex(CurveVertex( + point: q2, + inTangentRelative: CGPoint(x: 0, y: -cp.height), + outTangentRelative: CGPoint(x: 0, y: cp.height))) + + path.addVertex(CurveVertex( + point: q3, + inTangentRelative: CGPoint(x: cp.width, y: 0), + outTangentRelative: CGPoint(x: -cp.width, y: 0))) + + path.addVertex(CurveVertex( + point: q4, + inTangentRelative: CGPoint(x: 0, y: cp.height), + outTangentRelative: CGPoint(x: 0, y: -cp.height))) + + path.addVertex(CurveVertex( + point: q1, + inTangentRelative: CGPoint(x: -cp.width, y: 0), + outTangentRelative: CGPoint(x: cp.width, y: 0))) + path.close() + return path + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..49e502b9c4bda75d13873715e15dd7b2850d06ed --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift @@ -0,0 +1,170 @@ +// +// PolygonNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +// MARK: - PolygonNodeProperties + +final class PolygonNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(star: Star) { + keypathName = star.name + direction = star.direction + position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) + outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) + outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) + rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) + points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) + keypathProperties = [ + PropertyName.position.rawValue : position, + "Outer Radius" : outerRadius, + "Outer Roundedness" : outerRoundedness, + PropertyName.rotation.rawValue : rotation, + "Points" : points, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + var childKeypaths: [KeypathSearchable] = [] + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + let direction: PathDirection + let position: NodeProperty + let outerRadius: NodeProperty + let outerRoundedness: NodeProperty + let rotation: NodeProperty + let points: NodeProperty +} + +// MARK: - PolygonNode + +final class PolygonNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, star: Star) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = PolygonNodeProperties(star: star) + self.parentNode = parentNode + } + + // MARK: Internal + + /// Magic number needed for constructing path. + static let PolygonConstant: CGFloat = 0.25 + + let properties: PolygonNodeProperties + + let pathOutput: PathOutputNode + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + let path = BezierPath.polygon( + position: properties.position.value.pointValue, + numberOfPoints: properties.points.value.cgFloatValue, + outerRadius: properties.outerRadius.value.cgFloatValue, + outerRoundedness: properties.outerRoundedness.value.cgFloatValue, + rotation: properties.rotation.value.cgFloatValue, + direction: properties.direction) + + pathOutput.setPath(path, updateFrame: frame) + } + +} + +extension BezierPath { + /// Creates a `BezierPath` in the shape of a polygon + static func polygon( + position: CGPoint, + numberOfPoints: CGFloat, + outerRadius: CGFloat, + outerRoundedness inputOuterRoundedness: CGFloat, + rotation: CGFloat, + direction: PathDirection) + -> BezierPath + { + var currentAngle = (rotation - 90).toRadians() + let anglePerPoint = ((2 * CGFloat.pi) / numberOfPoints) + let outerRoundedness = inputOuterRoundedness * 0.01 + + var point = CGPoint( + x: outerRadius * cos(currentAngle), + y: outerRadius * sin(currentAngle)) + var vertices = [CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)] + + var previousPoint = point + currentAngle += anglePerPoint + for _ in 0.. + let size: NodeProperty + let cornerRadius: NodeProperty + +} + +// MARK: - RectangleNode + +final class RectangleNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, rectangle: Rectangle) { + properties = RectNodeProperties(rectangle: rectangle) + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + } + + // MARK: Internal + + let properties: RectNodeProperties + + let pathOutput: PathOutputNode + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath( + .rectangle( + position: properties.position.value.pointValue, + size: properties.size.value.sizeValue, + cornerRadius: properties.cornerRadius.value.cgFloatValue, + direction: properties.direction), + updateFrame: frame) + } + +} + +// MARK: - BezierPath + rectangle + +extension BezierPath { + /// Constructs a `BezierPath` in the shape of a rectangle, optionally with rounded corners + static func rectangle( + position: CGPoint, + size inputSize: CGSize, + cornerRadius: CGFloat, + direction: PathDirection) + -> BezierPath + { + let size = inputSize * 0.5 + let radius = min(min(cornerRadius, size.width) , size.height) + + var bezierPath = BezierPath() + let points: [CurveVertex] + + if radius <= 0 { + /// No Corners + points = [ + /// Lead In + CurveVertex( + point: CGPoint(x: size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 1 + CurveVertex( + point: CGPoint(x: size.width, y: size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 2 + CurveVertex( + point: CGPoint(x: -size.width, y: size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 3 + CurveVertex( + point: CGPoint(x: -size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 4 + CurveVertex( + point: CGPoint(x: size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + ] + } else { + let controlPoint = radius * EllipseNode.ControlPointConstant + points = [ + /// Lead In + CurveVertex( + CGPoint(x: radius, y: 0), + CGPoint(x: radius, y: 0), + CGPoint(x: radius, y: 0)) + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + /// Corner 1 + CurveVertex( + CGPoint(x: radius, y: 0), // In tangent + CGPoint(x: radius, y: 0), // Point + CGPoint(x: radius, y: controlPoint)) + .translated(CGPoint(x: -radius, y: -radius)) + .translated(CGPoint(x: size.width, y: size.height)) + .translated(position), + CurveVertex( + CGPoint(x: controlPoint, y: radius), // In tangent + CGPoint(x: 0, y: radius), // Point + CGPoint(x: 0, y: radius)) // Out Tangent + .translated(CGPoint(x: -radius, y: -radius)) + .translated(CGPoint(x: size.width, y: size.height)) + .translated(position), + /// Corner 2 + CurveVertex( + CGPoint(x: 0, y: radius), // In tangent + CGPoint(x: 0, y: radius), // Point + CGPoint(x: -controlPoint, y: radius)) // Out tangent + .translated(CGPoint(x: radius, y: -radius)) + .translated(CGPoint(x: -size.width, y: size.height)) + .translated(position), + CurveVertex( + CGPoint(x: -radius, y: controlPoint), // In tangent + CGPoint(x: -radius, y: 0), // Point + CGPoint(x: -radius, y: 0)) // Out tangent + .translated(CGPoint(x: radius, y: -radius)) + .translated(CGPoint(x: -size.width, y: size.height)) + .translated(position), + /// Corner 3 + CurveVertex( + CGPoint(x: -radius, y: 0), // In tangent + CGPoint(x: -radius, y: 0), // Point + CGPoint(x: -radius, y: -controlPoint)) // Out tangent + .translated(CGPoint(x: radius, y: radius)) + .translated(CGPoint(x: -size.width, y: -size.height)) + .translated(position), + CurveVertex( + CGPoint(x: -controlPoint, y: -radius), // In tangent + CGPoint(x: 0, y: -radius), // Point + CGPoint(x: 0, y: -radius)) // Out tangent + .translated(CGPoint(x: radius, y: radius)) + .translated(CGPoint(x: -size.width, y: -size.height)) + .translated(position), + /// Corner 4 + CurveVertex( + CGPoint(x: 0, y: -radius), // In tangent + CGPoint(x: 0, y: -radius), // Point + CGPoint(x: controlPoint, y: -radius)) // Out tangent + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + CurveVertex( + CGPoint(x: radius, y: -controlPoint), // In tangent + CGPoint(x: radius, y: 0), // Point + CGPoint(x: radius, y: 0)) // Out tangent + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + ] + } + let reversed = direction == .counterClockwise + let pathPoints = reversed ? points.reversed() : points + for point in pathPoints { + bezierPath.addVertex(reversed ? point.reversed() : point) + } + bezierPath.close() + return bezierPath + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..7bc7d9055d20215916097411a5ac2d87c2ed1038 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift @@ -0,0 +1,74 @@ +// +// PathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/16/19. +// + +import CoreGraphics +import Foundation + +// MARK: - ShapeNodeProperties + +final class ShapeNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(shape: Shape) { + keypathName = shape.name + path = NodeProperty(provider: KeyframeInterpolator(keyframes: shape.path.keyframes)) + keypathProperties = [ + "Path" : path, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let path: NodeProperty + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - ShapeNode + +final class ShapeNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, shape: Shape) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = ShapeNodeProperties(shape: shape) + self.parentNode = parentNode + } + + // MARK: Internal + + let properties: ShapeNodeProperties + + let pathOutput: PathOutputNode + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath(properties.path.value, updateFrame: frame) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..bf1753108fe89602b8aa6faf42f934673add4552 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift @@ -0,0 +1,222 @@ +// +// StarNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +// MARK: - StarNodeProperties + +final class StarNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(star: Star) { + keypathName = star.name + direction = star.direction + position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) + outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) + outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) + if let innerRadiusKeyframes = star.innerRadius?.keyframes { + innerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: innerRadiusKeyframes)) + } else { + innerRadius = NodeProperty(provider: SingleValueProvider(LottieVector1D(0))) + } + if let innderRoundedness = star.innerRoundness?.keyframes { + innerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: innderRoundedness)) + } else { + innerRoundedness = NodeProperty(provider: SingleValueProvider(LottieVector1D(0))) + } + rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) + points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) + keypathProperties = [ + PropertyName.position.rawValue : position, + "Outer Radius" : outerRadius, + "Outer Roundedness" : outerRoundedness, + "Inner Radius" : innerRadius, + "Inner Roundedness" : innerRoundedness, + PropertyName.rotation.rawValue : rotation, + "Points" : points, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + let direction: PathDirection + let position: NodeProperty + let outerRadius: NodeProperty + let outerRoundedness: NodeProperty + let innerRadius: NodeProperty + let innerRoundedness: NodeProperty + let rotation: NodeProperty + let points: NodeProperty +} + +// MARK: - StarNode + +final class StarNode: AnimatorNode, PathNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, star: Star) { + pathOutput = PathOutputNode(parent: parentNode?.outputNode) + properties = StarNodeProperties(star: star) + self.parentNode = parentNode + } + + // MARK: Internal + + /// Magic number needed for building path data + static let PolystarConstant: CGFloat = 0.47829 + + let properties: StarNodeProperties + + let pathOutput: PathOutputNode + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var isEnabled = true { + didSet { + pathOutput.isEnabled = isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + let path = BezierPath.star( + position: properties.position.value.pointValue, + outerRadius: properties.outerRadius.value.cgFloatValue, + innerRadius: properties.innerRadius.value.cgFloatValue, + outerRoundedness: properties.outerRoundedness.value.cgFloatValue, + innerRoundedness: properties.innerRoundedness.value.cgFloatValue, + numberOfPoints: properties.points.value.cgFloatValue, + rotation: properties.rotation.value.cgFloatValue, + direction: properties.direction) + + pathOutput.setPath(path, updateFrame: frame) + } + +} + +extension BezierPath { + /// Constructs a `BezierPath` in the shape of a star + static func star( + position: CGPoint, + outerRadius: CGFloat, + innerRadius: CGFloat, + outerRoundedness inoutOuterRoundedness: CGFloat, + innerRoundedness inputInnerRoundedness: CGFloat, + numberOfPoints: CGFloat, + rotation: CGFloat, + direction: PathDirection) + -> BezierPath + { + var currentAngle = (rotation - 90).toRadians() + let anglePerPoint = (2 * CGFloat.pi) / numberOfPoints + let halfAnglePerPoint = anglePerPoint / 2.0 + let partialPointAmount = numberOfPoints - floor(numberOfPoints) + let outerRoundedness = inoutOuterRoundedness * 0.01 + let innerRoundedness = inputInnerRoundedness * 0.01 + + var point: CGPoint = .zero + + var partialPointRadius: CGFloat = 0 + if partialPointAmount != 0 { + currentAngle += halfAnglePerPoint * (1 - partialPointAmount) + partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius) + point.x = (partialPointRadius * cos(currentAngle)) + point.y = (partialPointRadius * sin(currentAngle)) + currentAngle += anglePerPoint * partialPointAmount / 2 + } else { + point.x = (outerRadius * cos(currentAngle)) + point.y = (outerRadius * sin(currentAngle)) + currentAngle += halfAnglePerPoint + } + + var vertices = [CurveVertex]() + vertices.append(CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)) + + var previousPoint = point + var longSegment = false + let numPoints = Int(ceil(numberOfPoints) * 2) + for i in 0.. + let position: NodeProperty + let scale: NodeProperty + let rotationX: NodeProperty + let rotationY: NodeProperty + let rotationZ: NodeProperty + + let opacity: NodeProperty + let skew: NodeProperty + let skewAxis: NodeProperty + + var caTransform: CATransform3D { + CATransform3D.makeTransform( + anchor: anchor.value.pointValue, + position: position.value.pointValue, + scale: scale.value.sizeValue, + rotationX: rotationX.value.cgFloatValue, + rotationY: rotationY.value.cgFloatValue, + rotationZ: rotationZ.value.cgFloatValue, + skew: skew.value.cgFloatValue, + skewAxis: skewAxis.value.cgFloatValue) + } +} + +// MARK: - GroupNode + +final class GroupNode: AnimatorNode { + + // MARK: Lifecycle + + // MARK: Initializer + init(name: String, parentNode: AnimatorNode?, tree: NodeTree) { + self.parentNode = parentNode + keypathName = name + rootNode = tree.rootNode + properties = GroupNodeProperties(transform: tree.transform) + groupOutput = GroupOutputNode(parent: parentNode?.outputNode, rootNode: rootNode?.outputNode) + var childKeypaths: [KeypathSearchable] = tree.childrenNodes + childKeypaths.append(properties) + self.childKeypaths = childKeypaths + + for childContainer in tree.renderContainers { + container.insertRenderLayer(childContainer) + } + } + + // MARK: Internal + + // MARK: Properties + let groupOutput: GroupOutputNode + + let properties: GroupNodeProperties + + let rootNode: AnimatorNode? + + var container = ShapeContainerLayer() + + // MARK: Keypath Searchable + + let keypathName: String + + let childKeypaths: [KeypathSearchable] + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var keypathLayer: CALayer? { + container + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + properties + } + + var outputNode: NodeOutput { + groupOutput + } + + var isEnabled = true { + didSet { + container.isHidden = !isEnabled + } + } + + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + rootNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + } + + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { + rootNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) + } + + func rebuildOutputs(frame: CGFloat) { + container.opacity = Float(properties.opacity.value.cgFloatValue) * 0.01 + container.transform = properties.caTransform + groupOutput.setTransform(container.transform, forFrame: frame) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..1b079b084a080da863b0b1466e1caa87a6fb148f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift @@ -0,0 +1,90 @@ +// +// FillNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import CoreGraphics +import Foundation + +// MARK: - FillNodeProperties + +final class FillNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(fill: Fill) { + keypathName = fill.name + color = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.color.keyframes)) + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.opacity.keyframes)) + type = fill.fillRule + keypathProperties = [ + PropertyName.opacity.rawValue : opacity, + PropertyName.color.rawValue : color, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let opacity: NodeProperty + let color: NodeProperty + let type: FillRule + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - FillNode + +final class FillNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, fill: Fill) { + fillRender = FillRenderer(parent: parentNode?.outputNode) + fillProperties = FillNodeProperties(fill: fill) + self.parentNode = parentNode + } + + // MARK: Internal + + let fillRender: FillRenderer + + let fillProperties: FillNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + fillRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + fillProperties + } + + var isEnabled = true { + didSet { + fillRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + fillRender.color = fillProperties.color.value.cgColorValue + fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 + fillRender.fillRule = fillProperties.type + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..97e37ef614e208e6b5dac8af19486e50a63a5bb7 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift @@ -0,0 +1,105 @@ +// +// GradientFillNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +// MARK: - GradientFillProperties + +final class GradientFillProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(gradientfill: GradientFill) { + keypathName = gradientfill.name + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.opacity.keyframes)) + startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.startPoint.keyframes)) + endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.endPoint.keyframes)) + colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.colors.keyframes)) + gradientType = gradientfill.gradientType + numberOfColors = gradientfill.numberOfColors + fillRule = gradientfill.fillRule + keypathProperties = [ + PropertyName.opacity.rawValue : opacity, + "Start Point" : startPoint, + "End Point" : endPoint, + PropertyName.gradientColors.rawValue : colors, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let opacity: NodeProperty + let startPoint: NodeProperty + let endPoint: NodeProperty + let colors: NodeProperty<[Double]> + + let gradientType: GradientType + let numberOfColors: Int + let fillRule: FillRule + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - GradientFillNode + +final class GradientFillNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, gradientFill: GradientFill) { + fillRender = GradientFillRenderer(parent: parentNode?.outputNode) + fillProperties = GradientFillProperties(gradientfill: gradientFill) + self.parentNode = parentNode + } + + // MARK: Internal + + let fillRender: GradientFillRenderer + + let fillProperties: GradientFillProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + fillRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + fillProperties + } + + var isEnabled = true { + didSet { + fillRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + fillRender.start = fillProperties.startPoint.value.pointValue + fillRender.end = fillProperties.endPoint.value.pointValue + fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 + fillRender.colors = fillProperties.colors.value.map { CGFloat($0) } + fillRender.type = fillProperties.gradientType + fillRender.numberOfColors = fillProperties.numberOfColors + fillRender.fillRule = fillProperties.fillRule.caFillRule + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..62f0dfe6c752acc78fff788231e9c18a204495b3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift @@ -0,0 +1,151 @@ +// +// GradientStrokeNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import CoreGraphics +import Foundation + +// MARK: - GradientStrokeProperties + +final class GradientStrokeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(gradientStroke: GradientStroke) { + keypathName = gradientStroke.name + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.opacity.keyframes)) + startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.startPoint.keyframes)) + endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.endPoint.keyframes)) + colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.colors.keyframes)) + gradientType = gradientStroke.gradientType + numberOfColors = gradientStroke.numberOfColors + width = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.width.keyframes)) + miterLimit = CGFloat(gradientStroke.miterLimit) + lineCap = gradientStroke.lineCap + lineJoin = gradientStroke.lineJoin + + if let dashes = gradientStroke.dashPattern { + var dashPatterns = ContiguousArray>>() + var dashPhase = ContiguousArray>() + for dash in dashes { + if dash.type == .offset { + dashPhase = dash.value.keyframes + } else { + dashPatterns.append(dash.value.keyframes) + } + } + dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) + self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) + } else { + dashPattern = NodeProperty(provider: SingleValueProvider([LottieVector1D]())) + dashPhase = NodeProperty(provider: SingleValueProvider(LottieVector1D(0))) + } + keypathProperties = [ + PropertyName.opacity.rawValue : opacity, + "Start Point" : startPoint, + "End Point" : endPoint, + PropertyName.gradientColors.rawValue : colors, + PropertyName.strokeWidth.rawValue : width, + "Dashes" : dashPattern, + "Dash Phase" : dashPhase, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + var keypathName: String + + let opacity: NodeProperty + let startPoint: NodeProperty + let endPoint: NodeProperty + let colors: NodeProperty<[Double]> + let width: NodeProperty + + let dashPattern: NodeProperty<[LottieVector1D]> + let dashPhase: NodeProperty + + let lineCap: LineCap + let lineJoin: LineJoin + let miterLimit: CGFloat + let gradientType: GradientType + let numberOfColors: Int + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - GradientStrokeNode + +final class GradientStrokeNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, gradientStroke: GradientStroke) { + strokeRender = GradientStrokeRenderer(parent: parentNode?.outputNode) + strokeProperties = GradientStrokeProperties(gradientStroke: gradientStroke) + self.parentNode = parentNode + } + + // MARK: Internal + + let strokeRender: GradientStrokeRenderer + + let strokeProperties: GradientStrokeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + strokeRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + strokeProperties + } + + var isEnabled = true { + didSet { + strokeRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + /// Update gradient properties + strokeRender.gradientRender.start = strokeProperties.startPoint.value.pointValue + strokeRender.gradientRender.end = strokeProperties.endPoint.value.pointValue + strokeRender.gradientRender.opacity = strokeProperties.opacity.value.cgFloatValue + strokeRender.gradientRender.colors = strokeProperties.colors.value.map { CGFloat($0) } + strokeRender.gradientRender.type = strokeProperties.gradientType + strokeRender.gradientRender.numberOfColors = strokeProperties.numberOfColors + + /// Now update stroke properties + strokeRender.strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue + strokeRender.strokeRender.width = strokeProperties.width.value.cgFloatValue + strokeRender.strokeRender.miterLimit = strokeProperties.miterLimit + strokeRender.strokeRender.lineCap = strokeProperties.lineCap + strokeRender.strokeRender.lineJoin = strokeProperties.lineJoin + + /// Get dash lengths + let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } + if dashLengths.count > 0, dashLengths.isSupportedLayerDashPattern { + strokeRender.strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue + strokeRender.strokeRender.dashLengths = dashLengths + } else { + strokeRender.strokeRender.dashLengths = nil + strokeRender.strokeRender.dashPhase = nil + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..ae2aed4818e94bfdf1d44663167224ec41f17b81 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift @@ -0,0 +1,180 @@ +// +// StrokeNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +// MARK: - StrokeNodeProperties + +final class StrokeNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(stroke: Stroke) { + keypathName = stroke.name + color = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.color.keyframes)) + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.opacity.keyframes)) + width = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.width.keyframes)) + miterLimit = CGFloat(stroke.miterLimit) + lineCap = stroke.lineCap + lineJoin = stroke.lineJoin + + if let dashes = stroke.dashPattern { + let (dashPatterns, dashPhase) = dashes.shapeLayerConfiguration + dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) + if dashPhase.count == 0 { + self.dashPhase = NodeProperty(provider: SingleValueProvider(LottieVector1D(0))) + } else { + self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) + } + } else { + dashPattern = NodeProperty(provider: SingleValueProvider([LottieVector1D]())) + dashPhase = NodeProperty(provider: SingleValueProvider(LottieVector1D(0))) + } + keypathProperties = [ + PropertyName.opacity.rawValue : opacity, + PropertyName.color.rawValue : color, + PropertyName.strokeWidth.rawValue : width, + "Dashes" : dashPattern, + "Dash Phase" : dashPhase, + ] + properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathName: String + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + let opacity: NodeProperty + let color: NodeProperty + let width: NodeProperty + + let dashPattern: NodeProperty<[LottieVector1D]> + let dashPhase: NodeProperty + + let lineCap: LineCap + let lineJoin: LineJoin + let miterLimit: CGFloat + +} + +// MARK: - StrokeNode + +/// Node that manages stroking a path +final class StrokeNode: AnimatorNode, RenderNode { + + // MARK: Lifecycle + + init(parentNode: AnimatorNode?, stroke: Stroke) { + strokeRender = StrokeRenderer(parent: parentNode?.outputNode) + strokeProperties = StrokeNodeProperties(stroke: stroke) + self.parentNode = parentNode + } + + // MARK: Internal + + let strokeRender: StrokeRenderer + + let strokeProperties: StrokeNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + + var renderer: NodeOutput & Renderable { + strokeRender + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + strokeProperties + } + + var isEnabled = true { + didSet { + strokeRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + false + } + + func rebuildOutputs(frame _: CGFloat) { + strokeRender.color = strokeProperties.color.value.cgColorValue + strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue * 0.01 + strokeRender.width = strokeProperties.width.value.cgFloatValue + strokeRender.miterLimit = strokeProperties.miterLimit + strokeRender.lineCap = strokeProperties.lineCap + strokeRender.lineJoin = strokeProperties.lineJoin + + /// Get dash lengths + let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } + if dashLengths.count > 0, dashLengths.isSupportedLayerDashPattern { + strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue + strokeRender.dashLengths = dashLengths + } else { + strokeRender.dashLengths = nil + strokeRender.dashPhase = nil + } + } + +} + +// MARK: - [DashElement] + shapeLayerConfiguration + +extension [DashElement] { + typealias ShapeLayerConfiguration = ( + dashPatterns: ContiguousArray>>, + dashPhase: ContiguousArray>) + + /// Converts the `[DashElement]` data model into `lineDashPattern` and `lineDashPhase` + /// representations usable in a `CAShapeLayer` + var shapeLayerConfiguration: ShapeLayerConfiguration { + var dashPatterns = ContiguousArray>>() + var dashPhase = ContiguousArray>() + for dash in self { + if dash.type == .offset { + dashPhase = dash.value.keyframes + } else { + dashPatterns.append(dash.value.keyframes) + } + } + + dashPatterns = ContiguousArray(dashPatterns.map { pattern in + ContiguousArray(pattern.map { keyframe -> Keyframe in + // The recommended way to create a stroke of round dots, in theory, + // is to use a value of 0 followed by the stroke width, but for + // some reason Core Animation incorrectly (?) renders these as pills + // instead of circles. As a workaround, for parity with Lottie on other + // platforms, we can change `0`s to `0.01`: https://stackoverflow.com/a/38036486 + if keyframe.value.cgFloatValue == 0 { + return keyframe.withValue(LottieVector1D(0.01)) + } else { + return keyframe + } + }) + }) + + return (dashPatterns, dashPhase) + } +} + +extension [CGFloat] { + /// If all of the items in the dash pattern are zeros, then we shouldn't attempt to render it. + /// This causes Core Animation to have extremely poor performance for some reason, even though + /// it doesn't affect the appearance of the animation. + /// - We check for `== 0.01` instead of `== 0` because `dashPattern.shapeLayerConfiguration` + /// converts all `0` values to `0.01` to work around a different Core Animation rendering issue. + var isSupportedLayerDashPattern: Bool { + !allSatisfy { $0 == 0.01 } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..dd9e0685b0429aa8850199bedf44e42b5e28839f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift @@ -0,0 +1,346 @@ +// +// TextAnimatorNode.swift +// lottie-ios-iOS +// +// Created by Brandon Withrow on 2/19/19. +// + +import QuartzCore + +// MARK: - TextAnimatorNodeProperties + +final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable { + + // MARK: Lifecycle + + init(textAnimator: TextAnimator) { + keypathName = textAnimator.name + textRangeUnit = textAnimator.textRangeUnit + var properties = [String : AnyNodeProperty]() + + if let keyframeGroup = textAnimator.anchor { + anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Anchor"] = anchor + } else { + anchor = nil + } + + if let keyframeGroup = textAnimator.position { + position = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties[PropertyName.position.rawValue] = position + } else { + position = nil + } + + if let keyframeGroup = textAnimator.scale { + scale = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties[PropertyName.scale.rawValue] = scale + } else { + scale = nil + } + + if let keyframeGroup = textAnimator.skew { + skew = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Skew"] = skew + } else { + skew = nil + } + + if let keyframeGroup = textAnimator.skewAxis { + skewAxis = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Skew Axis"] = skewAxis + } else { + skewAxis = nil + } + + if let keyframeGroup = textAnimator.rotationX { + rotationX = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Rotation X"] = rotationX + } else { + rotationX = nil + } + + if let keyframeGroup = textAnimator.rotationY { + rotationY = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Rotation Y"] = rotationY + } else { + rotationY = nil + } + + if let keyframeGroup = textAnimator.rotationZ { + rotationZ = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Rotation Z"] = rotationZ + properties[PropertyName.rotation.rawValue] = rotationZ + } else { + rotationZ = nil + } + + if let keyframeGroup = textAnimator.opacity { + opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties[PropertyName.opacity.rawValue] = opacity + } else { + opacity = nil + } + + if let keyframeGroup = textAnimator.strokeColor { + strokeColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Stroke Color"] = strokeColor + } else { + strokeColor = nil + } + + if let keyframeGroup = textAnimator.fillColor { + fillColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Fill Color"] = fillColor + } else { + fillColor = nil + } + + if let keyframeGroup = textAnimator.strokeWidth { + strokeWidth = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties[PropertyName.strokeWidth.rawValue] = strokeWidth + } else { + strokeWidth = nil + } + + if let keyframeGroup = textAnimator.tracking { + tracking = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Tracking"] = tracking + } else { + tracking = nil + } + + if let startKeyframes = textAnimator.start { + start = NodeProperty(provider: KeyframeInterpolator(keyframes: startKeyframes.keyframes)) + properties["Start"] = start + } else { + start = nil + } + + if let endKeyframes = textAnimator.end { + end = NodeProperty(provider: KeyframeInterpolator(keyframes: endKeyframes.keyframes)) + properties["End"] = end + } else { + end = nil + } + + if let selectedRangeOpacityKeyframes = textAnimator.opacity { + selectedRangeOpacity = NodeProperty(provider: KeyframeInterpolator(keyframes: selectedRangeOpacityKeyframes.keyframes)) + properties["SelectedRangeOpacity"] = selectedRangeOpacity + } else { + selectedRangeOpacity = nil + } + + keypathProperties = properties + + self.properties = Array(keypathProperties.values) + } + + // MARK: Internal + + let keypathName: String + + let anchor: NodeProperty? + let position: NodeProperty? + let scale: NodeProperty? + let skew: NodeProperty? + let skewAxis: NodeProperty? + let rotationX: NodeProperty? + let rotationY: NodeProperty? + let rotationZ: NodeProperty? + let opacity: NodeProperty? + let strokeColor: NodeProperty? + let fillColor: NodeProperty? + let strokeWidth: NodeProperty? + let tracking: NodeProperty? + let start: NodeProperty? + let end: NodeProperty? + let selectedRangeOpacity: NodeProperty? + let textRangeUnit: TextRangeUnit? + + let keypathProperties: [String: AnyNodeProperty] + let properties: [AnyNodeProperty] + + var caTransform: CATransform3D { + CATransform3D.makeTransform( + anchor: anchor?.value.pointValue ?? .zero, + position: position?.value.pointValue ?? .zero, + scale: scale?.value.sizeValue ?? CGSize(width: 100, height: 100), + rotationX: rotationX?.value.cgFloatValue ?? 0, + rotationY: rotationY?.value.cgFloatValue ?? 0, + rotationZ: rotationZ?.value.cgFloatValue ?? 0, + skew: skew?.value.cgFloatValue, + skewAxis: skewAxis?.value.cgFloatValue) + } +} + +// MARK: - TextOutputNode + +final class TextOutputNode: NodeOutput { + + // MARK: Lifecycle + + init(parent: TextOutputNode?) { + parentTextNode = parent + } + + // MARK: Internal + + var parentTextNode: TextOutputNode? + var isEnabled = true + + var outputPath: CGPath? + + var parent: NodeOutput? { + parentTextNode + } + + var xform: CATransform3D { + get { + _xform ?? parentTextNode?.xform ?? CATransform3DIdentity + } + set { + _xform = newValue + } + } + + var opacity: CGFloat { + get { + _opacity ?? parentTextNode?.opacity ?? 1 + } + set { + _opacity = newValue + } + } + + var strokeColor: CGColor? { + get { + _strokeColor ?? parentTextNode?.strokeColor + } + set { + _strokeColor = newValue + } + } + + var fillColor: CGColor? { + get { + _fillColor ?? parentTextNode?.fillColor + } + set { + _fillColor = newValue + } + } + + var tracking: CGFloat { + get { + _tracking ?? parentTextNode?.tracking ?? 0 + } + set { + _tracking = newValue + } + } + + var strokeWidth: CGFloat { + get { + _strokeWidth ?? parentTextNode?.strokeWidth ?? 0 + } + set { + _strokeWidth = newValue + } + } + + var start: CGFloat? { + get { + _start + } + set { + _start = newValue + } + } + + var end: CGFloat? { + get { + _end + } + set { + _end = newValue + } + } + + var selectedRangeOpacity: CGFloat? { + get { + _selectedRangeOpacity + } + set { + _selectedRangeOpacity = newValue + } + } + + func hasOutputUpdates(_: CGFloat) -> Bool { + // TODO Fix This + true + } + + // MARK: Fileprivate + + fileprivate var _xform: CATransform3D? + fileprivate var _opacity: CGFloat? + fileprivate var _strokeColor: CGColor? + fileprivate var _fillColor: CGColor? + fileprivate var _tracking: CGFloat? + fileprivate var _strokeWidth: CGFloat? + fileprivate var _start: CGFloat? + fileprivate var _end: CGFloat? + fileprivate var _selectedRangeOpacity: CGFloat? +} + +// MARK: - TextAnimatorNode + +class TextAnimatorNode: AnimatorNode { + + // MARK: Lifecycle + + init(parentNode: TextAnimatorNode?, textAnimator: TextAnimator) { + textOutputNode = TextOutputNode(parent: parentNode?.textOutputNode) + textAnimatorProperties = TextAnimatorNodeProperties(textAnimator: textAnimator) + self.parentNode = parentNode + } + + // MARK: Internal + + let textOutputNode: TextOutputNode + + let textAnimatorProperties: TextAnimatorNodeProperties + + let parentNode: AnimatorNode? + var hasLocalUpdates = false + var hasUpstreamUpdates = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled = true + + var outputNode: NodeOutput { + textOutputNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + textAnimatorProperties + } + + func localUpdatesPermeateDownstream() -> Bool { + true + } + + func rebuildOutputs(frame _: CGFloat) { + textOutputNode.xform = textAnimatorProperties.caTransform + textOutputNode.opacity = 1.0 + textOutputNode.strokeColor = textAnimatorProperties.strokeColor?.value.cgColorValue + textOutputNode.fillColor = textAnimatorProperties.fillColor?.value.cgColorValue + textOutputNode.tracking = textAnimatorProperties.tracking?.value.cgFloatValue ?? 1 + textOutputNode.strokeWidth = textAnimatorProperties.strokeWidth?.value.cgFloatValue ?? 0 + textOutputNode.start = textAnimatorProperties.start?.value.cgFloatValue + textOutputNode.end = textAnimatorProperties.end?.value.cgFloatValue + textOutputNode.selectedRangeOpacity = (textAnimatorProperties.opacity?.value.cgFloatValue).flatMap { $0 * 0.01 } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..82bc247b39e12ed40f7b2f1f6db1b8d9354613c3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.swift @@ -0,0 +1,197 @@ +// +// AnimatorNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/15/19. +// + +import QuartzCore + +// MARK: - NodeOutput + +/// Defines the basic outputs of an animator node. +/// +protocol NodeOutput { + + /// The parent node. + var parent: NodeOutput? { get } + + /// Returns true if there are any updates upstream. OutputPath must be built before returning. + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool + + var outputPath: CGPath? { get } + + var isEnabled: Bool { get set } +} + +// MARK: - AnimatorNode + +/// The Animator Node is the base node in the render system tree. +/// +/// It defines a single node that has an output path and option input node. +/// At animation time the root animation node is asked to update its contents for +/// the current frame. +/// The node reaches up its chain of nodes until the first node that does not need +/// updating is found. Then each node updates its contents down the render pipeline. +/// Each node adds its local path to its input path and passes it forward. +/// +/// An animator node holds a group of interpolators. These interpolators determine +/// if the node needs an update for the current frame. +/// +protocol AnimatorNode: AnyObject, KeypathSearchable { + + /// The available properties of the Node. + /// + /// These properties are automatically updated each frame. + /// These properties are also settable and gettable through the dynamic + /// property system. + /// + var propertyMap: NodePropertyMap & KeypathSearchable { get } + + /// The upstream input node + var parentNode: AnimatorNode? { get } + + /// The output of the node. + var outputNode: NodeOutput { get } + + /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. + func rebuildOutputs(frame: CGFloat) + + /// Setters for marking current node state. + var isEnabled: Bool { get set } + var hasLocalUpdates: Bool { get set } + var hasUpstreamUpdates: Bool { get set } + var lastUpdateFrame: CGFloat? { get set } + + // MARK: Optional + + /// Marks if updates to this node affect nodes downstream. + func localUpdatesPermeateDownstream() -> Bool + func forceUpstreamOutputUpdates() -> Bool + + /// Called at the end of this nodes update cycle. Always called. Optional. + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) + + /// The default simply returns `hasLocalUpdates` + func shouldRebuildOutputs(frame: CGFloat) -> Bool +} + +/// Basic Node Logic +extension AnimatorNode { + + func shouldRebuildOutputs(frame _: CGFloat) -> Bool { + hasLocalUpdates + } + + func localUpdatesPermeateDownstream() -> Bool { + /// Optional override + true + } + + func forceUpstreamOutputUpdates() -> Bool { + /// Optional + false + } + + func performAdditionalLocalUpdates(frame _: CGFloat, forceLocalUpdate: Bool) -> Bool { + /// Optional + forceLocalUpdate + } + + func performAdditionalOutputUpdates(_: CGFloat, forceOutputUpdate _: Bool) { + /// Optional + } + + @discardableResult + func updateOutputs(_ frame: CGFloat, forceOutputUpdate: Bool) -> Bool { + guard isEnabled else { + // Disabled node, pass through. + lastUpdateFrame = frame + return parentNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) ?? false + } + + if forceOutputUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { + /// This node has already updated for this frame. Go ahead and return the results. + return hasUpstreamUpdates || hasLocalUpdates + } + + /// Ask if this node should force output updates upstream. + let forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates() + + /// Perform upstream output updates. Optionally mark upstream updates if any. + hasUpstreamUpdates = ( + parentNode? + .updateOutputs(frame, forceOutputUpdate: forceUpstreamUpdates) ?? false || hasUpstreamUpdates) + + /// Perform additional local output updates + performAdditionalOutputUpdates(frame, forceOutputUpdate: forceUpstreamUpdates) + + /// If there are local updates, or if updates have been force, rebuild outputs + if forceUpstreamUpdates || shouldRebuildOutputs(frame: frame) { + lastUpdateFrame = frame + rebuildOutputs(frame: frame) + } + return hasUpstreamUpdates || hasLocalUpdates + } + + /// Rebuilds the content of this node, and upstream nodes if necessary. + @discardableResult + func updateContents(_ frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + guard isEnabled else { + // Disabled node, pass through. + return parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + } + + if forceLocalUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { + /// This node has already updated for this frame. Go ahead and return the results. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates + } + + /// Are there local updates? If so mark the node. + hasLocalUpdates = forceLocalUpdate ? forceLocalUpdate : propertyMap.needsLocalUpdate(frame: frame) + + /// Were there upstream updates? If so mark the node + hasUpstreamUpdates = parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + + /// Perform property updates if necessary. + if hasLocalUpdates { + /// Rebuild local properties + propertyMap.updateNodeProperties(frame: frame) + } + + /// Ask the node to perform any other updates it might have. + hasUpstreamUpdates = performAdditionalLocalUpdates(frame: frame, forceLocalUpdate: forceLocalUpdate) || hasUpstreamUpdates + + /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates + } + + func updateTree(_ frame: CGFloat, forceUpdates: Bool = false) { + updateContents(frame, forceLocalUpdate: forceUpdates) + updateOutputs(frame, forceOutputUpdate: forceUpdates) + } + +} + +extension AnimatorNode { + /// Default implementation for Keypath searchable. + /// Forward all calls to the propertyMap. + + var keypathName: String { + propertyMap.keypathName + } + + var keypathProperties: [String: AnyNodeProperty] { + propertyMap.keypathProperties + } + + var childKeypaths: [KeypathSearchable] { + propertyMap.childKeypaths + } + + var keypathLayer: CALayer? { + nil + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/PathNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/PathNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..defae81534c6537ffe69d5dca61bef9f61207f34 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/PathNode.swift @@ -0,0 +1,20 @@ +// +// PathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +// MARK: - PathNode + +protocol PathNode { + var pathOutput: PathOutputNode { get } +} + +extension PathNode where Self: AnimatorNode { + + var outputNode: NodeOutput { + pathOutput + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.swift new file mode 100644 index 0000000000000000000000000000000000000000..959d47e00f6556360139f32899fb4452e19b810a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.swift @@ -0,0 +1,60 @@ +// +// RenderNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import QuartzCore + +// MARK: - RenderNode + +/// A protocol that defines a node that holds render instructions +protocol RenderNode { + var renderer: Renderable & NodeOutput { get } +} + +// MARK: - Renderable + +/// A protocol that defines anything with render instructions +protocol Renderable { + + /// The last frame in which this node was updated. + var hasUpdate: Bool { get } + + func hasRenderUpdates(_ forFrame: CGFloat) -> Bool + + /// Determines if the renderer requires a custom context for drawing. + /// If yes the shape layer will perform a custom drawing pass. + /// If no the shape layer will be a standard CAShapeLayer + var shouldRenderInContext: Bool { get } + + /// Passes in the CAShapeLayer to update + func updateShapeLayer(layer: CAShapeLayer) + + /// Asks the renderer what the renderable bounds is for the given box. + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect + + /// Opportunity for renderers to inject sublayers + func setupSublayers(layer: CAShapeLayer) + + /// Renders the shape in a custom context + func render(_ inContext: CGContext) +} + +extension RenderNode where Self: AnimatorNode { + + var outputNode: NodeOutput { + renderer + } + +} + +extension Renderable { + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + /// Optional + boundingBox + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..fbe177762ca67eaaa5d9e801cd8d8e5b8f967d03 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift @@ -0,0 +1,78 @@ +// +// ShapeContainerLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import QuartzCore + +/// The base layer that holds Shapes and Shape Renderers +class ShapeContainerLayer: CALayer { + + // MARK: Lifecycle + + override init() { + super.init() + actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "transform" : NSNull(), + "opacity" : NSNull(), + "hidden" : NSNull(), + ] + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + guard let layer = layer as? ShapeContainerLayer else { + fatalError("init(layer:) wrong class.") + } + super.init(layer: layer) + } + + // MARK: Internal + + private(set) var renderLayers: [ShapeContainerLayer] = [] + + var renderScale: CGFloat = 1 { + didSet { + updateRenderScale() + } + } + + func insertRenderLayer(_ layer: ShapeContainerLayer) { + renderLayers.append(layer) + insertSublayer(layer, at: 0) + } + + func markRenderUpdates(forFrame: CGFloat) { + if hasRenderUpdate(forFrame: forFrame) { + rebuildContents(forFrame: forFrame) + } + guard isHidden == false else { return } + for renderLayer in renderLayers { + renderLayer.markRenderUpdates(forFrame: forFrame) + } + } + + func hasRenderUpdate(forFrame _: CGFloat) -> Bool { + false + } + + func rebuildContents(forFrame _: CGFloat) { + /// Override + } + + func updateRenderScale() { + contentsScale = renderScale + for renderLayer in renderLayers { + renderLayer.renderScale = renderScale + } + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..bd2eac2c5f94b8cf91842307c8c171b93b6d3e61 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift @@ -0,0 +1,98 @@ +// +// RenderLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import QuartzCore + +/// The layer responsible for rendering shape objects +final class ShapeRenderLayer: ShapeContainerLayer { + + // MARK: Lifecycle + + init(renderer: Renderable & NodeOutput) { + self.renderer = renderer + super.init() + anchorPoint = .zero + actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "path" : NSNull(), + "transform" : NSNull(), + "opacity" : NSNull(), + "hidden" : NSNull(), + ] + shapeLayer.actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "path" : NSNull(), + "fillColor" : NSNull(), + "strokeColor" : NSNull(), + "lineWidth" : NSNull(), + "miterLimit" : NSNull(), + "lineDashPhase" : NSNull(), + "opacity": NSNull(), + "hidden" : NSNull(), + ] + addSublayer(shapeLayer) + + renderer.setupSublayers(layer: shapeLayer) + } + + override init(layer: Any) { + guard let layer = layer as? ShapeRenderLayer else { + fatalError("init(layer:) wrong class.") + } + renderer = layer.renderer + super.init(layer: layer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal + + fileprivate(set) var renderer: Renderable & NodeOutput + + let shapeLayer = CAShapeLayer() + + override func hasRenderUpdate(forFrame: CGFloat) -> Bool { + isHidden = !renderer.isEnabled + guard isHidden == false else { return false } + return renderer.hasRenderUpdates(forFrame) + } + + override func rebuildContents(forFrame _: CGFloat) { + if renderer.shouldRenderInContext { + if let newPath = renderer.outputPath { + bounds = renderer.renderBoundsFor(newPath.boundingBox) + } else { + bounds = .zero + } + position = bounds.origin + setNeedsDisplay() + } else { + shapeLayer.path = renderer.outputPath + renderer.updateShapeLayer(layer: shapeLayer) + } + } + + override func draw(in ctx: CGContext) { + if let path = renderer.outputPath { + if !path.isEmpty { + ctx.addPath(path) + } + } + renderer.render(ctx) + } + + override func updateRenderScale() { + super.updateRenderScale() + shapeLayer.contentsScale = renderScale + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/Asset.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/Asset.swift new file mode 100644 index 0000000000000000000000000000000000000000..514f8226c44a02e3321248a13d03e9dc231a7867 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/Asset.swift @@ -0,0 +1,50 @@ +// +// Asset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +// MARK: - Asset + +public class Asset: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Asset.CodingKeys.self) + if let id = try? container.decode(String.self, forKey: .id) { + self.id = id + } else { + id = String(try container.decode(Int.self, forKey: .id)) + } + } + + required init(dictionary: [String: Any]) throws { + if let id = dictionary[CodingKeys.id.rawValue] as? String { + self.id = id + } else if let id = dictionary[CodingKeys.id.rawValue] as? Int { + self.id = String(id) + } else { + throw InitializableError.invalidInput() + } + } + + // MARK: Public + + /// The ID of the asset + public let id: String + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case id + } +} + +// MARK: Sendable + +/// Since `Asset` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.` +/// All `Asset` subclasses are immutable `Sendable` values. +// swiftlint:disable:next no_unchecked_sendable +extension Asset: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/AssetLibrary.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/AssetLibrary.swift new file mode 100644 index 0000000000000000000000000000000000000000..7d00b0b7e181d3f819d1e7adcf83c3a62d8f347d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/AssetLibrary.swift @@ -0,0 +1,75 @@ +// +// AssetLibrary.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +final class AssetLibrary: Codable, AnyInitializable, Sendable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + var containerForKeys = container + + var decodedAssets = [String : Asset]() + + var imageAssets = [String : ImageAsset]() + var precompAssets = [String : PrecompAsset]() + + while + !container.isAtEnd, + let keyContainer = try? containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self) + { + if + keyContainer.contains(.layers), + let precompAsset = try? container.decode(PrecompAsset.self) + { + decodedAssets[precompAsset.id] = precompAsset + precompAssets[precompAsset.id] = precompAsset + } else if let imageAsset = try? container.decode(ImageAsset.self) { + decodedAssets[imageAsset.id] = imageAsset + imageAssets[imageAsset.id] = imageAsset + } + } + assets = decodedAssets + self.precompAssets = precompAssets + self.imageAssets = imageAssets + } + + init(value: Any) throws { + guard let dictionaries = value as? [[String: Any]] else { + throw InitializableError.invalidInput() + } + var decodedAssets = [String : Asset]() + var imageAssets = [String : ImageAsset]() + var precompAssets = [String : PrecompAsset]() + for dictionary in dictionaries { + if dictionary[PrecompAsset.CodingKeys.layers.rawValue] != nil { + let asset = try PrecompAsset(dictionary: dictionary) + decodedAssets[asset.id] = asset + precompAssets[asset.id] = asset + } else if let asset = try? ImageAsset(dictionary: dictionary) { + decodedAssets[asset.id] = asset + imageAssets[asset.id] = asset + } + } + assets = decodedAssets + self.precompAssets = precompAssets + self.imageAssets = imageAssets + } + + // MARK: Internal + + /// The Assets + let assets: [String: Asset] + + let imageAssets: [String: ImageAsset] + let precompAssets: [String: PrecompAsset] + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(contentsOf: Array(assets.values)) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/ImageAsset.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/ImageAsset.swift new file mode 100644 index 0000000000000000000000000000000000000000..4dc77d2f9db5d34a3d71c36306bbb8a1eb4e7d45 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/ImageAsset.swift @@ -0,0 +1,133 @@ +// +// ImageAsset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import CoreGraphics +import Foundation + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +// MARK: - ImageAsset + +public final class ImageAsset: Asset { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ImageAsset.CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + directory = try container.decode(String.self, forKey: .directory) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + name = try dictionary.value(for: CodingKeys.name) + directory = try dictionary.value(for: CodingKeys.directory) + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + try super.init(dictionary: dictionary) + } + + // MARK: Public + + /// Image name + public let name: String + + /// Image Directory + public let directory: String + + /// Image Size + public let width: Double + + public let height: Double + + override public func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(directory, forKey: .directory) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case name = "p" + case directory = "u" + case width = "w" + case height = "h" + } +} + +extension Data { + + // MARK: Lifecycle + + /// Initializes `Data` from an `ImageAsset`. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter imageAsset: The image asset that contains Data URL. + init?(imageAsset: ImageAsset) { + self.init(dataString: imageAsset.name) + } + + /// Initializes `Data` from a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) String. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter dataString: The data string to parse. + /// - parameter options: Options for the string parsing. Default value is `[]`. + init?(dataString: String, options: DataURLReadOptions = []) { + let trimmedDataString = dataString.trimmingCharacters(in: .whitespacesAndNewlines) + guard + dataString.hasPrefix("data:"), + let url = URL(string: trimmedDataString) + else { + return nil + } + // The code below is needed because Data(contentsOf:) floods logs + // with messages since url doesn't have a host. This only fixes flooding logs + // when data inside Data URL is base64 encoded. + if + let base64Range = trimmedDataString.range(of: ";base64,"), + !options.contains(DataURLReadOptions.legacy) + { + let encodedString = String(trimmedDataString[base64Range.upperBound...]) + self.init(base64Encoded: encodedString) + } else { + try? self.init(contentsOf: url) + } + } + + // MARK: Internal + + struct DataURLReadOptions: OptionSet { + let rawValue: Int + + /// Will read Data URL using Data(contentsOf:) + static let legacy = DataURLReadOptions(rawValue: 1 << 0) + } + +} + +extension ImageAsset { + /// A `CGImage` loaded from this asset if represented using a Base 64 encoding + var base64Image: CGImage? { + guard let data = Data(imageAsset: self) else { return nil } + + #if canImport(UIKit) + return UIImage(data: data)?.cgImage + #elseif canImport(AppKit) + return NSImage(data: data)?.lottie_CGImage + #endif + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/PrecompAsset.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/PrecompAsset.swift new file mode 100644 index 0000000000000000000000000000000000000000..dfd955e3761fbb4d1dc81afb9517caf1547f2c19 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Assets/PrecompAsset.swift @@ -0,0 +1,38 @@ +// +// PrecompAsset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +final class PrecompAsset: Asset { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: PrecompAsset.CodingKeys.self) + layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let layerDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.layers) + layers = try [LayerModel].fromDictionaries(layerDictionaries) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case layers + } + + /// Layers of the precomp + let layers: [LayerModel] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(layers, forKey: .layers) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DictionaryInitializable.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DictionaryInitializable.swift new file mode 100644 index 0000000000000000000000000000000000000000..992f8f87457a2e3ee4e82d83823a0759c9014f83 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DictionaryInitializable.swift @@ -0,0 +1,75 @@ +// +// DictionaryInitializable.swift +// Lottie +// +// Created by Marcelo Fabri on 5/5/22. +// + +// MARK: - InitializableError + +enum InitializableError: Error { + case invalidInput(file: StaticString = #file, line: UInt = #line) +} + +// MARK: - DictionaryInitializable + +protocol DictionaryInitializable { + + init(dictionary: [String: Any]) throws + +} + +// MARK: - AnyInitializable + +protocol AnyInitializable { + + init(value: Any) throws + +} + +extension Dictionary { + + @_disfavoredOverload + func value( + for key: KeyType, + file: StaticString = #file, + line: UInt = #line) + throws -> T where KeyType.RawValue == Key + { + guard let value = self[key.rawValue] as? T else { + throw InitializableError.invalidInput(file: file, line: line) + } + return value + } + + func value( + for key: KeyType, + file: StaticString = #file, + line: UInt = #line) + throws -> T where KeyType.RawValue == Key + { + if let value = self[key.rawValue] as? T { + return value + } + + if let value = self[key.rawValue] { + return try T(value: value) + } + + throw InitializableError.invalidInput(file: file, line: line) + } + +} + +// MARK: - AnyInitializable + AnyInitializable + +extension [Double]: AnyInitializable { + + init(value: Any) throws { + guard let array = value as? [Double] else { + throw InitializableError.invalidInput() + } + self = array + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..f7c94a667d63e13e75188efa4ea7f0fab17670be --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieAnimation.swift @@ -0,0 +1,57 @@ +// +// DotLottieAnimation.swift +// Pods +// +// Created by Evandro Harrison Hoffmann on 28/06/2021. +// + +import Foundation + +// MARK: - DotLottieAnimation + +struct DotLottieAnimation: Codable { + /// Id of Animation + var id: String + + /// Loop enabled + var loop: Bool? = false + + /// Animation Playback Speed + var speed: Double? = 1 + + /// 1 or -1 + var direction: Int? = 1 + + /// mode - "bounce" | "normal" + var mode: DotLottieAnimationMode? = .normal + + /// Loop mode for animation + var loopMode: LottieLoopMode { + switch mode { + case .bounce: + .autoReverse + case .normal, nil: + (loop ?? false) ? .loop : .playOnce + } + } + + /// Animation speed + var animationSpeed: Double { + (speed ?? 1) * Double(direction ?? 1) + } + + /// Loads `LottieAnimation` from `animationUrl` + /// - Returns: Deserialized `LottieAnimation`. Optional. + func animation(url: URL) throws -> LottieAnimation { + let animationUrl = url.appendingPathComponent("\(id).json") + let data = try Data(contentsOf: animationUrl) + return try LottieAnimation.from(data: data) + } +} + +// MARK: - DotLottieAnimationMode + +enum DotLottieAnimationMode: String, Codable { + case normal + case bounce +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieImageProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieImageProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..e426fc0a2468bc0150baae5aa1ebd750cd1317ec --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieImageProvider.swift @@ -0,0 +1,89 @@ +// +// DotLottieImageProvider.swift +// Lottie +// +// Created by Evandro Hoffmann on 20/10/22. +// + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +// MARK: - DotLottieImageProvider + +/// An image provider that loads the images from a DotLottieFile into memory +class DotLottieImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a specific filepath. + /// + /// - Parameter filepath: The absolute filepath containing the images. + /// + convenience init?(filepath: String) { + self.init(filepath: URL(fileURLWithPath: filepath)) + } + + init?(filepath: URL) { + guard filepath.urls.count > 0 else { return nil } + self.filepath = filepath + loadImages() + } + + // MARK: Internal + + let filepath: URL + + func imageForAsset(asset: ImageAsset) -> CGImage? { + if let base64Image = asset.base64Image { + return base64Image + } + + return images[asset.name] + } + + // MARK: Private + + /// This is intentionally a Dictionary instead of an NSCache. Files from a decompressed dotLottie zip archive + /// are only valid are deleted after being read into memory. If we used an NSCache then the OS could evict + /// the cache entries when under memory pressure, and we would have no way to reload them later. + /// - Ideally we would have a way to remove image data when under memory pressure, but this would require + /// re-decompressing the dotLottie file when requesting an image that has been loaded but then removed. + private var images = [String: CGImage]() + + private func loadImages() { + for url in filepath.urls { + #if canImport(UIKit) + if + let data = try? Data(contentsOf: url), + let image = UIImage(data: data)?.cgImage + { + images[url.lastPathComponent] = image + } + #elseif canImport(AppKit) + if + let data = try? Data(contentsOf: url), + let image = NSImage(data: data)?.lottie_CGImage + { + images[url.lastPathComponent] = image + } + #endif + } + } + +} + +// MARK: Hashable + +extension DotLottieImageProvider: Hashable { + static func ==(_ lhs: DotLottieImageProvider, _ rhs: DotLottieImageProvider) -> Bool { + lhs.filepath == rhs.filepath + } + + func hash(into hasher: inout Hasher) { + hasher.combine(filepath) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieManifest.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieManifest.swift new file mode 100644 index 0000000000000000000000000000000000000000..d475092852f4e0edea3033ca596aadd40d2b9e7c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieManifest.swift @@ -0,0 +1,42 @@ +// +// DotLottieManifest.swift +// Lottie +// +// Created by Evandro Harrison Hoffmann on 27/06/2020. +// + +import Foundation + +/// Manifest model for .lottie File +struct DotLottieManifest: Codable { + + var animations: [DotLottieAnimation] + var version: String? + var author: String? + var generator: String? + + /// Decodes data to Manifest model + /// - Parameter data: Data to decode + /// - Throws: Error + /// - Returns: .lottie Manifest model + static func decode(from data: Data) throws -> DotLottieManifest { + try JSONDecoder().decode(DotLottieManifest.self, from: data) + } + + /// Loads manifest from given URL + /// - Parameter path: URL path to Manifest + /// - Returns: Manifest Model + static func load(from url: URL) throws -> DotLottieManifest { + let data = try Data(contentsOf: url) + return try decode(from: data) + } + + /// Encodes to data + /// - Parameter encoder: JSONEncoder + /// - Throws: Error + /// - Returns: encoded Data + func encode(with encoder: JSONEncoder = JSONEncoder()) throws -> Data { + try encoder.encode(self) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieUtils.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieUtils.swift new file mode 100644 index 0000000000000000000000000000000000000000..401eb7c18f24867709ad94352a4287757c745221 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/DotLottie/DotLottieUtils.swift @@ -0,0 +1,66 @@ +// +// DotLottieUtils.swift +// Lottie +// +// Created by Evandro Harrison Hoffmann on 27/06/2020. +// + +import Foundation + +// MARK: - DotLottieUtils + +enum DotLottieUtils { + static let dotLottieExtension = "lottie" + static let jsonExtension = "json" + + /// Temp folder to app directory + static var tempDirectoryURL: URL { + FileManager.default.temporaryDirectory + } +} + +extension URL { + /// Checks if url is a lottie file + var isDotLottie: Bool { + pathExtension == DotLottieUtils.dotLottieExtension + } + + /// Checks if url is a json file + var isJsonFile: Bool { + pathExtension == DotLottieUtils.jsonExtension + } + + var urls: [URL] { + FileManager.default.urls(for: self) ?? [] + } +} + +extension FileManager { + /// Lists urls for all files in a directory + /// - Parameters: + /// - url: URL of directory to search + /// - skipsHiddenFiles: If should or not show hidden files + /// - Returns: Returns urls of all files matching criteria in the directory + func urls(for url: URL, skipsHiddenFiles: Bool = true) -> [URL]? { + try? contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: skipsHiddenFiles ? .skipsHiddenFiles : []) + } +} + +// MARK: - DotLottieError + +public enum DotLottieError: Error { + /// URL response has no data. + case noDataLoaded + /// Asset with this name was not found in the provided bundle. + case assetNotFound(name: String, bundle: Bundle?) + /// Animation loading from asset was not supported on macOS 10.10, + /// but this error is no longer used. + case loadingFromAssetNotSupported + + @available(*, deprecated, message: "Unused") + case invalidFileFormat + @available(*, deprecated, message: "Unused") + case invalidData + @available(*, deprecated, message: "Unused") + case animationNotAvailable +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Extensions/Bundle.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Extensions/Bundle.swift new file mode 100644 index 0000000000000000000000000000000000000000..65f7c5b3a5cec0ec2153582eb4920418350a45eb --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Extensions/Bundle.swift @@ -0,0 +1,51 @@ +import Foundation +#if canImport(UIKit) +import UIKit +#endif + +extension Bundle { + func getAnimationData(_ name: String, subdirectory: String? = nil) throws -> Data { + // Check for files in the bundle at the given path + let name = name.removingJSONSuffix() + if let url = url(forResource: name, withExtension: "json", subdirectory: subdirectory) { + return try Data(contentsOf: url) + } + + // Check for data assets + let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name + return try Data(assetName: assetKey, in: self) + } + + func dotLottieData(_ name: String, subdirectory: String? = nil) throws -> Data { + // Check for files in the bundle at the given path + let name = name.removingDotLottieSuffix() + if let url = url(forResource: name, withExtension: "lottie", subdirectory: subdirectory) { + return try Data(contentsOf: url) + } + + let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name + return try Data(assetName: assetKey, in: self) + } +} + +extension String { + fileprivate func removingJSONSuffix() -> String { + // Allow filenames to be passed with a ".json" extension (but not other extensions) + // to keep the behavior from Lottie 2.x - instead of failing to load the animation + guard hasSuffix(".json") else { + return self + } + + return (self as NSString).deletingPathExtension + } + + fileprivate func removingDotLottieSuffix() -> String { + // Allow filenames to be passed with a ".lottie" extension (but not other extensions) + // to keep the behavior from Lottie 2.x - instead of failing to load the file + guard hasSuffix(".lottie") else { + return self + } + + return (self as NSString).deletingPathExtension + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..01e9d80cb1bcd24684a1ae8e8f5d90f067d3aa35 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift @@ -0,0 +1,71 @@ +// From: https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf + +// MARK: - ClassFamily + +/// To support a new class family, create an enum that conforms to this protocol and contains the different types. +protocol ClassFamily: Decodable { + /// The discriminator key. + static var discriminator: Discriminator { get } + + /// The "unknown" fallback case if the type discriminator could not be parsed successfully. + static var unknown: Self { get } + + /// Returns the class type of the object corresponding to the value. + func getType() -> AnyObject.Type +} + +// MARK: - Discriminator + +/// Discriminator key enum used to retrieve discriminator fields in JSON payloads. +enum Discriminator: String, CodingKey { + case type = "ty" +} + +extension KeyedDecodingContainer { + + /// Decode a heterogeneous list of objects for a given family. + /// - Parameters: + /// - heterogeneousType: The decodable type of the list. + /// - family: The ClassFamily enum for the type family. + /// - key: The CodingKey to look up the list in the current container. + /// - Returns: The resulting list of heterogeneousType elements. + func decode(_: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T] { + var container = try nestedUnkeyedContainer(forKey: key) + var list = [T]() + var tmpContainer = container + while !container.isAtEnd { + let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self) + let family: U = (try? typeContainer.decodeIfPresent(U.self, forKey: U.discriminator)) ?? .unknown + if let type = family.getType() as? T.Type { + list.append(try tmpContainer.decode(type)) + } + } + return list + } + + /// Decode a heterogeneous list of objects for a given family if the given key is present. + /// - Parameters: + /// - heterogeneousType: The decodable type of the list. + /// - family: The ClassFamily enum for the type family. + /// - key: The CodingKey to look up the list in the current container. + /// - Returns: The resulting list of heterogeneousType elements. + func decodeIfPresent(_: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T]? { + var container: UnkeyedDecodingContainer + do { + container = try nestedUnkeyedContainer(forKey: key) + } catch { + return nil + } + + var list = [T]() + var tmpContainer = container + while !container.isAtEnd { + let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self) + let family: U = (try? typeContainer.decodeIfPresent(U.self, forKey: U.discriminator)) ?? .unknown + if let type = family.getType() as? T.Type { + list.append(try tmpContainer.decode(type)) + } + } + return list + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Keyframes/KeyframeData.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Keyframes/KeyframeData.swift new file mode 100644 index 0000000000000000000000000000000000000000..753d7f028309dede72389e0f7e2c03b6d1ec3563 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Keyframes/KeyframeData.swift @@ -0,0 +1,110 @@ +// +// Keyframe.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +// MARK: - KeyframeData + +/// A generic class used to parse and remap keyframe json. +/// +/// Keyframe json has a couple of different variations and formats depending on the +/// type of keyframea and also the version of the JSON. By parsing the raw data +/// we can reconfigure it into a constant format. +final class KeyframeData { + + // MARK: Lifecycle + + init( + startValue: T?, + endValue: T?, + time: AnimationFrameTime?, + hold: Int?, + inTangent: LottieVector2D?, + outTangent: LottieVector2D?, + spatialInTangent: LottieVector3D?, + spatialOutTangent: LottieVector3D?) + { + self.startValue = startValue + self.endValue = endValue + self.time = time + self.hold = hold + self.inTangent = inTangent + self.outTangent = outTangent + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case startValue = "s" + case endValue = "e" + case time = "t" + case hold = "h" + case inTangent = "i" + case outTangent = "o" + case spatialInTangent = "ti" + case spatialOutTangent = "to" + } + + /// The start value of the keyframe + let startValue: T? + /// The End value of the keyframe. Note: Newer versions animation json do not have this field. + let endValue: T? + /// The time in frames of the keyframe. + let time: AnimationFrameTime? + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + let hold: Int? + + /// The in tangent for the time interpolation curve. + let inTangent: LottieVector2D? + /// The out tangent for the time interpolation curve. + let outTangent: LottieVector2D? + + /// The spacial in tangent of the vector. + let spatialInTangent: LottieVector3D? + /// The spacial out tangent of the vector. + let spatialOutTangent: LottieVector3D? + + var isHold: Bool { + if let hold { + return hold > 0 + } + return false + } +} + +// MARK: Encodable + +extension KeyframeData: Encodable where T: Encodable { } + +// MARK: Decodable + +extension KeyframeData: Decodable where T: Decodable { } + +// MARK: DictionaryInitializable + +extension KeyframeData: DictionaryInitializable where T: AnyInitializable { + convenience init(dictionary: [String: Any]) throws { + let startValue = try? dictionary[CodingKeys.startValue.rawValue].flatMap(T.init) + let endValue = try? dictionary[CodingKeys.endValue.rawValue].flatMap(T.init) + let time: AnimationFrameTime? = try? dictionary.value(for: CodingKeys.time) + let hold: Int? = try? dictionary.value(for: CodingKeys.hold) + let inTangent: LottieVector2D? = try? dictionary.value(for: CodingKeys.inTangent) + let outTangent: LottieVector2D? = try? dictionary.value(for: CodingKeys.outTangent) + let spatialInTangent: LottieVector3D? = try? dictionary.value(for: CodingKeys.spatialInTangent) + let spatialOutTangent: LottieVector3D? = try? dictionary.value(for: CodingKeys.spatialOutTangent) + + self.init( + startValue: startValue, + endValue: endValue, + time: time, + hold: hold, + inTangent: inTangent, + outTangent: outTangent, + spatialInTangent: spatialInTangent, + spatialOutTangent: spatialOutTangent) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Keyframes/KeyframeGroup.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Keyframes/KeyframeGroup.swift new file mode 100644 index 0000000000000000000000000000000000000000..5da4c4c1b09476060a11fd57d78e27ae2433164e --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Keyframes/KeyframeGroup.swift @@ -0,0 +1,256 @@ +// +// KeyframeGroup.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +// MARK: - KeyframeGroup + +/// Used for coding/decoding a group of Keyframes by type. +/// +/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. +/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. +/// This helper object is needed to properly decode the json. +final class KeyframeGroup { + + // MARK: Lifecycle + + init( + keyframes: ContiguousArray>, + unsupportedAfterEffectsExpression: String? = nil) + { + self.keyframes = keyframes + self.unsupportedAfterEffectsExpression = unsupportedAfterEffectsExpression + } + + init( + _ value: T, + unsupportedAfterEffectsExpression: String? = nil) + { + keyframes = [Keyframe(value)] + self.unsupportedAfterEffectsExpression = unsupportedAfterEffectsExpression + } + + // MARK: Internal + + enum KeyframeWrapperKey: String, CodingKey { + case keyframeData = "k" + case unsupportedAfterEffectsExpression = "x" + } + + let keyframes: ContiguousArray> + + /// lottie-ios doesn't support After Effects expressions, but we parse them so we can log diagnostics. + /// More info: https://helpx.adobe.com/after-effects/using/expression-basics.html + let unsupportedAfterEffectsExpression: String? + +} + +// MARK: Decodable + +extension KeyframeGroup: Decodable where T: Decodable { + convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: KeyframeWrapperKey.self) + let unsupportedAfterEffectsExpression = try? container.decode(String.self, forKey: .unsupportedAfterEffectsExpression) + + if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) { + /// Try to decode raw value; No keyframe data. + self.init( + keyframes: [Keyframe(keyframeData)], + unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression) + } else { + // Decode and array of keyframes. + // + // Body Movin and Lottie deal with keyframes in different ways. + // + // A keyframe object in Body movin defines a span of time with a START + // and an END, from the current keyframe time to the next keyframe time. + // + // A keyframe object in Lottie defines a singular point in time/space. + // This point has an in-tangent and an out-tangent. + // + // To properly decode this we must iterate through keyframes while holding + // reference to the previous keyframe. + + var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData) + var keyframes = ContiguousArray>() + var previousKeyframeData: KeyframeData? + while !keyframesContainer.isAtEnd { + // Ensure that Time and Value are present. + + let keyframeData = try keyframesContainer.decode(KeyframeData.self) + + guard + let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue, + let time = keyframeData.time + else { + /// Missing keyframe data. JSON must be corrupt. + throw DecodingError.dataCorruptedError( + forKey: KeyframeWrapperKey.keyframeData, + in: container, + debugDescription: "Missing keyframe data.") + } + + keyframes.append(Keyframe( + value: value, + time: AnimationFrameTime(time), + isHold: keyframeData.isHold, + inTangent: previousKeyframeData?.inTangent, + outTangent: keyframeData.outTangent, + spatialInTangent: previousKeyframeData?.spatialInTangent, + spatialOutTangent: keyframeData.spatialOutTangent)) + previousKeyframeData = keyframeData + } + self.init( + keyframes: keyframes, + unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression) + } + } +} + +// MARK: Encodable + +extension KeyframeGroup: Encodable where T: Encodable { + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: KeyframeWrapperKey.self) + + if keyframes.count == 1 { + let keyframe = keyframes[0] + try container.encode(keyframe.value, forKey: .keyframeData) + } else { + var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData) + + for i in 1..( + startValue: keyframe.value, + endValue: nextKeyframe.value, + time: keyframe.time, + hold: keyframe.isHold ? 1 : nil, + inTangent: nextKeyframe.inTangent, + outTangent: keyframe.outTangent, + spatialInTangent: nil, + spatialOutTangent: nil) + try keyframeContainer.encode(keyframeData) + } + } + } +} + +// MARK: DictionaryInitializable + +extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable { + convenience init(dictionary: [String: Any]) throws { + var keyframes = ContiguousArray>() + let unsupportedAfterEffectsExpression = dictionary[KeyframeWrapperKey.unsupportedAfterEffectsExpression.rawValue] as? String + if + let rawValue = dictionary[KeyframeWrapperKey.keyframeData.rawValue], + let value = try? T(value: rawValue) + { + keyframes = [Keyframe(value)] + } else { + let frameDictionaries: [[String: Any]] = + if + let singleFrameDictionary = + dictionary[KeyframeWrapperKey.keyframeData.rawValue] as? [String: Any] + { + [singleFrameDictionary] + } else { + try dictionary.value(for: KeyframeWrapperKey.keyframeData) + } + var previousKeyframeData: KeyframeData? + for frameDictionary in frameDictionaries { + let data = try KeyframeData(dictionary: frameDictionary) + guard + let value: T = data.startValue ?? previousKeyframeData?.endValue, + let time = data.time + else { + throw InitializableError.invalidInput() + } + keyframes.append(Keyframe( + value: value, + time: time, + isHold: data.isHold, + inTangent: previousKeyframeData?.inTangent, + outTangent: data.outTangent, + spatialInTangent: previousKeyframeData?.spatialInTangent, + spatialOutTangent: data.spatialOutTangent)) + previousKeyframeData = data + } + } + + self.init( + keyframes: keyframes, + unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression) + } +} + +// MARK: Equatable + +extension KeyframeGroup: Equatable where T: Equatable { + static func == (_ lhs: KeyframeGroup, _ rhs: KeyframeGroup) -> Bool { + lhs.keyframes == rhs.keyframes + } +} + +// MARK: Hashable + +extension KeyframeGroup: Hashable where T: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(keyframes) + } +} + +// MARK: Sendable + +extension KeyframeGroup: Sendable where T: Sendable { } + +extension Keyframe { + /// Creates a copy of this `Keyframe` with the same timing data, but a different value + func withValue(_ newValue: Value) -> Keyframe { + Keyframe( + value: newValue, + time: time, + isHold: isHold, + inTangent: inTangent, + outTangent: outTangent, + spatialInTangent: spatialInTangent, + spatialOutTangent: spatialOutTangent) + } +} + +extension KeyframeGroup { + /// Maps the values of each individual keyframe in this group + func map(_ transformation: (T) throws -> NewValue) rethrows -> KeyframeGroup { + KeyframeGroup( + keyframes: ContiguousArray(try keyframes.map { keyframe in + keyframe.withValue(try transformation(keyframe.value)) + }), + unsupportedAfterEffectsExpression: unsupportedAfterEffectsExpression) + } +} + +// MARK: - AnyKeyframeGroup + +/// A type-erased wrapper for `KeyframeGroup`s +protocol AnyKeyframeGroup { + /// An untyped copy of these keyframes + var untyped: KeyframeGroup { get } + + /// An untyped `KeyframeInterpolator` for these keyframes + var interpolator: AnyValueProvider { get } +} + +// MARK: - KeyframeGroup + AnyKeyframeGroup + +extension KeyframeGroup: AnyKeyframeGroup where T: AnyInterpolatable { + var untyped: KeyframeGroup { + map { $0 as Any } + } + + var interpolator: AnyValueProvider { + KeyframeInterpolator(keyframes: keyframes) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/DropShadowEffect.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/DropShadowEffect.swift new file mode 100644 index 0000000000000000000000000000000000000000..655c7f1d492eff747e203d22d34b45a3e58b9af3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/DropShadowEffect.swift @@ -0,0 +1,43 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +final class DropShadowEffect: LayerEffect { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The color of the drop shadow + var color: ColorEffectValue? { + value(named: "Shadow Color") + } + + /// Opacity between 0 and 255 + var opacity: Vector1DEffectValue? { + value(named: "Opacity") + } + + /// The direction / angle of the drop shadow, in degrees + var direction: Vector1DEffectValue? { + value(named: "Direction") + } + + /// The distance of the drop shadow + var distance: Vector1DEffectValue? { + value(named: "Distance") + } + + /// The softness of the drop shadow + var softness: Vector1DEffectValue? { + value(named: "Softness") + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift new file mode 100644 index 0000000000000000000000000000000000000000..125c65d2c7ea3c1729e05a181f8e9c5fdce96121 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift @@ -0,0 +1,36 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +final class ColorEffectValue: EffectValue { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + value = try? container.decode(KeyframeGroup.self, forKey: .value) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let valueDictionary: [String: Any] = try dictionary.value(for: CodingKeys.value) + value = try KeyframeGroup(dictionary: valueDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The value of the color + let value: KeyframeGroup? + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: .value) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case value = "v" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/EffectValue.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/EffectValue.swift new file mode 100644 index 0000000000000000000000000000000000000000..4f154f52c913c885e99eec2c870b2f5cc8762f6a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/EffectValue.swift @@ -0,0 +1,97 @@ +// Created by Cal Stephens on 8/15/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +// MARK: - EffectValueType + +/// https://lottiefiles.github.io/lottie-docs/schema/#/$defs/effect-values +enum EffectValueType: Int, Codable, Sendable { + case slider = 0 + case angle = 1 + case color = 2 + case unknown = 9999 + + init(from decoder: Decoder) throws { + self = try EffectValueType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } +} + +// MARK: ClassFamily + +extension EffectValueType: ClassFamily { + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .slider: + Vector1DEffectValue.self + case .angle: + Vector1DEffectValue.self + case .color: + ColorEffectValue.self + case .unknown: + // Unsupported + LayerEffect.self + } + } +} + +// MARK: - EffectValue + +class EffectValue: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: EffectValue.CodingKeys.self) + type = try container.decode(EffectValueType.self, forKey: .type) + name = try container.decode(String.self, forKey: .name) + } + + required init(dictionary: [String: Any]) throws { + type = (try? dictionary.value(for: CodingKeys.type)).flatMap(EffectValueType.init(rawValue:)) ?? .unknown + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Effect" + } + + // MARK: Internal + + /// The type of effect value + let type: EffectValueType + + /// The name of the effect value + let name: String + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case type = "ty" + case name = "nm" + } +} + +extension [EffectValue] { + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [EffectValue] { + try dictionaries.compactMap { dictionary in + let shapeType = dictionary[EffectValue.CodingKeys.type.rawValue] as? Int + switch EffectValueType(rawValue: shapeType ?? EffectValueType.unknown.rawValue) { + case .slider: + return try Vector1DEffectValue(dictionary: dictionary) + case .angle: + return try Vector1DEffectValue(dictionary: dictionary) + case .color: + return try ColorEffectValue(dictionary: dictionary) + case .unknown: + // Unsupported + return try EffectValue(dictionary: dictionary) + case nil: + return nil + } + } + } +} + +// MARK: - EffectValue + Sendable + +/// Since `EffectValue` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.` +/// All `EffectValue` subclasses are immutable `Sendable` values. +// swiftlint:disable:next no_unchecked_sendable +extension EffectValue: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift new file mode 100644 index 0000000000000000000000000000000000000000..59555a495ed1fe028d1b423992a734bffb620777 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift @@ -0,0 +1,36 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +final class Vector1DEffectValue: EffectValue { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + value = try? container.decode(KeyframeGroup.self, forKey: .value) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let valueDictionary: [String: Any] = try dictionary.value(for: CodingKeys.value) + value = try KeyframeGroup(dictionary: valueDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The value of the slider + let value: KeyframeGroup? + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: .value) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case value = "v" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/LayerEffect.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/LayerEffect.swift new file mode 100644 index 0000000000000000000000000000000000000000..654acfc2fc3b7f4352cdc83d54370eb46e10f0da --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerEffects/LayerEffect.swift @@ -0,0 +1,102 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +// MARK: - LayerEffectType + +/// https://lottiefiles.github.io/lottie-docs/schema/#/$defs/effects +enum LayerEffectType: Int, Codable, Sendable { + case dropShadow = 25 + case unknown = 9999 + + init(from decoder: Decoder) throws { + self = try LayerEffectType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } +} + +// MARK: ClassFamily + +extension LayerEffectType: ClassFamily { + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .dropShadow: + DropShadowEffect.self + case .unknown: + // Unsupported + LayerEffect.self + } + } +} + +// MARK: - LayerEffect + +class LayerEffect: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LayerEffect.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Effect" + type = try container.decode(LayerEffectType.self, forKey: .type) + effects = try container.decodeIfPresent([EffectValue].self, ofFamily: EffectValueType.self, forKey: .effects) ?? [] + } + + required init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer" + type = LayerEffectType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .unknown + if let valueDictionaries = dictionary[CodingKeys.effects.rawValue] as? [[String: Any]] { + effects = try [EffectValue].fromDictionaries(valueDictionaries) + } else { + effects = [] + } + } + + // MARK: Internal + + /// The name of the effect + let name: String + + /// The type of effect + let type: LayerEffectType + + /// Values that configure the behavior of the effect + let effects: [EffectValue] + + /// Retrieves the `EffectValue` for the given name + func value(named name: String) -> ValueType? { + effects.first(where: { + $0.name == name && $0 is ValueType + }) as? ValueType + } + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case name = "nm" + case type = "ty" + case effects = "ef" + } +} + +extension [LayerEffect] { + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [LayerEffect] { + try dictionaries.compactMap { dictionary in + let shapeType = dictionary[LayerEffect.CodingKeys.type.rawValue] as? Int + switch LayerEffectType(rawValue: shapeType ?? LayerEffectType.unknown.rawValue) { + case .dropShadow: + return try DropShadowEffect(dictionary: dictionary) + case .unknown, nil: + // Unsupported + return try LayerEffect(dictionary: dictionary) + } + } + } +} + +// MARK: - LayerEffect + Sendable + +/// Since `LayerEffect` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.` +/// All `LayerEffect` subclasses are immutable `Sendable` values. +// swiftlint:disable:next no_unchecked_sendable +extension LayerEffect: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerStyles/DropShadowStyle.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerStyles/DropShadowStyle.swift new file mode 100644 index 0000000000000000000000000000000000000000..1f2d14a904cb904f2175a4e97e3002907ef24af5 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerStyles/DropShadowStyle.swift @@ -0,0 +1,70 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +final class DropShadowStyle: LayerStyle { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DropShadowStyle.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + color = try container.decode(KeyframeGroup.self, forKey: .color) + angle = try container.decode(KeyframeGroup.self, forKey: .angle) + size = try container.decode(KeyframeGroup.self, forKey: .size) + distance = try container.decode(KeyframeGroup.self, forKey: .distance) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let colorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.color) + color = try KeyframeGroup(dictionary: colorDictionary) + let angleDictionary: [String: Any] = try dictionary.value(for: CodingKeys.angle) + angle = try KeyframeGroup(dictionary: angleDictionary) + let sizeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.size) + size = try KeyframeGroup(dictionary: sizeDictionary) + let distanceDictionary: [String: Any] = try dictionary.value(for: CodingKeys.distance) + distance = try KeyframeGroup(dictionary: distanceDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the drop shadow + let opacity: KeyframeGroup + + /// The color of the drop shadow + let color: KeyframeGroup + + /// The angle of the drop shadow, in degrees, + /// with `0` representing a shadow straight-down from the layer + /// (`offsetY=distance, offsetX=0`). + let angle: KeyframeGroup + + /// The size of the drop shadow + let size: KeyframeGroup + + /// The distance of the drop shadow + let distance: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(angle, forKey: .angle) + try container.encode(size, forKey: .size) + try container.encode(distance, forKey: .distance) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case color = "c" + case opacity = "o" + case angle = "a" + case size = "s" + case distance = "d" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerStyles/LayerStyle.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerStyles/LayerStyle.swift new file mode 100644 index 0000000000000000000000000000000000000000..70d28a5d5a700a7303e7b0697194f4d085d82ec0 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/LayerStyles/LayerStyle.swift @@ -0,0 +1,84 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +// MARK: - LayerStyleType + +enum LayerStyleType: Int, Codable, Sendable { + case dropShadow = 1 + case unknown = 9999 + + init(from decoder: Decoder) throws { + self = try LayerStyleType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } +} + +// MARK: ClassFamily + +extension LayerStyleType: ClassFamily { + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .dropShadow: + DropShadowStyle.self + case .unknown: + // Unsupported + LayerStyle.self + } + } +} + +// MARK: - LayerStyle + +class LayerStyle: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LayerStyle.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Style" + type = try container.decode(LayerStyleType.self, forKey: .type) + } + + required init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer" + type = LayerStyleType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .unknown + } + + // MARK: Internal + + /// The name of the style + let name: String + + /// The type of style + let type: LayerStyleType + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case name = "nm" + case type = "ty" + } +} + +extension [LayerStyle] { + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [LayerStyle] { + try dictionaries.compactMap { dictionary in + let shapeType = dictionary[LayerStyle.CodingKeys.type.rawValue] as? Int + switch LayerStyleType(rawValue: shapeType ?? LayerStyleType.unknown.rawValue) { + case .dropShadow: + return try DropShadowStyle(dictionary: dictionary) + case .unknown, nil: + // Unsupported + return try LayerStyle(dictionary: dictionary) + } + } + } +} + +// MARK: - LayerStyle + Sendable + +/// Since `LayerStyle` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.` +/// All `LayerStyle` subclasses are immutable `Sendable` values. +// swiftlint:disable:next no_unchecked_sendable +extension LayerStyle: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/ImageLayerModel.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/ImageLayerModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..781e5c1f0111ba6400d524a69186885a87c7d10f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/ImageLayerModel.swift @@ -0,0 +1,40 @@ +// +// ImageLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// A layer that holds an image. +final class ImageLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ImageLayerModel.CodingKeys.self) + referenceID = try container.decode(String.self, forKey: .referenceID) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + referenceID = try dictionary.value(for: CodingKeys.referenceID) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The reference ID of the image. + let referenceID: String + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(referenceID, forKey: .referenceID) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case referenceID = "refId" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/LayerModel.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/LayerModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..e2e3e38888f05a1bbf6fe75f69ead28c37c85b2d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/LayerModel.swift @@ -0,0 +1,266 @@ +// +// Layer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +// MARK: - LayerType + ClassFamily + +/// Used for mapping a heterogeneous list to classes for parsing. +extension LayerType: ClassFamily { + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .precomp: + PreCompLayerModel.self + case .solid: + SolidLayerModel.self + case .image: + ImageLayerModel.self + case .null: + LayerModel.self + case .shape: + ShapeLayerModel.self + case .text: + TextLayerModel.self + case .unknown: + LayerModel.self + } + } +} + +// MARK: - LayerType + +public enum LayerType: Int, Codable { + case precomp + case solid + case image + case null + case shape + case text + case unknown + + public init(from decoder: Decoder) throws { + self = try LayerType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .null + } +} + +// MARK: - MatteType + +public enum MatteType: Int, Codable { + case none + case add + case invert + case unknown +} + +// MARK: - BlendMode + +public enum BlendMode: Int, Codable { + case normal + case multiply + case screen + case overlay + case darken + case lighten + case colorDodge + case colorBurn + case hardLight + case softLight + case difference + case exclusion + case hue + case saturation + case color + case luminosity +} + +// MARK: - LayerModel + +/// A base top container for shapes, images, and other view objects. +class LayerModel: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LayerModel.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" + index = try container.decodeIfPresent(Int.self, forKey: .index) ?? .random(in: Int.min...Int.max) + type = try container.decode(LayerType.self, forKey: .type) + coordinateSpace = try container.decodeIfPresent(CoordinateSpace.self, forKey: .coordinateSpace) ?? .type2d + inFrame = try container.decode(Double.self, forKey: .inFrame) + outFrame = try container.decode(Double.self, forKey: .outFrame) + startTime = try container.decode(Double.self, forKey: .startTime) + transform = try container.decodeIfPresent(Transform.self, forKey: .transform) ?? .default + parent = try container.decodeIfPresent(Int.self, forKey: .parent) + blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal + masks = try container.decodeIfPresent([Mask].self, forKey: .masks) + timeStretch = try container.decodeIfPresent(Double.self, forKey: .timeStretch) ?? 1 + matte = try container.decodeIfPresent(MatteType.self, forKey: .matte) + hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false + styles = try container.decodeIfPresent([LayerStyle].self, ofFamily: LayerStyleType.self, forKey: .styles) ?? [] + effects = try container.decodeIfPresent([LayerEffect].self, ofFamily: LayerEffectType.self, forKey: .effects) ?? [] + } + + required init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer" + index = try dictionary.value(for: CodingKeys.index) ?? .random(in: Int.min...Int.max) + type = LayerType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .null + if + let coordinateSpaceRawValue = dictionary[CodingKeys.coordinateSpace.rawValue] as? Int, + let coordinateSpace = CoordinateSpace(rawValue: coordinateSpaceRawValue) + { + self.coordinateSpace = coordinateSpace + } else { + coordinateSpace = .type2d + } + inFrame = try dictionary.value(for: CodingKeys.inFrame) + outFrame = try dictionary.value(for: CodingKeys.outFrame) + startTime = try dictionary.value(for: CodingKeys.startTime) + parent = try? dictionary.value(for: CodingKeys.parent) + if + let transformDictionary: [String: Any] = try dictionary.value(for: CodingKeys.transform), + let transform = try? Transform(dictionary: transformDictionary) + { + self.transform = transform + } else { + transform = .default + } + if + let blendModeRawValue = dictionary[CodingKeys.blendMode.rawValue] as? Int, + let blendMode = BlendMode(rawValue: blendModeRawValue) + { + self.blendMode = blendMode + } else { + blendMode = .normal + } + if let maskDictionaries = dictionary[CodingKeys.masks.rawValue] as? [[String: Any]] { + masks = try maskDictionaries.map { try Mask(dictionary: $0) } + } else { + masks = nil + } + timeStretch = (try? dictionary.value(for: CodingKeys.timeStretch)) ?? 1 + if let matteRawValue = dictionary[CodingKeys.matte.rawValue] as? Int { + matte = MatteType(rawValue: matteRawValue) + } else { + matte = nil + } + hidden = (try? dictionary.value(for: CodingKeys.hidden)) ?? false + if let styleDictionaries = dictionary[CodingKeys.styles.rawValue] as? [[String: Any]] { + styles = try [LayerStyle].fromDictionaries(styleDictionaries) + } else { + styles = [] + } + if let effectDictionaries = dictionary[CodingKeys.effects.rawValue] as? [[String: Any]] { + effects = try [LayerEffect].fromDictionaries(effectDictionaries) + } else { + effects = [] + } + } + + // MARK: Internal + + /// The readable name of the layer + let name: String + + /// The index of the layer + let index: Int + + /// The type of the layer. + let type: LayerType + + /// The coordinate space + let coordinateSpace: CoordinateSpace + + /// The in time of the layer in frames. + let inFrame: Double + /// The out time of the layer in frames. + let outFrame: Double + + /// The start time of the layer in frames. + let startTime: Double + + /// The transform of the layer + let transform: Transform + + /// The index of the parent layer, if applicable. + let parent: Int? + + /// The blending mode for the layer + let blendMode: BlendMode + + /// An array of masks for the layer. + let masks: [Mask]? + + /// A number that stretches time by a multiplier + let timeStretch: Double + + /// The type of matte if any. + let matte: MatteType? + + /// Whether or not this layer is hidden, in which case it will not be rendered. + let hidden: Bool + + /// A list of styles to apply to this layer + let styles: [LayerStyle] + + /// A list of effects to apply to this layer + let effects: [LayerEffect] + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case name = "nm" + case index = "ind" + case type = "ty" + case coordinateSpace = "ddd" + case inFrame = "ip" + case outFrame = "op" + case startTime = "st" + case transform = "ks" + case parent + case blendMode = "bm" + case masks = "masksProperties" + case timeStretch = "sr" + case matte = "tt" + case hidden = "hd" + case styles = "sy" + case effects = "ef" + } +} + +extension [LayerModel] { + + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [LayerModel] { + try dictionaries.compactMap { dictionary in + let layerType = dictionary[LayerModel.CodingKeys.type.rawValue] as? Int + switch LayerType(rawValue: layerType ?? LayerType.null.rawValue) { + case .precomp: + return try PreCompLayerModel(dictionary: dictionary) + case .solid: + return try SolidLayerModel(dictionary: dictionary) + case .image: + return try ImageLayerModel(dictionary: dictionary) + case .null: + return try LayerModel(dictionary: dictionary) + case .shape: + return try ShapeLayerModel(dictionary: dictionary) + case .text: + return try TextLayerModel(dictionary: dictionary) + case .unknown: + return try LayerModel(dictionary: dictionary) + case .none: + return nil + } + } + } +} + +// MARK: - LayerModel + Sendable + +/// Since `LayerModel` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.` +/// All `LayerModel` subclasses are immutable `Sendable` values. +// swiftlint:disable:next no_unchecked_sendable +extension LayerModel: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/PreCompLayerModel.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/PreCompLayerModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..a83214d75d22697f2b5679682218f34846f13194 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/PreCompLayerModel.swift @@ -0,0 +1,65 @@ +// +// PreCompLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// A layer that holds another animation composition. +final class PreCompLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: PreCompLayerModel.CodingKeys.self) + referenceID = try container.decode(String.self, forKey: .referenceID) + timeRemapping = try container.decodeIfPresent(KeyframeGroup.self, forKey: .timeRemapping) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + referenceID = try dictionary.value(for: CodingKeys.referenceID) + if let timeRemappingDictionary = dictionary[CodingKeys.timeRemapping.rawValue] as? [String: Any] { + timeRemapping = try KeyframeGroup(dictionary: timeRemappingDictionary) + } else { + timeRemapping = nil + } + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The reference ID of the precomp. + let referenceID: String + + /// A value that remaps time over time. + let timeRemapping: KeyframeGroup? + + /// Precomp Width + let width: Double + + /// Precomp Height + let height: Double + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(referenceID, forKey: .referenceID) + try container.encode(timeRemapping, forKey: .timeRemapping) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case referenceID = "refId" + case timeRemapping = "tm" + case width = "w" + case height = "h" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/ShapeLayerModel.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/ShapeLayerModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..805da83a9547a906b8c4d9238dc21b6564335388 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/ShapeLayerModel.swift @@ -0,0 +1,41 @@ +// +// ShapeLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// A layer that holds vector shape objects. +final class ShapeLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeLayerModel.CodingKeys.self) + items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let itemDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.items) + items = try [ShapeItem].fromDictionaries(itemDictionaries) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// A list of shape items. + let items: [ShapeItem] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(items, forKey: .items) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case items = "shapes" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/SolidLayerModel.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/SolidLayerModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..17312c90e1a3b99814d5b3045937a1f187687fdd --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/SolidLayerModel.swift @@ -0,0 +1,54 @@ +// +// SolidLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// A layer that holds a solid color. +final class SolidLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: SolidLayerModel.CodingKeys.self) + colorHex = try container.decode(String.self, forKey: .colorHex) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + colorHex = try dictionary.value(for: CodingKeys.colorHex) + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The color of the solid in Hex // Change to value provider. + let colorHex: String + + /// The Width of the color layer + let width: Double + + /// The height of the color layer + let height: Double + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(colorHex, forKey: .colorHex) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case colorHex = "sc" + case width = "sw" + case height = "sh" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/TextLayerModel.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/TextLayerModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..604a331f513686d45cd1bcc31097be4c89224a55 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Layers/TextLayerModel.swift @@ -0,0 +1,56 @@ +// +// TextLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// A layer that holds text. +final class TextLayerModel: LayerModel { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TextLayerModel.CodingKeys.self) + let textContainer = try container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) + text = try textContainer.decode(KeyframeGroup.self, forKey: .text) + animators = try textContainer.decode([TextAnimator].self, forKey: .animators) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let containerDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textGroup) + let textDictionary: [String: Any] = try containerDictionary.value(for: TextCodingKeys.text) + text = try KeyframeGroup(dictionary: textDictionary) + let animatorDictionaries: [[String: Any]] = try containerDictionary.value(for: TextCodingKeys.animators) + animators = try animatorDictionaries.map { try TextAnimator(dictionary: $0) } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The text for the layer + let text: KeyframeGroup + + /// Text animators + let animators: [TextAnimator] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + var textContainer = container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) + try textContainer.encode(text, forKey: .text) + try textContainer.encode(animators, forKey: .animators) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case textGroup = "t" + } + + private enum TextCodingKeys: String, CodingKey { + case text = "d" + case animators = "a" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/DashPattern.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/DashPattern.swift new file mode 100644 index 0000000000000000000000000000000000000000..0c2da6858ca410310d7628d5dd764dd745b7036a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/DashPattern.swift @@ -0,0 +1,42 @@ +// +// DashPattern.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +// MARK: - DashElementType + +enum DashElementType: String, Codable { + case offset = "o" + case dash = "d" + case gap = "g" +} + +// MARK: - DashElement + +final class DashElement: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + let typeRawValue: String = try dictionary.value(for: CodingKeys.type) + guard let type = DashElementType(rawValue: typeRawValue) else { + throw InitializableError.invalidInput() + } + self.type = type + let valueDictionary: [String: Any] = try dictionary.value(for: CodingKeys.value) + value = try KeyframeGroup(dictionary: valueDictionary) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case type = "n" + case value = "v" + } + + let type: DashElementType + let value: KeyframeGroup + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Marker.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Marker.swift new file mode 100644 index 0000000000000000000000000000000000000000..a724452695d98a494076e3ac609ba7581efe71b8 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Marker.swift @@ -0,0 +1,35 @@ +// +// Marker.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +/// A time marker +final class Marker: Codable, Sendable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + name = try dictionary.value(for: CodingKeys.name) + frameTime = try dictionary.value(for: CodingKeys.frameTime) + durationFrameTime = try dictionary.value(for: CodingKeys.durationFrameTime) + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case name = "cm" + case frameTime = "tm" + case durationFrameTime = "dr" + } + + /// The Marker Name + let name: String + + /// The Frame time of the marker + let frameTime: AnimationFrameTime + + /// The duration of the marker, in frames. + let durationFrameTime: AnimationFrameTime +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Mask.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Mask.swift new file mode 100644 index 0000000000000000000000000000000000000000..a3fa34b990dd11c80c1ef58b2d527852abe1c25d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Mask.swift @@ -0,0 +1,80 @@ +// +// Mask.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - MaskMode + +enum MaskMode: String, Codable { + case add = "a" + case subtract = "s" + case intersect = "i" + case lighten = "l" + case darken = "d" + case difference = "f" + case none = "n" +} + +// MARK: - Mask + +final class Mask: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Mask.CodingKeys.self) + mode = try container.decodeIfPresent(MaskMode.self, forKey: .mode) ?? .add + opacity = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(LottieVector1D(100)) + shape = try container.decode(KeyframeGroup.self, forKey: .shape) + inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false + expansion = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .expansion) ?? KeyframeGroup(LottieVector1D(0)) + } + + init(dictionary: [String: Any]) throws { + if + let modeRawType = dictionary[CodingKeys.mode.rawValue] as? String, + let mode = MaskMode(rawValue: modeRawType) + { + self.mode = mode + } else { + mode = .add + } + if let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any] { + opacity = try KeyframeGroup(dictionary: opacityDictionary) + } else { + opacity = KeyframeGroup(LottieVector1D(100)) + } + let shapeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.shape) + shape = try KeyframeGroup(dictionary: shapeDictionary) + inverted = (try? dictionary.value(for: CodingKeys.inverted)) ?? false + if let expansionDictionary = dictionary[CodingKeys.expansion.rawValue] as? [String: Any] { + expansion = try KeyframeGroup(dictionary: expansionDictionary) + } else { + expansion = KeyframeGroup(LottieVector1D(0)) + } + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case mode + case opacity = "o" + case inverted = "inv" + case shape = "pt" + case expansion = "x" + } + + let mode: MaskMode + + let opacity: KeyframeGroup + + let shape: KeyframeGroup + + let inverted: Bool + + let expansion: KeyframeGroup +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Transform.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Transform.swift new file mode 100644 index 0000000000000000000000000000000000000000..ced097a9e61af755b93b70a8e2b87d0a01ee2ea1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Objects/Transform.swift @@ -0,0 +1,260 @@ +// +// Transform.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +/// The animatable transform for a layer. Controls position, rotation, scale, and opacity. +final class Transform: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + /// This manual override of decode is required because we want to throw an error + /// in the case that there is not position data. + let container = try decoder.container(keyedBy: Transform.CodingKeys.self) + + // AnchorPoint + anchorPoint = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? + KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + + // Position + if container.contains(.positionX), container.contains(.positionY) { + // Position dimensions are split into two keyframe groups + positionX = try container.decode(KeyframeGroup.self, forKey: .positionX) + positionY = try container.decode(KeyframeGroup.self, forKey: .positionY) + position = nil + } else if let positionKeyframes = try? container.decode(KeyframeGroup.self, forKey: .position) { + // Position dimensions are a single keyframe group. + position = positionKeyframes + positionX = nil + positionY = nil + } else if + let positionContainer = try? container.nestedContainer(keyedBy: PositionCodingKeys.self, forKey: .position), + let positionX = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionX), + let positionY = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionY) + { + /// Position keyframes are split and nested. + self.positionX = positionX + self.positionY = positionY + position = nil + } else { + /// Default value. + position = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + positionX = nil + positionY = nil + } + + // Scale + scale = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? + KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)) + + // Rotation + if let rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationX) { + rotationX = rotation + } else { + rotationX = KeyframeGroup(LottieVector1D(0)) + } + + if let rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationY) { + rotationY = rotation + } else { + rotationY = KeyframeGroup(LottieVector1D(0)) + } + + if let rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { + rotationZ = rotation + } else { + rotationZ = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(LottieVector1D(0)) + } + rotation = nil + // Opacity + opacity = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(LottieVector1D(100)) + } + + init(dictionary: [String: Any]) throws { + if + let anchorPointDictionary = dictionary[CodingKeys.anchorPoint.rawValue] as? [String: Any], + let anchorPoint = try? KeyframeGroup(dictionary: anchorPointDictionary) + { + self.anchorPoint = anchorPoint + } else { + anchorPoint = Transform.default.anchorPoint + } + + if + let xDictionary = dictionary[CodingKeys.positionX.rawValue] as? [String: Any], + let yDictionary = dictionary[CodingKeys.positionY.rawValue] as? [String: Any] + { + positionX = try KeyframeGroup(dictionary: xDictionary) + positionY = try KeyframeGroup(dictionary: yDictionary) + position = nil + } else if + let positionDictionary = dictionary[CodingKeys.position.rawValue] as? [String: Any], + positionDictionary[KeyframeGroup.KeyframeWrapperKey.keyframeData.rawValue] != nil + { + position = try KeyframeGroup(dictionary: positionDictionary) + positionX = nil + positionY = nil + } else if + let positionDictionary = dictionary[CodingKeys.position.rawValue] as? [String: Any], + let xDictionary = positionDictionary[PositionCodingKeys.positionX.rawValue] as? [String: Any], + let yDictionary = positionDictionary[PositionCodingKeys.positionY.rawValue] as? [String: Any] + { + positionX = try KeyframeGroup(dictionary: xDictionary) + positionY = try KeyframeGroup(dictionary: yDictionary) + position = nil + } else { + position = Transform.default.position + positionX = nil + positionY = nil + } + + if + let scaleDictionary = dictionary[CodingKeys.scale.rawValue] as? [String: Any], + let scale = try? KeyframeGroup(dictionary: scaleDictionary) + { + self.scale = scale + } else { + scale = Transform.default.scale + } + + if + let rotationDictionary = dictionary[CodingKeys.rotationX.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationX = rotation + } else { + rotationX = Transform.default.rotationX + } + + if + let rotationDictionary = dictionary[CodingKeys.rotationY.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationY = rotation + } else { + rotationY = Transform.default.rotationY + } + + if + let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationZ = rotation + } else if + let rotationDictionary = dictionary[CodingKeys.rotationZ.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationZ = rotation + } else { + rotationZ = Transform.default.rotationZ + } + rotation = nil + if + let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any], + let opacity = try? KeyframeGroup(dictionary: opacityDictionary) + { + self.opacity = opacity + } else { + opacity = Transform.default.opacity + } + } + + init( + anchorPoint: KeyframeGroup, + position: KeyframeGroup?, + positionX: KeyframeGroup?, + positionY: KeyframeGroup?, + scale: KeyframeGroup, + rotationX: KeyframeGroup, + rotationY: KeyframeGroup, + rotationZ: KeyframeGroup, + opacity: KeyframeGroup, + rotation: KeyframeGroup?) + { + self.anchorPoint = anchorPoint + self.position = position + self.positionX = positionX + self.positionY = positionY + self.scale = scale + self.rotationX = rotationX + self.rotationY = rotationY + self.rotationZ = rotationZ + self.opacity = opacity + self.rotation = rotation + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case anchorPoint = "a" + case position = "p" + case positionX = "px" + case positionY = "py" + case scale = "s" + case rotation = "r" + case rotationX = "rx" + case rotationY = "ry" + case rotationZ = "rz" + case opacity = "o" + } + + enum PositionCodingKeys: String, CodingKey { + case split = "s" + case positionX = "x" + case positionY = "y" + } + + /// Default transform values to use if no transform is provided + static var `default`: Transform { + Transform( + anchorPoint: KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)), + position: KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)), + positionX: nil, + positionY: nil, + scale: KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)), + rotationX: KeyframeGroup(LottieVector1D(0)), + rotationY: KeyframeGroup(LottieVector1D(0)), + rotationZ: KeyframeGroup(LottieVector1D(0)), + opacity: KeyframeGroup(LottieVector1D(100)), + rotation: nil) + } + + /// The anchor point of the transform. + let anchorPoint: KeyframeGroup + + /// The position of the transform. This is nil if the position data was split. + let position: KeyframeGroup? + + /// The positionX of the transform. This is nil if the position property is set. + let positionX: KeyframeGroup? + + /// The positionY of the transform. This is nil if the position property is set. + let positionY: KeyframeGroup? + + /// The scale of the transform. + let scale: KeyframeGroup + + /// The rotation of the transform on X axis. + let rotationX: KeyframeGroup + + /// The rotation of the transform on Y axis. + let rotationY: KeyframeGroup + + /// The rotation of the transform on Z axis. + let rotationZ: KeyframeGroup + + /// The opacity of the transform. + let opacity: KeyframeGroup + + // MARK: Private + + /// Here for the CodingKeys.rotation = "r". `r` and `rz` are the same. + private let rotation: KeyframeGroup? +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Ellipse.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Ellipse.swift new file mode 100644 index 0000000000000000000000000000000000000000..42f4bc10d8bc53383674438d2ea3b54586edf6ae --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Ellipse.swift @@ -0,0 +1,72 @@ +// +// EllipseItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - PathDirection + +enum PathDirection: Int, Codable { + case clockwise = 1 + case userSetClockwise = 2 + case counterClockwise = 3 +} + +// MARK: - Ellipse + +final class Ellipse: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Ellipse.CodingKeys.self) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + position = try container.decode(KeyframeGroup.self, forKey: .position) + size = try container.decode(KeyframeGroup.self, forKey: .size) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let directionRawType = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawType) + { + self.direction = direction + } else { + direction = .clockwise + } + let positionDictionary: [String: Any] = try dictionary.value(for: CodingKeys.position) + position = try KeyframeGroup(dictionary: positionDictionary) + let sizeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.size) + size = try KeyframeGroup(dictionary: sizeDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The direction of the ellipse. + let direction: PathDirection + + /// The position of the ellipse + let position: KeyframeGroup + + /// The size of the ellipse + let size: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(size, forKey: .size) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case direction = "d" + case position = "p" + case size = "s" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Fill.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Fill.swift new file mode 100644 index 0000000000000000000000000000000000000000..f0090ae1adedaa1950d37fe11c9fcdfcc0e9877c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Fill.swift @@ -0,0 +1,72 @@ +// +// FillShape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - FillRule + +enum FillRule: Int, Codable { + case none + case nonZeroWinding + case evenOdd +} + +// MARK: - Fill + +final class Fill: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Fill.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + color = try container.decode(KeyframeGroup.self, forKey: .color) + fillRule = try container.decodeIfPresent(FillRule.self, forKey: .fillRule) ?? .nonZeroWinding + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let colorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.color) + color = try KeyframeGroup(dictionary: colorDictionary) + if + let fillRuleRawValue = dictionary[CodingKeys.fillRule.rawValue] as? Int, + let fillRule = FillRule(rawValue: fillRuleRawValue) + { + self.fillRule = fillRule + } else { + fillRule = .nonZeroWinding + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The color keyframes for the fill + let color: KeyframeGroup + + /// The fill rule to use when filling a path + let fillRule: FillRule + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(fillRule, forKey: .fillRule) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case color = "c" + case fillRule = "r" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/GradientFill.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/GradientFill.swift new file mode 100644 index 0000000000000000000000000000000000000000..5e83880046a01b231d839cca390a62224f736979 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/GradientFill.swift @@ -0,0 +1,135 @@ +// +// GradientFill.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - GradientType + +enum GradientType: Int, Codable, Sendable { + case none + case linear + case radial +} + +// MARK: - GradientFill + +final class GradientFill: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: GradientFill.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) + endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) + gradientType = try container.decode(GradientType.self, forKey: .gradientType) + highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) + highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) + fillRule = try container.decodeIfPresent(FillRule.self, forKey: .fillRule) ?? .nonZeroWinding + let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) + numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let startPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.startPoint) + startPoint = try KeyframeGroup(dictionary: startPointDictionary) + let endPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.endPoint) + endPoint = try KeyframeGroup(dictionary: endPointDictionary) + let gradientRawType: Int = try dictionary.value(for: CodingKeys.gradientType) + guard let gradient = GradientType(rawValue: gradientRawType) else { + throw InitializableError.invalidInput() + } + gradientType = gradient + if let highlightLengthDictionary = dictionary[CodingKeys.highlightLength.rawValue] as? [String: Any] { + highlightLength = try? KeyframeGroup(dictionary: highlightLengthDictionary) + } else { + highlightLength = nil + } + if let highlightAngleDictionary = dictionary[CodingKeys.highlightAngle.rawValue] as? [String: Any] { + highlightAngle = try? KeyframeGroup(dictionary: highlightAngleDictionary) + } else { + highlightAngle = nil + } + let colorsDictionary: [String: Any] = try dictionary.value(for: CodingKeys.colors) + let nestedColorsDictionary: [String: Any] = try colorsDictionary.value(for: GradientDataKeys.colors) + colors = try KeyframeGroup<[Double]>(dictionary: nestedColorsDictionary) + numberOfColors = try colorsDictionary.value(for: GradientDataKeys.numberOfColors) + if + let fillRuleRawValue = dictionary[CodingKeys.fillRule.rawValue] as? Int, + let fillRule = FillRule(rawValue: fillRuleRawValue) + { + self.fillRule = fillRule + } else { + fillRule = .nonZeroWinding + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The start of the gradient + let startPoint: KeyframeGroup + + /// The end of the gradient + let endPoint: KeyframeGroup + + /// The type of gradient + let gradientType: GradientType + + /// Gradient Highlight Length. Only if type is Radial + let highlightLength: KeyframeGroup? + + /// Highlight Angle. Only if type is Radial + let highlightAngle: KeyframeGroup? + + /// The number of color points in the gradient + let numberOfColors: Int + + /// The Colors of the gradient. + let colors: KeyframeGroup<[Double]> + + /// The fill rule to use when filling a path + let fillRule: FillRule + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(startPoint, forKey: .startPoint) + try container.encode(endPoint, forKey: .endPoint) + try container.encode(gradientType, forKey: .gradientType) + try container.encodeIfPresent(highlightLength, forKey: .highlightLength) + try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) + try container.encodeIfPresent(fillRule, forKey: .fillRule) + var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) + try colorsContainer.encode(colors, forKey: .colors) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case startPoint = "s" + case endPoint = "e" + case gradientType = "t" + case highlightLength = "h" + case highlightAngle = "a" + case colors = "g" + case fillRule = "r" + } + + private enum GradientDataKeys: String, CodingKey { + case numberOfColors = "p" + case colors = "k" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/GradientStroke.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/GradientStroke.swift new file mode 100644 index 0000000000000000000000000000000000000000..e2ef2bda1e08cf8ad699986635807221c9ffd626 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/GradientStroke.swift @@ -0,0 +1,236 @@ +// +// GradientStroke.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - LineCap + +enum LineCap: Int, Codable, Sendable { + case none + case butt + case round + case square +} + +// MARK: - LineJoin + +enum LineJoin: Int, Codable, Sendable { + case none + case miter + case round + case bevel +} + +// MARK: - GradientStroke + +final class GradientStroke: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: GradientStroke.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) + endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) + gradientType = try container.decode(GradientType.self, forKey: .gradientType) + highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) + highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) + width = try container.decode(KeyframeGroup.self, forKey: .width) + lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round + lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round + miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 + // TODO Decode Color Objects instead of array. + let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) + numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) + dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let startPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.startPoint) + startPoint = try KeyframeGroup(dictionary: startPointDictionary) + let endPointDictionary: [String: Any] = try dictionary.value(for: CodingKeys.endPoint) + endPoint = try KeyframeGroup(dictionary: endPointDictionary) + let gradientRawType: Int = try dictionary.value(for: CodingKeys.gradientType) + guard let gradient = GradientType(rawValue: gradientRawType) else { + throw InitializableError.invalidInput() + } + gradientType = gradient + if let highlightLengthDictionary = dictionary[CodingKeys.highlightLength.rawValue] as? [String: Any] { + highlightLength = try? KeyframeGroup(dictionary: highlightLengthDictionary) + } else { + highlightLength = nil + } + if let highlightAngleDictionary = dictionary[CodingKeys.highlightAngle.rawValue] as? [String: Any] { + highlightAngle = try? KeyframeGroup(dictionary: highlightAngleDictionary) + } else { + highlightAngle = nil + } + let widthDictionary: [String: Any] = try dictionary.value(for: CodingKeys.width) + width = try KeyframeGroup(dictionary: widthDictionary) + if + let lineCapRawValue = dictionary[CodingKeys.lineCap.rawValue] as? Int, + let lineCap = LineCap(rawValue: lineCapRawValue) + { + self.lineCap = lineCap + } else { + lineCap = .round + } + if + let lineJoinRawValue = dictionary[CodingKeys.lineJoin.rawValue] as? Int, + let lineJoin = LineJoin(rawValue: lineJoinRawValue) + { + self.lineJoin = lineJoin + } else { + lineJoin = .round + } + miterLimit = (try? dictionary.value(for: CodingKeys.miterLimit)) ?? 4 + let colorsDictionary: [String: Any] = try dictionary.value(for: CodingKeys.colors) + let nestedColorsDictionary: [String: Any] = try colorsDictionary.value(for: GradientDataKeys.colors) + colors = try KeyframeGroup<[Double]>(dictionary: nestedColorsDictionary) + numberOfColors = try colorsDictionary.value(for: GradientDataKeys.numberOfColors) + let dashPatternDictionaries = dictionary[CodingKeys.dashPattern.rawValue] as? [[String: Any]] + dashPattern = try? dashPatternDictionaries?.map { try DashElement(dictionary: $0) } + try super.init(dictionary: dictionary) + } + + init( + name: String, + hidden: Bool, + opacity: KeyframeGroup, + startPoint: KeyframeGroup, + endPoint: KeyframeGroup, + gradientType: GradientType, + highlightLength: KeyframeGroup?, + highlightAngle: KeyframeGroup?, + numberOfColors: Int, + colors: KeyframeGroup<[Double]>, + width: KeyframeGroup, + lineCap: LineCap, + lineJoin: LineJoin, + miterLimit: Double, + dashPattern: [DashElement]?) + { + self.opacity = opacity + self.startPoint = startPoint + self.endPoint = endPoint + self.gradientType = gradientType + self.highlightLength = highlightLength + self.highlightAngle = highlightAngle + self.numberOfColors = numberOfColors + self.colors = colors + self.width = width + self.lineCap = lineCap + self.lineJoin = lineJoin + self.miterLimit = miterLimit + self.dashPattern = dashPattern + super.init(name: name, type: .gradientStroke, hidden: hidden) + } + + // MARK: Internal + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The start of the gradient + let startPoint: KeyframeGroup + + /// The end of the gradient + let endPoint: KeyframeGroup + + /// The type of gradient + let gradientType: GradientType + + /// Gradient Highlight Length. Only if type is Radial + let highlightLength: KeyframeGroup? + + /// Highlight Angle. Only if type is Radial + let highlightAngle: KeyframeGroup? + + /// The number of color points in the gradient + let numberOfColors: Int + + /// The Colors of the gradient. + let colors: KeyframeGroup<[Double]> + + /// The width of the stroke + let width: KeyframeGroup + + /// Line Cap + let lineCap: LineCap + + /// Line Join + let lineJoin: LineJoin + + /// Miter Limit + let miterLimit: Double + + /// The dash pattern of the stroke + let dashPattern: [DashElement]? + + /// Creates a copy of this GradientStroke with the given updated width keyframes + func copy(width newWidth: KeyframeGroup) -> GradientStroke { + GradientStroke( + name: name, + hidden: hidden, + opacity: opacity, + startPoint: startPoint, + endPoint: endPoint, + gradientType: gradientType, + highlightLength: highlightLength, + highlightAngle: highlightAngle, + numberOfColors: numberOfColors, + colors: colors, + width: newWidth, + lineCap: lineCap, + lineJoin: lineJoin, + miterLimit: miterLimit, + dashPattern: dashPattern) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(startPoint, forKey: .startPoint) + try container.encode(endPoint, forKey: .endPoint) + try container.encode(gradientType, forKey: .gradientType) + try container.encodeIfPresent(highlightLength, forKey: .highlightLength) + try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) + try container.encode(width, forKey: .width) + try container.encode(lineCap, forKey: .lineCap) + try container.encode(lineJoin, forKey: .lineJoin) + try container.encode(miterLimit, forKey: .miterLimit) + var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) + try colorsContainer.encode(colors, forKey: .colors) + try container.encodeIfPresent(dashPattern, forKey: .dashPattern) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case startPoint = "s" + case endPoint = "e" + case gradientType = "t" + case highlightLength = "h" + case highlightAngle = "a" + case colors = "g" + case width = "w" + case lineCap = "lc" + case lineJoin = "lj" + case miterLimit = "ml" + case dashPattern = "d" + } + + private enum GradientDataKeys: String, CodingKey { + case numberOfColors = "p" + case colors = "k" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Group.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Group.swift new file mode 100644 index 0000000000000000000000000000000000000000..84a48dde4d88aa77d9281e5ec2a1b285f2813e84 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Group.swift @@ -0,0 +1,46 @@ +// +// GroupItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// An item that define a a group of shape items +final class Group: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Group.CodingKeys.self) + items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let itemDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.items) + items = try [ShapeItem].fromDictionaries(itemDictionaries) + try super.init(dictionary: dictionary) + } + + init(items: [ShapeItem], name: String) { + self.items = items + super.init(name: name, type: .group, hidden: false) + } + + // MARK: Internal + + /// A list of shape items. + let items: [ShapeItem] + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(items, forKey: .items) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case items = "it" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Merge.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Merge.swift new file mode 100644 index 0000000000000000000000000000000000000000..e14e5934e5cc75700ff18a1fc8c75cfc038d70bf --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Merge.swift @@ -0,0 +1,56 @@ +// +// Merge.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - MergeMode + +enum MergeMode: Int, Codable, Sendable { + case none + case merge + case add + case subtract + case intersect + case exclude +} + +// MARK: - Merge + +final class Merge: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Merge.CodingKeys.self) + mode = try container.decode(MergeMode.self, forKey: .mode) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let modeRawType: Int = try dictionary.value(for: CodingKeys.mode) + guard let mode = MergeMode(rawValue: modeRawType) else { + throw InitializableError.invalidInput() + } + self.mode = mode + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The mode of the merge path + let mode: MergeMode + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(mode, forKey: .mode) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case mode = "mm" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Rectangle.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Rectangle.swift new file mode 100644 index 0000000000000000000000000000000000000000..339c1d7521cad406678b0c53dd1f8037301019df --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Rectangle.swift @@ -0,0 +1,70 @@ +// +// Rectangle.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +final class Rectangle: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Rectangle.CodingKeys.self) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + position = try container.decode(KeyframeGroup.self, forKey: .position) + size = try container.decode(KeyframeGroup.self, forKey: .size) + cornerRadius = try container.decode(KeyframeGroup.self, forKey: .cornerRadius) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let directionRawType = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawType) + { + self.direction = direction + } else { + direction = .clockwise + } + let positionDictionary: [String: Any] = try dictionary.value(for: CodingKeys.position) + position = try KeyframeGroup(dictionary: positionDictionary) + let sizeDictionary: [String: Any] = try dictionary.value(for: CodingKeys.size) + size = try KeyframeGroup(dictionary: sizeDictionary) + let cornerRadiusDictionary: [String: Any] = try dictionary.value(for: CodingKeys.cornerRadius) + cornerRadius = try KeyframeGroup(dictionary: cornerRadiusDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The direction of the rect. + let direction: PathDirection + + /// The position + let position: KeyframeGroup + + /// The size + let size: KeyframeGroup + + /// The Corner radius of the rectangle + let cornerRadius: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(size, forKey: .size) + try container.encode(cornerRadius, forKey: .cornerRadius) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case direction = "d" + case position = "p" + case size = "s" + case cornerRadius = "r" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Repeater.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Repeater.swift new file mode 100644 index 0000000000000000000000000000000000000000..7062bdd53b43cf3c240dbf3041ab184505ef0c6c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Repeater.swift @@ -0,0 +1,172 @@ +// +// Repeater.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +final class Repeater: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Repeater.CodingKeys.self) + copies = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .copies) ?? KeyframeGroup(LottieVector1D(0)) + offset = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .offset) ?? KeyframeGroup(LottieVector1D(0)) + let transformContainer = try container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) + startOpacity = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .startOpacity) ?? KeyframeGroup(LottieVector1D(100)) + endOpacity = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .endOpacity) ?? KeyframeGroup(LottieVector1D(100)) + if let rotation = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) { + rotationZ = rotation + } else if let rotation = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { + rotationZ = rotation + } else { + rotationZ = KeyframeGroup(LottieVector1D(0)) + } + + rotationX = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .rotationX) ?? KeyframeGroup(LottieVector1D(0)) + rotationY = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .rotationY) ?? KeyframeGroup(LottieVector1D(0)) + + position = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? + KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + anchorPoint = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? + KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + scale = try transformContainer + .decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? + KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if let copiesDictionary = dictionary[CodingKeys.copies.rawValue] as? [String: Any] { + copies = try KeyframeGroup(dictionary: copiesDictionary) + } else { + copies = KeyframeGroup(LottieVector1D(0)) + } + if let offsetDictionary = dictionary[CodingKeys.offset.rawValue] as? [String: Any] { + offset = try KeyframeGroup(dictionary: offsetDictionary) + } else { + offset = KeyframeGroup(LottieVector1D(0)) + } + let transformDictionary: [String: Any] = try dictionary.value(for: CodingKeys.transform) + if let startOpacityDictionary = transformDictionary[TransformKeys.startOpacity.rawValue] as? [String: Any] { + startOpacity = try KeyframeGroup(dictionary: startOpacityDictionary) + } else { + startOpacity = KeyframeGroup(LottieVector1D(100)) + } + if let endOpacityDictionary = transformDictionary[TransformKeys.endOpacity.rawValue] as? [String: Any] { + endOpacity = try KeyframeGroup(dictionary: endOpacityDictionary) + } else { + endOpacity = KeyframeGroup(LottieVector1D(100)) + } + if let rotationDictionary = transformDictionary[TransformKeys.rotationX.rawValue] as? [String: Any] { + rotationX = try KeyframeGroup(dictionary: rotationDictionary) + } else { + rotationX = KeyframeGroup(LottieVector1D(0)) + } + if let rotationDictionary = transformDictionary[TransformKeys.rotationY.rawValue] as? [String: Any] { + rotationY = try KeyframeGroup(dictionary: rotationDictionary) + } else { + rotationY = KeyframeGroup(LottieVector1D(0)) + } + if let rotationDictionary = transformDictionary[TransformKeys.rotation.rawValue] as? [String: Any] { + rotationZ = try KeyframeGroup(dictionary: rotationDictionary) + } else if let rotationDictionary = transformDictionary[TransformKeys.rotationZ.rawValue] as? [String: Any] { + rotationZ = try KeyframeGroup(dictionary: rotationDictionary) + } else { + rotationZ = KeyframeGroup(LottieVector1D(0)) + } + if let positionDictionary = transformDictionary[TransformKeys.position.rawValue] as? [String: Any] { + position = try KeyframeGroup(dictionary: positionDictionary) + } else { + position = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + } + if let anchorPointDictionary = transformDictionary[TransformKeys.anchorPoint.rawValue] as? [String: Any] { + anchorPoint = try KeyframeGroup(dictionary: anchorPointDictionary) + } else { + anchorPoint = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + } + if let scaleDictionary = transformDictionary[TransformKeys.scale.rawValue] as? [String: Any] { + scale = try KeyframeGroup(dictionary: scaleDictionary) + } else { + scale = KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)) + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The number of copies to repeat + let copies: KeyframeGroup + + /// The offset of each copy + let offset: KeyframeGroup + + /// Start Opacity + let startOpacity: KeyframeGroup + + /// End opacity + let endOpacity: KeyframeGroup + + /// The rotation on X axis + let rotationX: KeyframeGroup + + /// The rotation on Y axis + let rotationY: KeyframeGroup + + /// The rotation on Z axis + let rotationZ: KeyframeGroup + + /// Anchor Point + let anchorPoint: KeyframeGroup + + /// Position + let position: KeyframeGroup + + /// Scale + let scale: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(copies, forKey: .copies) + try container.encode(offset, forKey: .offset) + var transformContainer = container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) + try transformContainer.encode(startOpacity, forKey: .startOpacity) + try transformContainer.encode(endOpacity, forKey: .endOpacity) + try transformContainer.encode(rotationX, forKey: .rotationX) + try transformContainer.encode(rotationY, forKey: .rotationY) + try transformContainer.encode(rotationZ, forKey: .rotationZ) + try transformContainer.encode(position, forKey: .position) + try transformContainer.encode(anchorPoint, forKey: .anchorPoint) + try transformContainer.encode(scale, forKey: .scale) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case copies = "c" + case offset = "o" + case transform = "tr" + } + + private enum TransformKeys: String, CodingKey { + case rotation = "r" + case rotationX = "rx" + case rotationY = "ry" + case rotationZ = "rz" + case startOpacity = "so" + case endOpacity = "eo" + case anchorPoint = "a" + case position = "p" + case scale = "s" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/RoundedCorners.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/RoundedCorners.swift new file mode 100644 index 0000000000000000000000000000000000000000..6cc1aa85591dc688aed03f6bb8fdc17574ad0d62 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/RoundedCorners.swift @@ -0,0 +1,45 @@ +// +// RoundedCorners.swift +// Lottie +// +// Created by Duolingo on 10/31/22. +// + +// MARK: - RoundedCorners + +final class RoundedCorners: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: RoundedCorners.CodingKeys.self) + radius = try + container.decode( + KeyframeGroup.self, + forKey: .radius) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let radiusDictionary: [String: Any] = try dictionary.value(for: CodingKeys.radius) + radius = try KeyframeGroup(dictionary: radiusDictionary) + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The radius of rounded corners + let radius: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(radius, forKey: .radius) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case radius = "r" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Shape.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Shape.swift new file mode 100644 index 0000000000000000000000000000000000000000..eb5158bf11c83aeaa345b5ad996531eceda93eca --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Shape.swift @@ -0,0 +1,54 @@ +// +// VectorShape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +/// An item that defines an custom shape +final class Shape: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Shape.CodingKeys.self) + path = try container.decode(KeyframeGroup.self, forKey: .path) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let pathDictionary: [String: Any] = try dictionary.value(for: CodingKeys.path) + path = try KeyframeGroup(dictionary: pathDictionary) + if + let directionRawValue = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawValue) + { + self.direction = direction + } else { + direction = nil + } + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The Path + let path: KeyframeGroup + + let direction: PathDirection? + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(path, forKey: .path) + try container.encodeIfPresent(direction, forKey: .direction) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case path = "ks" + case direction = "d" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/ShapeItem.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/ShapeItem.swift new file mode 100644 index 0000000000000000000000000000000000000000..f0581006006ccce53b6f476652992ee6d6a8fa17 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/ShapeItem.swift @@ -0,0 +1,171 @@ +// +// ShapeItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - ShapeType + +enum ShapeType: String, Codable, Sendable { + case ellipse = "el" + case fill = "fl" + case gradientFill = "gf" + case group = "gr" + case gradientStroke = "gs" + case merge = "mm" + case rectangle = "rc" + case repeater = "rp" + case round = "rd" + case shape = "sh" + case star = "sr" + case stroke = "st" + case trim = "tm" + case transform = "tr" + case unknown + + public init(from decoder: Decoder) throws { + self = try ShapeType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } +} + +// MARK: ClassFamily + +extension ShapeType: ClassFamily { + + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .ellipse: + Ellipse.self + case .fill: + Fill.self + case .gradientFill: + GradientFill.self + case .group: + Group.self + case .gradientStroke: + GradientStroke.self + case .merge: + Merge.self + case .rectangle: + Rectangle.self + case .repeater: + Repeater.self + case .round: + RoundedCorners.self + case .shape: + Shape.self + case .star: + Star.self + case .stroke: + Stroke.self + case .trim: + Trim.self + case .transform: + ShapeTransform.self + default: + ShapeItem.self + } + } +} + +// MARK: - ShapeItem + +/// An item belonging to a Shape Layer +class ShapeItem: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeItem.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" + type = try container.decode(ShapeType.self, forKey: .type) + hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false + } + + required init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer" + type = ShapeType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .unknown + hidden = (try? dictionary.value(for: CodingKeys.hidden)) ?? false + } + + init( + name: String, + type: ShapeType, + hidden: Bool) + { + self.name = name + self.type = type + self.hidden = hidden + } + + // MARK: Internal + + /// The name of the shape + let name: String + + /// The type of shape + let type: ShapeType + + let hidden: Bool + + // MARK: Fileprivate + + fileprivate enum CodingKeys: String, CodingKey { + case name = "nm" + case type = "ty" + case hidden = "hd" + } +} + +extension [ShapeItem] { + + static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [ShapeItem] { + try dictionaries.compactMap { dictionary in + let shapeType = dictionary[ShapeItem.CodingKeys.type.rawValue] as? String + switch ShapeType(rawValue: shapeType ?? ShapeType.unknown.rawValue) { + case .ellipse: + return try Ellipse(dictionary: dictionary) + case .fill: + return try Fill(dictionary: dictionary) + case .gradientFill: + return try GradientFill(dictionary: dictionary) + case .group: + return try Group(dictionary: dictionary) + case .gradientStroke: + return try GradientStroke(dictionary: dictionary) + case .merge: + return try Merge(dictionary: dictionary) + case .rectangle: + return try Rectangle(dictionary: dictionary) + case .repeater: + return try Repeater(dictionary: dictionary) + case .round: + return try RoundedCorners(dictionary: dictionary) + case .shape: + return try Shape(dictionary: dictionary) + case .star: + return try Star(dictionary: dictionary) + case .stroke: + return try Stroke(dictionary: dictionary) + case .trim: + return try Trim(dictionary: dictionary) + case .transform: + return try ShapeTransform(dictionary: dictionary) + case .none: + return nil + default: + return try ShapeItem(dictionary: dictionary) + } + } + } +} + +// MARK: - ShapeItem + Sendable + +/// Since `ShapeItem` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.` +/// All `ShapeItem` subclasses are immutable `Sendable` values. +// swiftlint:disable:next no_unchecked_sendable +extension ShapeItem: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/ShapeTransform.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/ShapeTransform.swift new file mode 100644 index 0000000000000000000000000000000000000000..26c13db45d38269958edf487d07d3115abd9db72 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/ShapeTransform.swift @@ -0,0 +1,193 @@ +// +// TransformItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +final class ShapeTransform: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeTransform.CodingKeys.self) + anchor = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .anchor) ?? + KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + position = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? + KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + scale = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? + KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)) + + rotationX = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .rotationX) ?? KeyframeGroup(LottieVector1D(0)) + rotationY = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .rotationY) ?? KeyframeGroup(LottieVector1D(0)) + if + let rotation = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .rotation) + { + rotationZ = rotation + } else if + let rotation = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) + { + rotationZ = rotation + } else { + rotationZ = KeyframeGroup(LottieVector1D(0)) + } + + opacity = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(LottieVector1D(100)) + skew = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skew) ?? KeyframeGroup(LottieVector1D(0)) + skewAxis = try container + .decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) ?? KeyframeGroup(LottieVector1D(0)) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let anchorDictionary = dictionary[CodingKeys.anchor.rawValue] as? [String: Any], + let anchor = try? KeyframeGroup(dictionary: anchorDictionary) + { + self.anchor = anchor + } else { + anchor = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + } + if + let positionDictionary = dictionary[CodingKeys.position.rawValue] as? [String: Any], + let position = try? KeyframeGroup(dictionary: positionDictionary) + { + self.position = position + } else { + position = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)) + } + if + let scaleDictionary = dictionary[CodingKeys.scale.rawValue] as? [String: Any], + let scale = try? KeyframeGroup(dictionary: scaleDictionary) + { + self.scale = scale + } else { + scale = KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)) + } + + if + let rotationDictionary = dictionary[CodingKeys.rotationX.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationX = rotation + } else { + rotationX = KeyframeGroup(LottieVector1D(0)) + } + + if + let rotationDictionary = dictionary[CodingKeys.rotationY.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationY = rotation + } else { + rotationY = KeyframeGroup(LottieVector1D(0)) + } + + if + let rotationDictionary = dictionary[CodingKeys.rotation.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationZ = rotation + } else if + let rotationDictionary = dictionary[CodingKeys.rotationZ.rawValue] as? [String: Any], + let rotation = try? KeyframeGroup(dictionary: rotationDictionary) + { + rotationZ = rotation + } else { + rotationZ = KeyframeGroup(LottieVector1D(0)) + } + + if + let opacityDictionary = dictionary[CodingKeys.opacity.rawValue] as? [String: Any], + let opacity = try? KeyframeGroup(dictionary: opacityDictionary) + { + self.opacity = opacity + } else { + opacity = KeyframeGroup(LottieVector1D(100)) + } + if + let skewDictionary = dictionary[CodingKeys.skew.rawValue] as? [String: Any], + let skew = try? KeyframeGroup(dictionary: skewDictionary) + { + self.skew = skew + } else { + skew = KeyframeGroup(LottieVector1D(0)) + } + if + let skewAxisDictionary = dictionary[CodingKeys.skewAxis.rawValue] as? [String: Any], + let skewAxis = try? KeyframeGroup(dictionary: skewAxisDictionary) + { + self.skewAxis = skewAxis + } else { + skewAxis = KeyframeGroup(LottieVector1D(0)) + } + + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// Anchor Point + let anchor: KeyframeGroup + + /// Position + let position: KeyframeGroup + + /// Scale + let scale: KeyframeGroup + + /// Rotation on X axis + let rotationX: KeyframeGroup + + /// Rotation on Y axis + let rotationY: KeyframeGroup + + /// Rotation on Z axis + let rotationZ: KeyframeGroup + + /// opacity + let opacity: KeyframeGroup + + /// Skew + let skew: KeyframeGroup + + /// Skew Axis + let skewAxis: KeyframeGroup + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(anchor, forKey: .anchor) + try container.encode(position, forKey: .position) + try container.encode(scale, forKey: .scale) + try container.encode(rotationX, forKey: .rotationX) + try container.encode(rotationY, forKey: .rotationY) + try container.encode(rotationZ, forKey: .rotationZ) + try container.encode(opacity, forKey: .opacity) + try container.encode(skew, forKey: .skew) + try container.encode(skewAxis, forKey: .skewAxis) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case anchor = "a" + case position = "p" + case scale = "s" + case rotation = "r" + case rotationX = "rx" + case rotationY = "ry" + case rotationZ = "rz" + case opacity = "o" + case skew = "sk" + case skewAxis = "sa" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Star.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Star.swift new file mode 100644 index 0000000000000000000000000000000000000000..06bd869d66becfbd66dca6bea22d22a0857fd415 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Star.swift @@ -0,0 +1,129 @@ +// +// Star.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - StarType + +enum StarType: Int, Codable, Sendable { + case none + case star + case polygon +} + +// MARK: - Star + +final class Star: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Star.CodingKeys.self) + direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + position = try container.decode(KeyframeGroup.self, forKey: .position) + outerRadius = try container.decode(KeyframeGroup.self, forKey: .outerRadius) + outerRoundness = try container.decode(KeyframeGroup.self, forKey: .outerRoundness) + innerRadius = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRadius) + innerRoundness = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRoundness) + rotation = try container.decode(KeyframeGroup.self, forKey: .rotation) + points = try container.decode(KeyframeGroup.self, forKey: .points) + starType = try container.decode(StarType.self, forKey: .starType) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + if + let directionRawValue = dictionary[CodingKeys.direction.rawValue] as? Int, + let direction = PathDirection(rawValue: directionRawValue) + { + self.direction = direction + } else { + direction = .clockwise + } + let positionDictionary: [String: Any] = try dictionary.value(for: CodingKeys.position) + position = try KeyframeGroup(dictionary: positionDictionary) + let outerRadiusDictionary: [String: Any] = try dictionary.value(for: CodingKeys.outerRadius) + outerRadius = try KeyframeGroup(dictionary: outerRadiusDictionary) + let outerRoundnessDictionary: [String: Any] = try dictionary.value(for: CodingKeys.outerRoundness) + outerRoundness = try KeyframeGroup(dictionary: outerRoundnessDictionary) + if let innerRadiusDictionary = dictionary[CodingKeys.innerRadius.rawValue] as? [String: Any] { + innerRadius = try KeyframeGroup(dictionary: innerRadiusDictionary) + } else { + innerRadius = nil + } + if let innerRoundnessDictionary = dictionary[CodingKeys.innerRoundness.rawValue] as? [String: Any] { + innerRoundness = try KeyframeGroup(dictionary: innerRoundnessDictionary) + } else { + innerRoundness = nil + } + let rotationDictionary: [String: Any] = try dictionary.value(for: CodingKeys.rotation) + rotation = try KeyframeGroup(dictionary: rotationDictionary) + let pointsDictionary: [String: Any] = try dictionary.value(for: CodingKeys.points) + points = try KeyframeGroup(dictionary: pointsDictionary) + let starTypeRawValue: Int = try dictionary.value(for: CodingKeys.starType) + guard let starType = StarType(rawValue: starTypeRawValue) else { + throw InitializableError.invalidInput() + } + self.starType = starType + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The direction of the star. + let direction: PathDirection + + /// The position of the star + let position: KeyframeGroup + + /// The outer radius of the star + let outerRadius: KeyframeGroup + + /// The outer roundness of the star + let outerRoundness: KeyframeGroup + + /// The outer radius of the star + let innerRadius: KeyframeGroup? + + /// The outer roundness of the star + let innerRoundness: KeyframeGroup? + + /// The rotation of the star + let rotation: KeyframeGroup + + /// The number of points on the star + let points: KeyframeGroup + + /// The type of star + let starType: StarType + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(outerRadius, forKey: .outerRadius) + try container.encode(outerRoundness, forKey: .outerRoundness) + try container.encode(innerRadius, forKey: .innerRadius) + try container.encode(innerRoundness, forKey: .innerRoundness) + try container.encode(rotation, forKey: .rotation) + try container.encode(points, forKey: .points) + try container.encode(starType, forKey: .starType) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case direction = "d" + case position = "p" + case outerRadius = "or" + case outerRoundness = "os" + case innerRadius = "ir" + case innerRoundness = "is" + case rotation = "r" + case points = "pt" + case starType = "sy" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Stroke.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Stroke.swift new file mode 100644 index 0000000000000000000000000000000000000000..c9f5e8b1cc2e0084148c9ccbccbc5aa0d51a7b3f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Stroke.swift @@ -0,0 +1,134 @@ +// +// Stroke.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +final class Stroke: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Stroke.CodingKeys.self) + opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + color = try container.decode(KeyframeGroup.self, forKey: .color) + width = try container.decode(KeyframeGroup.self, forKey: .width) + lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round + lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round + miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 + dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let opacityDictionary: [String: Any] = try dictionary.value(for: CodingKeys.opacity) + opacity = try KeyframeGroup(dictionary: opacityDictionary) + let colorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.color) + color = try KeyframeGroup(dictionary: colorDictionary) + let widthDictionary: [String: Any] = try dictionary.value(for: CodingKeys.width) + width = try KeyframeGroup(dictionary: widthDictionary) + if + let lineCapRawValue = dictionary[CodingKeys.lineCap.rawValue] as? Int, + let lineCap = LineCap(rawValue: lineCapRawValue) + { + self.lineCap = lineCap + } else { + lineCap = .round + } + if + let lineJoinRawValue = dictionary[CodingKeys.lineJoin.rawValue] as? Int, + let lineJoin = LineJoin(rawValue: lineJoinRawValue) + { + self.lineJoin = lineJoin + } else { + lineJoin = .round + } + miterLimit = (try? dictionary.value(for: CodingKeys.miterLimit)) ?? 4 + let dashPatternDictionaries = dictionary[CodingKeys.dashPattern.rawValue] as? [[String: Any]] + dashPattern = try? dashPatternDictionaries?.map { try DashElement(dictionary: $0) } + try super.init(dictionary: dictionary) + } + + init( + name: String, + hidden: Bool, + opacity: KeyframeGroup, + color: KeyframeGroup, + width: KeyframeGroup, + lineCap: LineCap, + lineJoin: LineJoin, + miterLimit: Double, + dashPattern: [DashElement]?) + { + self.opacity = opacity + self.color = color + self.width = width + self.lineCap = lineCap + self.lineJoin = lineJoin + self.miterLimit = miterLimit + self.dashPattern = dashPattern + super.init(name: name, type: .stroke, hidden: hidden) + } + + // MARK: Internal + + /// The opacity of the stroke + let opacity: KeyframeGroup + + /// The Color of the stroke + let color: KeyframeGroup + + /// The width of the stroke + let width: KeyframeGroup + + /// Line Cap + let lineCap: LineCap + + /// Line Join + let lineJoin: LineJoin + + /// Miter Limit + let miterLimit: Double + + /// The dash pattern of the stroke + let dashPattern: [DashElement]? + + /// Creates a copy of this Stroke with the given updated width keyframes + func copy(width newWidth: KeyframeGroup) -> Stroke { + Stroke( + name: name, + hidden: hidden, + opacity: opacity, + color: color, + width: newWidth, + lineCap: lineCap, + lineJoin: lineJoin, + miterLimit: miterLimit, + dashPattern: dashPattern) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(width, forKey: .width) + try container.encode(lineCap, forKey: .lineCap) + try container.encode(lineJoin, forKey: .lineJoin) + try container.encode(miterLimit, forKey: .miterLimit) + try container.encodeIfPresent(dashPattern, forKey: .dashPattern) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case opacity = "o" + case color = "c" + case width = "w" + case lineCap = "lc" + case lineJoin = "lj" + case miterLimit = "ml" + case dashPattern = "d" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Trim.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Trim.swift new file mode 100644 index 0000000000000000000000000000000000000000..0843d8858b700bfcf124d8faa8f0c68cae79a10f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/ShapeItems/Trim.swift @@ -0,0 +1,83 @@ +// +// Trim.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +// MARK: - TrimType + +enum TrimType: Int, Codable { + case simultaneously = 1 + case individually = 2 +} + +// MARK: - Trim + +final class Trim: ShapeItem { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Trim.CodingKeys.self) + start = try container.decode(KeyframeGroup.self, forKey: .start) + end = try container.decode(KeyframeGroup.self, forKey: .end) + offset = try container.decode(KeyframeGroup.self, forKey: .offset) + trimType = try container.decode(TrimType.self, forKey: .trimType) + try super.init(from: decoder) + } + + required init(dictionary: [String: Any]) throws { + let startDictionary: [String: Any] = try dictionary.value(for: CodingKeys.start) + start = try KeyframeGroup(dictionary: startDictionary) + let endDictionary: [String: Any] = try dictionary.value(for: CodingKeys.end) + end = try KeyframeGroup(dictionary: endDictionary) + let offsetDictionary: [String: Any] = try dictionary.value(for: CodingKeys.offset) + offset = try KeyframeGroup(dictionary: offsetDictionary) + let trimTypeRawValue: Int = try dictionary.value(for: CodingKeys.trimType) + guard let trimType = TrimType(rawValue: trimTypeRawValue) else { + throw InitializableError.invalidInput() + } + self.trimType = trimType + try super.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The start of the trim + let start: KeyframeGroup + + /// The end of the trim + let end: KeyframeGroup + + /// The offset of the trim + let offset: KeyframeGroup + + let trimType: TrimType + + /// If this trim doesn't affect the path at all then we can consider it empty + var isEmpty: Bool { + start.keyframes.count == 1 + && start.keyframes[0].value.value == 0 + && end.keyframes.count == 1 + && end.keyframes[0].value.value == 100 + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(start, forKey: .start) + try container.encode(end, forKey: .end) + try container.encode(offset, forKey: .offset) + try container.encode(trimType, forKey: .trimType) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case start = "s" + case end = "e" + case offset = "o" + case trimType = "m" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/Font.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/Font.swift new file mode 100644 index 0000000000000000000000000000000000000000..0a1187f5bd67514c778908cceecd388c4c4b2c9f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/Font.swift @@ -0,0 +1,59 @@ +// +// Font.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +// MARK: - Font + +final class Font: Codable, Sendable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + name = try dictionary.value(for: CodingKeys.name) + familyName = try dictionary.value(for: CodingKeys.familyName) + style = try dictionary.value(for: CodingKeys.style) + ascent = try dictionary.value(for: CodingKeys.ascent) + } + + // MARK: Internal + + let name: String + let familyName: String + let style: String + let ascent: Double + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case name = "fName" + case familyName = "fFamily" + case style = "fStyle" + case ascent + } + +} + +// MARK: - FontList + +/// A list of fonts +final class FontList: Codable, Sendable, DictionaryInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + let fontDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.fonts) + fonts = try fontDictionaries.map { try Font(dictionary: $0) } + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case fonts = "list" + } + + let fonts: [Font] + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/Glyph.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/Glyph.swift new file mode 100644 index 0000000000000000000000000000000000000000..7c68c0701ed88c63f37d1a856bf6580ef73d2e59 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/Glyph.swift @@ -0,0 +1,94 @@ +// +// Glyph.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +/// A model that holds a vector character +final class Glyph: Codable, Sendable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Glyph.CodingKeys.self) + character = try container.decode(String.self, forKey: .character) + fontSize = try container.decode(Double.self, forKey: .fontSize) + fontFamily = try container.decode(String.self, forKey: .fontFamily) + fontStyle = try container.decode(String.self, forKey: .fontStyle) + width = try container.decode(Double.self, forKey: .width) + if + container.contains(.shapeWrapper), + let shapeContainer = try? container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper), + shapeContainer.contains(.shapes) + { + shapes = try shapeContainer.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .shapes) + } else { + shapes = [] + } + } + + init(dictionary: [String: Any]) throws { + character = try dictionary.value(for: CodingKeys.character) + fontSize = try dictionary.value(for: CodingKeys.fontSize) + fontFamily = try dictionary.value(for: CodingKeys.fontFamily) + fontStyle = try dictionary.value(for: CodingKeys.fontStyle) + width = try dictionary.value(for: CodingKeys.width) + if + let shapes = dictionary[CodingKeys.shapeWrapper.rawValue] as? [String: Any], + let shapeDictionaries = shapes[ShapeKey.shapes.rawValue] as? [[String: Any]] + { + self.shapes = try [ShapeItem].fromDictionaries(shapeDictionaries) + } else { + shapes = [ShapeItem]() + } + } + + // MARK: Internal + + /// The character + let character: String + + /// The font size of the character + let fontSize: Double + + /// The font family of the character + let fontFamily: String + + /// The Style of the character + let fontStyle: String + + /// The Width of the character + let width: Double + + /// The Shape Data of the Character + let shapes: [ShapeItem] + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(character, forKey: .character) + try container.encode(fontSize, forKey: .fontSize) + try container.encode(fontFamily, forKey: .fontFamily) + try container.encode(fontStyle, forKey: .fontStyle) + try container.encode(width, forKey: .width) + + var shapeContainer = container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper) + try shapeContainer.encode(shapes, forKey: .shapes) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case character = "ch" + case fontSize = "size" + case fontFamily = "fFamily" + case fontStyle = "style" + case width = "w" + case shapeWrapper = "data" + } + + private enum ShapeKey: String, CodingKey { + case shapes + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/TextAnimator.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/TextAnimator.swift new file mode 100644 index 0000000000000000000000000000000000000000..0ff9415e4f3e433a48a1cd4696cbf60b87ffe9d0 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/TextAnimator.swift @@ -0,0 +1,240 @@ +// +// TextAnimator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +// MARK: - TextRangeUnit + +enum TextRangeUnit: Int, RawRepresentable, Codable { + case percentage = 1 + case index = 2 +} + +// MARK: - TextAnimator + +final class TextAnimator: Codable, DictionaryInitializable { + + // MARK: Lifecycle + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TextAnimator.CodingKeys.self) + name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" + + let animatorContainer = try container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) + fillColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .fillColor) + strokeColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeColor) + strokeWidth = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeWidth) + tracking = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .tracking) + anchor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) + position = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) + scale = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) + skew = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skew) + skewAxis = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) + rotationX = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotationX) + rotationY = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotationY) + if let rotation = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) { + rotationZ = rotation + } else if let rotation = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { + rotationZ = rotation + } else { + rotationZ = nil + } + opacity = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) + + let selectorContainer = try? container.nestedContainer(keyedBy: TextSelectorKeys.self, forKey: .textSelector) + start = try? selectorContainer?.decodeIfPresent(KeyframeGroup.self, forKey: .start) + end = try? selectorContainer?.decodeIfPresent(KeyframeGroup.self, forKey: .end) + textRangeUnit = try? selectorContainer?.decodeIfPresent(TextRangeUnit.self, forKey: .textRangeUnits) + } + + init(dictionary: [String: Any]) throws { + name = (try? dictionary.value(for: CodingKeys.name)) ?? "" + + let animatorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textAnimator) + if let fillColorDictionary = animatorDictionary[TextAnimatorKeys.fillColor.rawValue] as? [String: Any] { + fillColor = try? KeyframeGroup(dictionary: fillColorDictionary) + } else { + fillColor = nil + } + if let strokeColorDictionary = animatorDictionary[TextAnimatorKeys.strokeColor.rawValue] as? [String: Any] { + strokeColor = try? KeyframeGroup(dictionary: strokeColorDictionary) + } else { + strokeColor = nil + } + if let strokeWidthDictionary = animatorDictionary[TextAnimatorKeys.strokeWidth.rawValue] as? [String: Any] { + strokeWidth = try? KeyframeGroup(dictionary: strokeWidthDictionary) + } else { + strokeWidth = nil + } + if let trackingDictionary = animatorDictionary[TextAnimatorKeys.tracking.rawValue] as? [String: Any] { + tracking = try? KeyframeGroup(dictionary: trackingDictionary) + } else { + tracking = nil + } + if let anchorDictionary = animatorDictionary[TextAnimatorKeys.anchor.rawValue] as? [String: Any] { + anchor = try? KeyframeGroup(dictionary: anchorDictionary) + } else { + anchor = nil + } + if let positionDictionary = animatorDictionary[TextAnimatorKeys.position.rawValue] as? [String: Any] { + position = try? KeyframeGroup(dictionary: positionDictionary) + } else { + position = nil + } + if let scaleDictionary = animatorDictionary[TextAnimatorKeys.scale.rawValue] as? [String: Any] { + scale = try? KeyframeGroup(dictionary: scaleDictionary) + } else { + scale = nil + } + if let skewDictionary = animatorDictionary[TextAnimatorKeys.skew.rawValue] as? [String: Any] { + skew = try? KeyframeGroup(dictionary: skewDictionary) + } else { + skew = nil + } + if let skewAxisDictionary = animatorDictionary[TextAnimatorKeys.skewAxis.rawValue] as? [String: Any] { + skewAxis = try? KeyframeGroup(dictionary: skewAxisDictionary) + } else { + skewAxis = nil + } + if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotationX.rawValue] as? [String: Any] { + rotationX = try? KeyframeGroup(dictionary: rotationDictionary) + } else { + rotationX = nil + } + + if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotationY.rawValue] as? [String: Any] { + rotationY = try? KeyframeGroup(dictionary: rotationDictionary) + } else { + rotationY = nil + } + + if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotation.rawValue] as? [String: Any] { + rotationZ = try? KeyframeGroup(dictionary: rotationDictionary) + } else if let rotationDictionary = animatorDictionary[TextAnimatorKeys.rotationZ.rawValue] as? [String: Any] { + rotationZ = try? KeyframeGroup(dictionary: rotationDictionary) + } else { + rotationZ = nil + } + + if let opacityDictionary = animatorDictionary[TextAnimatorKeys.opacity.rawValue] as? [String: Any] { + opacity = try KeyframeGroup(dictionary: opacityDictionary) + } else { + opacity = nil + } + + let selectorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textSelector) + + if let startDictionary = selectorDictionary[TextSelectorKeys.start.rawValue] as? [String: Any] { + start = try KeyframeGroup(dictionary: startDictionary) + } else { + start = nil + } + + if let endDictionary = selectorDictionary[TextSelectorKeys.end.rawValue] as? [String: Any] { + end = try KeyframeGroup(dictionary: endDictionary) + } else { + end = nil + } + + if let textRangeUnitValue = selectorDictionary[TextSelectorKeys.textRangeUnits.rawValue] as? Int { + textRangeUnit = TextRangeUnit(rawValue: textRangeUnitValue) + } else { + textRangeUnit = nil + } + } + + // MARK: Internal + + let name: String + + /// Anchor + let anchor: KeyframeGroup? + + /// Position + let position: KeyframeGroup? + + /// Scale + let scale: KeyframeGroup? + + /// Skew + let skew: KeyframeGroup? + + /// Skew Axis + let skewAxis: KeyframeGroup? + + /// Rotation on X axis + let rotationX: KeyframeGroup? + + /// Rotation on Y axis + let rotationY: KeyframeGroup? + + /// Rotation on Z axis + let rotationZ: KeyframeGroup? + + /// Opacity + let opacity: KeyframeGroup? + + /// Stroke Color + let strokeColor: KeyframeGroup? + + /// Fill Color + let fillColor: KeyframeGroup? + + /// Stroke Width + let strokeWidth: KeyframeGroup? + + /// Tracking + let tracking: KeyframeGroup? + + /// Start + let start: KeyframeGroup? + + /// End + let end: KeyframeGroup? + + /// The type of unit used by the start/end ranges + let textRangeUnit: TextRangeUnit? + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var animatorContainer = container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) + try animatorContainer.encodeIfPresent(fillColor, forKey: .fillColor) + try animatorContainer.encodeIfPresent(strokeColor, forKey: .strokeColor) + try animatorContainer.encodeIfPresent(strokeWidth, forKey: .strokeWidth) + try animatorContainer.encodeIfPresent(tracking, forKey: .tracking) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case textSelector = "s" + case textAnimator = "a" + case name = "nm" + } + + private enum TextSelectorKeys: String, CodingKey { + case start = "s" + case end = "e" + case offset = "o" + case textRangeUnits = "r" + } + + private enum TextAnimatorKeys: String, CodingKey { + case fillColor = "fc" + case strokeColor = "sc" + case strokeWidth = "sw" + case tracking = "t" + case anchor = "a" + case position = "p" + case scale = "s" + case skew = "sk" + case skewAxis = "sa" + case rotation = "r" + case rotationX = "rx" + case rotationY = "ry" + case rotationZ = "rz" + case opacity = "o" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/TextDocument.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/TextDocument.swift new file mode 100644 index 0000000000000000000000000000000000000000..674c9488ca7980989519c4929195e92560067266 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Model/Text/TextDocument.swift @@ -0,0 +1,121 @@ +// +// TextDocument.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +// MARK: - TextJustification + +enum TextJustification: Int, Codable { + case left + case right + case center +} + +// MARK: - TextDocument + +final class TextDocument: Codable, DictionaryInitializable, AnyInitializable { + + // MARK: Lifecycle + + init(dictionary: [String: Any]) throws { + text = try dictionary.value(for: CodingKeys.text) + fontSize = try dictionary.value(for: CodingKeys.fontSize) + fontFamily = try dictionary.value(for: CodingKeys.fontFamily) + let justificationValue: Int = try dictionary.value(for: CodingKeys.justification) + guard let justification = TextJustification(rawValue: justificationValue) else { + throw InitializableError.invalidInput() + } + self.justification = justification + tracking = try dictionary.value(for: CodingKeys.tracking) + lineHeight = try dictionary.value(for: CodingKeys.lineHeight) + baseline = try dictionary.value(for: CodingKeys.baseline) + if let fillColorRawValue = dictionary[CodingKeys.fillColorData.rawValue] { + fillColorData = try? LottieColor(value: fillColorRawValue) + } else { + fillColorData = nil + } + if let strokeColorRawValue = dictionary[CodingKeys.strokeColorData.rawValue] { + strokeColorData = try? LottieColor(value: strokeColorRawValue) + } else { + strokeColorData = nil + } + strokeWidth = try? dictionary.value(for: CodingKeys.strokeWidth) + strokeOverFill = try? dictionary.value(for: CodingKeys.strokeOverFill) + if let textFramePositionRawValue = dictionary[CodingKeys.textFramePosition.rawValue] { + textFramePosition = try? LottieVector3D(value: textFramePositionRawValue) + } else { + textFramePosition = nil + } + if let textFrameSizeRawValue = dictionary[CodingKeys.textFrameSize.rawValue] { + textFrameSize = try? LottieVector3D(value: textFrameSizeRawValue) + } else { + textFrameSize = nil + } + } + + convenience init(value: Any) throws { + guard let dictionary = value as? [String: Any] else { + throw InitializableError.invalidInput() + } + try self.init(dictionary: dictionary) + } + + // MARK: Internal + + /// The Text + let text: String + + /// The Font size + let fontSize: Double + + /// The Font Family + let fontFamily: String + + /// Justification + let justification: TextJustification + + /// Tracking + let tracking: Int + + /// Line Height + let lineHeight: Double + + /// Baseline + let baseline: Double? + + /// Fill Color data + let fillColorData: LottieColor? + + /// Scroke Color data + let strokeColorData: LottieColor? + + /// Stroke Width + let strokeWidth: Double? + + /// Stroke Over Fill + let strokeOverFill: Bool? + + let textFramePosition: LottieVector3D? + + let textFrameSize: LottieVector3D? + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case text = "t" + case fontSize = "s" + case fontFamily = "f" + case justification = "j" + case tracking = "tr" + case lineHeight = "lh" + case baseline = "ls" + case fillColorData = "fc" + case strokeColorData = "sc" + case strokeWidth = "sw" + case strokeOverFill = "of" + case textFramePosition = "ps" + case textFrameSize = "sz" + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/RootAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/RootAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..aa1854162c13c022a748a0ef18062c1268406b82 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/RootAnimationLayer.swift @@ -0,0 +1,53 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +// MARK: - RootAnimationLayer + +/// A root `CALayer` responsible for playing a Lottie animation +protocol RootAnimationLayer: CALayer { + var lottieAnimationLayer: LottieAnimationLayer? { get set } + + var currentFrame: AnimationFrameTime { get set } + var renderScale: CGFloat { get set } + var respectAnimationFrameRate: Bool { get set } + + var _animationLayers: [CALayer] { get } + var imageProvider: AnimationImageProvider { get set } + var textProvider: AnimationKeypathTextProvider { get set } + var fontProvider: AnimationFontProvider { get set } + + /// The `CAAnimation` key corresponding to the primary animation. + /// - `LottieAnimationView` uses this key to check if the animation is still active + var primaryAnimationKey: AnimationKey { get } + + /// Whether or not this layer is currently playing an animation + /// - If the layer returns `nil`, `LottieAnimationView` determines if an animation + /// is playing by checking if there is an active animation for `primaryAnimationKey` + var isAnimationPlaying: Bool? { get } + + /// Instructs this layer to remove all `CAAnimation`s, + /// other than the `CAAnimation` managed by `LottieAnimationView` (if applicable) + func removeAnimations() + + func reloadImages() + func forceDisplayUpdate() + func logHierarchyKeypaths() + func allHierarchyKeypaths() -> [String] + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) + func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? + func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? + + func layer(for keypath: AnimationKeypath) -> CALayer? + func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? +} + +// MARK: - AnimationKey + +enum AnimationKey { + /// The primary animation and its key should be managed by `LottieAnimationView` + case managed + /// The primary animation always uses the given key + case specific(String) +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/AnimatorNodeDebugging.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/AnimatorNodeDebugging.swift new file mode 100644 index 0000000000000000000000000000000000000000..79b7f5c04f251aaf41284c8b2914c242d1ae7dac --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/AnimatorNodeDebugging.swift @@ -0,0 +1,23 @@ +// +// AnimatorNodeDebugging.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +extension AnimatorNode { + + func printNodeTree() { + parentNode?.printNodeTree() + LottieLogger.shared.info(String(describing: type(of: self))) + + if let group = self as? GroupNode { + LottieLogger.shared.info("* |Children") + group.rootNode?.printNodeTree() + LottieLogger.shared.info("*") + } else { + LottieLogger.shared.info("|") + } + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/LayerDebugging.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/LayerDebugging.swift new file mode 100644 index 0000000000000000000000000000000000000000..601ec870f7bd84faecac8b1fc8b315e3f227241e --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/LayerDebugging.swift @@ -0,0 +1,222 @@ +// +// LayerDebugging.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/24/19. +// + +import QuartzCore + +// MARK: - LayerDebugStyle + +struct LayerDebugStyle { + let anchorColor: CGColor + let boundsColor: CGColor + let anchorWidth: CGFloat + let boundsWidth: CGFloat +} + +// MARK: - LayerDebugging + +protocol LayerDebugging { + var debugStyle: LayerDebugStyle { get } +} + +// MARK: - CustomLayerDebugging + +protocol CustomLayerDebugging { + func layerForDebugging() -> CALayer +} + +// MARK: - DebugLayer + +class DebugLayer: CALayer { + init(style: LayerDebugStyle) { + super.init() + zPosition = 1000 + bounds = CGRect(x: 0, y: 0, width: style.anchorWidth, height: style.anchorWidth) + backgroundColor = style.anchorColor + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension CALayer { + + @nonobjc + public func logLayerTree(withIndent: Int = 0) { + var string = "" + for _ in 0...withIndent { + string = string + " " + } + string = string + "|_" + String(describing: self) + LottieLogger.shared.info(string) + if let sublayers { + for sublayer in sublayers { + sublayer.logLayerTree(withIndent: withIndent + 1) + } + } + } + +} + +// MARK: - CompositionLayer + CustomLayerDebugging + +extension CompositionLayer: CustomLayerDebugging { + func layerForDebugging() -> CALayer { + contentsLayer + } +} + +extension CALayer { + + @nonobjc + func setDebuggingState(visible: Bool) { + var sublayers = sublayers + if let cust = self as? CustomLayerDebugging { + sublayers = cust.layerForDebugging().sublayers + } + + if let sublayers { + for i in 0.. LayerDebugStyle { + let anchorColor = CGColor.rgb(1, 0, 0) + let boundsColor = CGColor.rgb(1, 1, 0) + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func topLayerStyle() -> LayerDebugStyle { + let anchorColor = CGColor.rgba(1, 0.5, 0, 0) + let boundsColor = CGColor.rgb(0, 1, 0) + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func nullLayerStyle() -> LayerDebugStyle { + let anchorColor = CGColor.rgba(0, 0, 1, 0) + let boundsColor = CGColor.rgb(0, 1, 0) + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func shapeLayerStyle() -> LayerDebugStyle { + let anchorColor = CGColor.rgba(0, 1, 0, 0) + let boundsColor = CGColor.rgb(0, 1, 0) + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func shapeRenderLayerStyle() -> LayerDebugStyle { + let anchorColor = CGColor.rgba(0, 1, 1, 0) + let boundsColor = CGColor.rgb(0, 1, 0) + + return LayerDebugStyle( + anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } +} + +extension [LayerModel] { + + var parents: [Int] { + var array = [Int]() + for layer in self { + if let parent = layer.parent { + array.append(parent) + } else { + array.append(-1) + } + } + return array + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/TestHelpers.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/TestHelpers.swift new file mode 100644 index 0000000000000000000000000000000000000000..cfe5d4813e7fb078d923392934609663e5cc9786 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Debugging/TestHelpers.swift @@ -0,0 +1,10 @@ +// Created by Cal Stephens on 1/28/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +enum TestHelpers { + /// Whether or not snapshot tests are currently running in a test target + static var snapshotTestsAreRunning = false + + /// Whether or not performance tests are currently running in a test target + static var performanceTestsAreRunning = false +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/AnimationKeypathExtension.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/AnimationKeypathExtension.swift new file mode 100644 index 0000000000000000000000000000000000000000..f79920e3e66256fc61c5d7f0df37d6e58d55f6d2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/AnimationKeypathExtension.swift @@ -0,0 +1,303 @@ +// +// KeypathSearchableExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import QuartzCore + +extension KeypathSearchable { + + func animatorNodes(for keyPath: AnimationKeypath) -> [AnimatorNode]? { + // Make sure there is a current key path. + guard let currentKey = keyPath.currentKey else { return nil } + + // Now try popping the keypath for wildcard / child search + guard let nextKeypath = keyPath.popKey(keypathName) else { + // We may be on the final keypath. Check for match. + if + let node = self as? AnimatorNode, + currentKey.equalsKeypath(keypathName) + { + // This is the final keypath and matches self. Return.s + return [node] + } + /// Nope. Stop Search + return nil + } + + var results: [AnimatorNode] = [] + + if + let node = self as? AnimatorNode, + nextKeypath.currentKey == nil + { + // Keypath matched self and was the final keypath. + results.append(node) + } + + for childNode in childKeypaths { + // Check if the child has any nodes matching the next keypath. + if let foundNodes = childNode.animatorNodes(for: nextKeypath) { + results.append(contentsOf: foundNodes) + } + + // In this case the current key is fuzzy, and both child and self match the next keyname. Keep digging! + if + currentKey.keyPathType == .fuzzyWildcard, + let nextKeypath = keyPath.nextKeypath, + nextKeypath.equalsKeypath(childNode.keypathName), + let foundNodes = childNode.animatorNodes(for: keyPath) + { + results.append(contentsOf: foundNodes) + } + } + + guard results.count > 0 else { + return nil + } + + return results + } + + func nodeProperties(for keyPath: AnimationKeypath) -> [AnyNodeProperty]? { + guard let nextKeypath = keyPath.popKey(keypathName) else { + /// Nope. Stop Search + return nil + } + + /// Keypath matches in some way. Continue the search. + var results: [AnyNodeProperty] = [] + + /// Check if we have a property keypath yet + if + let propertyKey = nextKeypath.propertyKey, + let property = keypathProperties[propertyKey] + { + /// We found a property! + results.append(property) + } + + if nextKeypath.nextKeypath != nil { + /// Now check child keypaths. + for child in childKeypaths { + if let childProperties = child.nodeProperties(for: nextKeypath) { + results.append(contentsOf: childProperties) + } + } + } + + guard results.count > 0 else { + return nil + } + + return results + } + + func layer(for keyPath: AnimationKeypath) -> CALayer? { + if keyPath.nextKeypath == nil, let layerKey = keyPath.currentKey, layerKey.equalsKeypath(keypathName) { + /// We found our layer! + return keypathLayer + } + guard let nextKeypath = keyPath.popKey(keypathName) else { + /// Nope. Stop Search + return nil + } + + /// Now check child keypaths. + for child in childKeypaths { + if let layer = child.layer(for: nextKeypath) { + return layer + } + } + return nil + } + + /// Searches this layer's keypaths to find the keypath for the given layer + func keypath(for layer: CALayer) -> AnimationKeypath? { + let allKeypaths = layerKeypaths() + return allKeypaths[layer] + } + + /// Computes the list of animation keypaths that descend from this layer + func allKeypaths(for keyPath: AnimationKeypath? = nil) -> [String] { + var allKeypaths: [String] = [] + + let newKeypath: AnimationKeypath = + if let previousKeypath = keyPath { + previousKeypath.appendingKey(keypathName) + } else { + AnimationKeypath(keys: [keypathName]) + } + + allKeypaths.append(newKeypath.fullPath) + + for key in keypathProperties.keys { + allKeypaths.append(newKeypath.appendingKey(key).fullPath) + } + + for child in childKeypaths { + allKeypaths.append(contentsOf: child.allKeypaths(for: newKeypath)) + } + + return allKeypaths + } + + /// Computes the list of animation keypaths that descend from this layer + func layerKeypaths(for keyPath: AnimationKeypath? = nil) -> [CALayer: AnimationKeypath] { + var allKeypaths: [CALayer: AnimationKeypath] = [:] + + let newKeypath: AnimationKeypath = + if let previousKeypath = keyPath { + previousKeypath.appendingKey(keypathName) + } else { + AnimationKeypath(keys: [keypathName]) + } + + if let layer = self as? CALayer { + allKeypaths[layer] = newKeypath + } + + for child in childKeypaths { + for (layer, keypath) in child.layerKeypaths(for: newKeypath) { + allKeypaths[layer] = keypath + } + } + + return allKeypaths + } +} + +extension AnimationKeypath { + var currentKey: String? { + keys.first + } + + var nextKeypath: String? { + guard keys.count > 1 else { + return nil + } + return keys[1] + } + + var propertyKey: String? { + if nextKeypath == nil { + /// There are no more keypaths. This is a property key. + return currentKey + } + if keys.count == 2, currentKey?.keyPathType == .fuzzyWildcard { + /// The next keypath is the last and the current is a fuzzy key. + return nextKeypath + } + return nil + } + + var fullPath: String { + keys.joined(separator: ".") + } + + /// Pops the top keypath from the stack if the keyname matches. + func popKey(_ keyname: String) -> AnimationKeypath? { + guard + let currentKey, + currentKey.equalsKeypath(keyname), + keys.count > 1 + else { + // Current key either doesnt match or we are on the last key. + return nil + } + + // Pop the keypath from the stack and return the new stack. + let newKeys: [String] + + if currentKey.keyPathType == .fuzzyWildcard { + /// Dont remove if current key is a fuzzy wildcard, and if the next keypath doesnt equal keypathname + if + let nextKeypath, + nextKeypath.equalsKeypath(keyname) + { + /// Remove next two keypaths. This keypath breaks the wildcard. + var oldKeys = keys + oldKeys.remove(at: 0) + oldKeys.remove(at: 0) + newKeys = oldKeys + } else { + newKeys = keys + } + } else { + var oldKeys = keys + oldKeys.remove(at: 0) + newKeys = oldKeys + } + + return AnimationKeypath(keys: newKeys) + } + + func appendingKey(_ key: String) -> AnimationKeypath { + var newKeys = keys + newKeys.append(key) + return AnimationKeypath(keys: newKeys) + } +} + +extension String { + var keyPathType: KeyType { + switch self { + case "*": + .wildcard + case "**": + .fuzzyWildcard + default: + .specific + } + } + + func equalsKeypath(_ keyname: String) -> Bool { + if keyPathType == .wildcard || keyPathType == .fuzzyWildcard { + return true + } + if self == keyname { + return true + } + if let index = firstIndex(of: "*") { + // Wildcard search. + let prefix = String(prefix(upTo: index)) + let suffix = String(suffix(from: self.index(after: index))) + + if prefix.count > 0 { + // Match prefix. + if keyname.count < prefix.count { + return false + } + let testPrefix = String(keyname.prefix(upTo: keyname.index(keyname.startIndex, offsetBy: prefix.count))) + if testPrefix != prefix { + // Prefix doesnt match + return false + } + } + if suffix.count > 0 { + // Match suffix. + if keyname.count < suffix.count { + // Suffix doesnt match + return false + } + let index = keyname.index(keyname.endIndex, offsetBy: -suffix.count) + let testSuffix = String(keyname.suffix(from: index)) + if testSuffix != suffix { + return false + } + } + return true + } + return false + } +} + +// MARK: - KeyType + +enum KeyType { + case specific + case wildcard + case fuzzyWildcard +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/BlendMode+Filter.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/BlendMode+Filter.swift new file mode 100644 index 0000000000000000000000000000000000000000..2d7ad22167fcff09bb276b8a522b4dc159d62042 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/BlendMode+Filter.swift @@ -0,0 +1,31 @@ +// +// File.swift +// +// +// Created by Denis Koryttsev on 10.05.2022. +// + +extension BlendMode { + /// The Core Image filter name for this `BlendMode`, that can be applied to a `CALayer`'s `compositingFilter`. + /// Supported compositing filters are defined here: https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/uid/TP30000136-SW71 + var filterName: String? { + switch self { + case .normal: nil + case .multiply: "multiplyBlendMode" + case .screen: "screenBlendMode" + case .overlay: "overlayBlendMode" + case .darken: "darkenBlendMode" + case .lighten: "lightenBlendMode" + case .colorDodge: "colorDodgeBlendMode" + case .colorBurn: "colorBurnBlendMode" + case .hardLight: "hardLightBlendMode" + case .softLight: "softLightBlendMode" + case .difference: "differenceBlendMode" + case .exclusion: "exclusionBlendMode" + case .hue: "hueBlendMode" + case .saturation: "saturationBlendMode" + case .color: "colorBlendMode" + case .luminosity: "luminosityBlendMode" + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/CGColor+RGB.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/CGColor+RGB.swift new file mode 100644 index 0000000000000000000000000000000000000000..b0c7f08f8f2e89eaed52f2170e65a6f76deacbc1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/CGColor+RGB.swift @@ -0,0 +1,43 @@ +// Created by Cal Stephens on 1/7/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import QuartzCore + +extension CGColor { + /// Retrieves the red, green, and blue color values from this `CGColor` + var rgb: (red: CGFloat, green: CGFloat, blue: CGFloat)? { + guard let components else { return nil } + + switch numberOfComponents { + case 2: + return (red: components[0], green: components[0], blue: components[0]) + + case 3, 4: + return (red: components[0], green: components[1], blue: components[2]) + + default: + // Unsupported conversion + return nil + } + } + + /// Initializes a `CGColor` using the given `RGB` values + static func rgb(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat) -> CGColor { + rgba(red, green, blue, 1.0) + } + + /// Initializes a `CGColor` using the given grayscale value + static func gray(_ gray: CGFloat) -> CGColor { + CGColor( + colorSpace: CGColorSpaceCreateDeviceGray(), + components: [gray, 1.0])! + } + + /// Initializes a `CGColor` using the given `RGBA` values + static func rgba(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat) -> CGColor { + CGColor( + colorSpace: LottieConfiguration.shared.colorSpace, + components: [red, green, blue, alpha])! + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/CGFloatExtensions.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/CGFloatExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..28f0e29bf3021c13dd584842b309f75b3d183ba3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/CGFloatExtensions.swift @@ -0,0 +1,152 @@ +// +// CGFloatExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import QuartzCore + +extension CGFloat { + + // MARK: Internal + + var squared: CGFloat { + self * self + } + + var cubed: CGFloat { + self * self * self + } + + var cubicRoot: CGFloat { + CGFloat(pow(Double(self), 1.0 / 3.0)) + } + + func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool { + from <= self && self <= to + } + + func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool { + from < self && self < to + } + + func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat { + var t: CGFloat + if self == P0.x { + // Handle corner cases explicitly to prevent rounding errors + t = 0 + } else if self == P3.x { + t = 1 + } else { + // Calculate t + let a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x + let b = 3 * P0.x - 6 * P1.x + 3 * P2.x + let c = -3 * P0.x + 3 * P1.x + let d = P0.x - self + let tTemp = CGFloat.SolveCubic(a, b, c, d) + if tTemp == -1 { + return -1 + } + t = tTemp + } + + // Calculate y from t + return (1 - t).cubed * P0.y + 3 * t * (1 - t).squared * P1.y + 3 * t.squared * (1 - t) * P2.y + t.cubed * P3.y + } + + func cubicBezier(_ t: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat { + let t_ = (1.0 - t) + let tt_ = t_ * t_ + let ttt_ = t_ * t_ * t_ + let tt = t * t + let ttt = t * t * t + + return self * ttt_ + + 3.0 * c1 * tt_ * t + + 3.0 * c2 * t_ * tt + + end * ttt + } + + // MARK: Fileprivate + + fileprivate static func SolveQuadratic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat { + var result = (-b + sqrt(b.squared - 4 * a * c)) / (2 * a) + guard !result.isInRangeOrEqual(0, 1) else { + return result + } + + result = (-b - sqrt(b.squared - 4 * a * c)) / (2 * a) + guard !result.isInRangeOrEqual(0, 1) else { + return result + } + + return -1 + } + + fileprivate static func SolveCubic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat, _ d: CGFloat) -> CGFloat { + if a == 0 { + return SolveQuadratic(b, c, d) + } + if d == 0 { + return 0 + } + let a = a + var b = b + var c = c + var d = d + b /= a + c /= a + d /= a + var q = (3.0 * c - b.squared) / 9.0 + let r = (-27.0 * d + b * (9.0 * c - 2.0 * b.squared)) / 54.0 + let disc = q.cubed + r.squared + let term1 = b / 3.0 + + if disc > 0 { + var s = r + sqrt(disc) + s = (s < 0) ? -((-s).cubicRoot) : s.cubicRoot + var t = r - sqrt(disc) + t = (t < 0) ? -((-t).cubicRoot) : t.cubicRoot + + let result = -term1 + s + t + if result.isInRangeOrEqual(0, 1) { + return result + } + } else if disc == 0 { + let r13 = (r < 0) ? -((-r).cubicRoot) : r.cubicRoot + + var result = -term1 + 2.0 * r13 + if result.isInRangeOrEqual(0, 1) { + return result + } + + result = -(r13 + term1) + if result.isInRangeOrEqual(0, 1) { + return result + } + + } else { + q = -q + var dum1 = q * q * q + dum1 = acos(r / sqrt(dum1)) + let r13 = 2.0 * sqrt(q) + + var result = -term1 + r13 * cos(dum1 / 3.0) + if result.isInRangeOrEqual(0, 1) { + return result + } + result = -term1 + r13 * cos((dum1 + 2.0 * .pi) / 3.0) + if result.isInRangeOrEqual(0, 1) { + return result + } + result = -term1 + r13 * cos((dum1 + 4.0 * .pi) / 3.0) + if result.isInRangeOrEqual(0, 1) { + return result + } + } + + return -1 + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/DataExtension.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/DataExtension.swift new file mode 100644 index 0000000000000000000000000000000000000000..de25a5eca28eeac2a91a53e2f812c8e3007eddfa --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/DataExtension.swift @@ -0,0 +1,33 @@ +// +// DataExtension.swift +// Lottie +// +// Created by René Fouquet on 03.05.21. +// + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +extension Data { + + init(assetName: String, in bundle: Bundle) throws { + #if canImport(UIKit) + if let asset = NSDataAsset(name: assetName, bundle: bundle) { + self = asset.data + return + } else { + throw DotLottieError.assetNotFound(name: assetName, bundle: bundle) + } + #else + if let asset = NSDataAsset(name: assetName, bundle: bundle) { + self = asset.data + return + } else { + throw DotLottieError.assetNotFound(name: assetName, bundle: bundle) + } + #endif + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/MathKit.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/MathKit.swift new file mode 100644 index 0000000000000000000000000000000000000000..12db23e71ca253b771cc27bd60bf4db208035ed4 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/MathKit.swift @@ -0,0 +1,443 @@ +// +// MathKit.swift +// UIToolBox +// +// Created by Brandon Withrow on 10/10/18. +// +// From https://github.com/buba447/UIToolBox + +import CoreGraphics +import Foundation + +extension Int { + var cgFloat: CGFloat { + CGFloat(self) + } +} + +extension Double { + var cgFloat: CGFloat { + CGFloat(self) + } +} + +// MARK: - CGFloat + Interpolatable + +extension CGFloat { + + func remap(fromLow: CGFloat, fromHigh: CGFloat, toLow: CGFloat, toHigh: CGFloat) -> CGFloat { + guard (fromHigh - fromLow) != 0 else { + // Would produce NAN + return 0 + } + return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + } + + /// Returns the difference between the receiver and the given number. + /// - Parameter absolute: If *true* (Default) the returned value will always be positive. + func diff(_ a: CGFloat, absolute: Bool = true) -> CGFloat { + absolute ? abs(a - self) : a - self + } + + func toRadians() -> CGFloat { self * .pi / 180 } + func toDegrees() -> CGFloat { self * 180 / .pi } + +} + +// MARK: - Double + +extension Double { + func remap(fromLow: Double, fromHigh: Double, toLow: Double, toHigh: Double) -> Double { + toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + } +} + +extension Numeric where Self: Comparable { + /// Returns a value that is clamped between the two numbers + /// + /// 1. The order of arguments does not matter. + func clamp(_ a: Self, _ b: Self) -> Self { + let minValue = a <= b ? a : b + let maxValue = a <= b ? b : a + return max(min(self, maxValue), minValue) + } +} + +extension CGRect { + + // MARK: Lifecycle + + /// Initializes a new CGRect with a center point and size. + init(center: CGPoint, size: CGSize) { + self.init( + x: center.x - (size.width * 0.5), + y: center.y - (size.height * 0.5), + width: size.width, + height: size.height) + } + + // MARK: Internal + + /// Returns the total area of the rect. + var area: CGFloat { + width * height + } + + /// The center point of the rect. Settable. + var center: CGPoint { + get { + CGPoint(x: midX, y: midY) + } + set { + origin = CGPoint( + x: newValue.x - (size.width * 0.5), + y: newValue.y - (size.height * 0.5)) + } + } + + /// The top left point of the rect. Settable. + var topLeft: CGPoint { + get { + CGPoint(x: minX, y: minY) + } + set { + origin = CGPoint( + x: newValue.x, + y: newValue.y) + } + } + + /// The bottom left point of the rect. Settable. + var bottomLeft: CGPoint { + get { + CGPoint(x: minX, y: maxY) + } + set { + origin = CGPoint( + x: newValue.x, + y: newValue.y - size.height) + } + } + + /// The top right point of the rect. Settable. + var topRight: CGPoint { + get { + CGPoint(x: maxX, y: minY) + } + set { + origin = CGPoint( + x: newValue.x - size.width, + y: newValue.y) + } + } + + /// The bottom right point of the rect. Settable. + var bottomRight: CGPoint { + get { + CGPoint(x: maxX, y: maxY) + } + set { + origin = CGPoint( + x: newValue.x - size.width, + y: newValue.y - size.height) + } + } + +} + +extension CGSize { + + /// Operator convenience to add sizes with + + static func +(left: CGSize, right: CGSize) -> CGSize { + left.add(right) + } + + /// Operator convenience to subtract sizes with - + static func -(left: CGSize, right: CGSize) -> CGSize { + left.subtract(right) + } + + /// Operator convenience to multiply sizes with * + static func *(left: CGSize, right: CGFloat) -> CGSize { + CGSize(width: left.width * right, height: left.height * right) + } + + /// Returns the scale float that will fit the receive inside of the given size. + func scaleThatFits(_ size: CGSize) -> CGFloat { + CGFloat.minimum(width / size.width, height / size.height) + } + + /// Adds receiver size to give size. + func add(_ size: CGSize) -> CGSize { + CGSize(width: width + size.width, height: height + size.height) + } + + /// Subtracts given size from receiver size. + func subtract(_ size: CGSize) -> CGSize { + CGSize(width: width - size.width, height: height - size.height) + } + + /// Multiplies receiver size by the given size. + func multiply(_ size: CGSize) -> CGSize { + CGSize(width: width * size.width, height: height * size.height) + } +} + +// MARK: - CGLine + +/// A struct that defines a line segment with two CGPoints +struct CGLine { + + // MARK: Lifecycle + + /// Initializes a line segment with start and end points + init(start: CGPoint, end: CGPoint) { + self.start = start + self.end = end + } + + // MARK: Internal + + /// The Start of the line segment. + var start: CGPoint + /// The End of the line segment. + var end: CGPoint + + /// The length of the line segment. + var length: CGFloat { + end.distanceTo(start) + } + + /// Returns a line segment that is normalized to a length of 1 + func normalize() -> CGLine { + let len = length + guard len > 0 else { + return self + } + let relativeEnd = end - start + let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) + let absoluteVector = relativeVector + start + return CGLine(start: start, end: absoluteVector) + } + + /// Trims a line segment to the given length + func trimmedToLength(_ toLength: CGFloat) -> CGLine { + let len = length + guard len > 0 else { + return self + } + let relativeEnd = end - start + let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) + let sizedVector = CGPoint(x: relativeVector.x * toLength, y: relativeVector.y * toLength) + let absoluteVector = sizedVector + start + return CGLine(start: start, end: absoluteVector) + } + + /// Flips a line vertically and horizontally from the start point. + func flipped() -> CGLine { + let relativeEnd = end - start + let flippedEnd = CGPoint(x: relativeEnd.x * -1, y: relativeEnd.y * -1) + return CGLine(start: start, end: flippedEnd + start) + } + + /// Move the line to the new start point. + func transpose(_ toPoint: CGPoint) -> CGLine { + let diff = toPoint - start + let newEnd = end + diff + return CGLine(start: toPoint, end: newEnd) + } + +} + +infix operator +| +infix operator +- + +extension CGPoint { + + /// Returns the length between the receiver and *CGPoint.zero* + var vectorLength: CGFloat { + distanceTo(.zero) + } + + var isZero: Bool { + x == 0 && y == 0 + } + + /// Operator convenience to divide points with / + static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { + CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) + } + + /// Operator convenience to multiply points with * + static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { + CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) + } + + /// Operator convenience to add points with + + static func +(left: CGPoint, right: CGPoint) -> CGPoint { + left.add(right) + } + + /// Operator convenience to subtract points with - + static func -(left: CGPoint, right: CGPoint) -> CGPoint { + left.subtract(right) + } + + static func +|(left: CGPoint, right: CGFloat) -> CGPoint { + CGPoint(x: left.x, y: left.y + right) + } + + static func +-(left: CGPoint, right: CGFloat) -> CGPoint { + CGPoint(x: left.x + right, y: left.y) + } + + /// Returns the distance between the receiver and the given point. + func distanceTo(_ a: CGPoint) -> CGFloat { + let xDist = a.x - x + let yDist = a.y - y + return CGFloat(sqrt((xDist * xDist) + (yDist * yDist))) + } + + func rounded(decimal: CGFloat) -> CGPoint { + CGPoint(x: round(decimal * x) / decimal, y: round(decimal * y) / decimal) + } + + func interpolate( + _ to: CGPoint, + outTangent: CGPoint, + inTangent: CGPoint, + amount: CGFloat, + maxIterations: Int = 3, + samples: Int = 20, + accuracy: CGFloat = 1) + -> CGPoint + { + if amount == 0 { + return self + } + if amount == 1 { + return to + } + + if + colinear(outTangent, inTangent) == true, + outTangent.colinear(inTangent, to) == true + { + return interpolate(to: to, amount: amount) + } + + let step = 1 / CGFloat(samples) + + var points: [(point: CGPoint, distance: CGFloat)] = [(point: self, distance: 0)] + var totalLength: CGFloat = 0 + + var previousPoint = self + var previousAmount = CGFloat(0) + + var closestPoint = 0 + + while previousAmount < 1 { + previousAmount = previousAmount + step + + if previousAmount < amount { + closestPoint = closestPoint + 1 + } + + let newPoint = pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: previousAmount) + let distance = previousPoint.distanceTo(newPoint) + totalLength = totalLength + distance + points.append((point: newPoint, distance: totalLength)) + previousPoint = newPoint + } + + let accurateDistance = amount * totalLength + var point = points[closestPoint] + + var foundPoint = false + + var pointAmount = CGFloat(closestPoint) * step + var nextPointAmount: CGFloat = pointAmount + step + + var refineIterations = 0 + while foundPoint == false { + refineIterations = refineIterations + 1 + /// First see if the next point is still less than the projected length. + let nextPoint = points[min(closestPoint + 1, points.indices.last!)] + if nextPoint.distance < accurateDistance { + point = nextPoint + closestPoint = closestPoint + 1 + pointAmount = CGFloat(closestPoint) * step + nextPointAmount = pointAmount + step + if closestPoint == points.count { + foundPoint = true + } + continue + } + if accurateDistance < point.distance { + closestPoint = closestPoint - 1 + if closestPoint < 0 { + foundPoint = true + continue + } + point = points[closestPoint] + pointAmount = CGFloat(closestPoint) * step + nextPointAmount = pointAmount + step + continue + } + + /// Now we are certain the point is the closest point under the distance + let pointDiff = nextPoint.distance - point.distance + let proposedPointAmount = ((accurateDistance - point.distance) / pointDiff) + .remap(fromLow: 0, fromHigh: 1, toLow: pointAmount, toHigh: nextPointAmount) + + let newPoint = pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: proposedPointAmount) + let newDistance = point.distance + point.point.distanceTo(newPoint) + pointAmount = proposedPointAmount + point = (point: newPoint, distance: newDistance) + if + accurateDistance - newDistance <= accuracy || + newDistance - accurateDistance <= accuracy + { + foundPoint = true + } + + if refineIterations == maxIterations { + foundPoint = true + } + } + return point.point + } + + func pointOnPath(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat) -> CGPoint { + let a = interpolate(to: outTangent, amount: amount) + let b = outTangent.interpolate(to: inTangent, amount: amount) + let c = inTangent.interpolate(to: to, amount: amount) + let d = a.interpolate(to: b, amount: amount) + let e = b.interpolate(to: c, amount: amount) + let f = d.interpolate(to: e, amount: amount) + return f + } + + func colinear(_ a: CGPoint, _ b: CGPoint) -> Bool { + let area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y) + let accuracy: CGFloat = 0.05 + if area < accuracy, area > -accuracy { + return true + } + return false + } + + /// Subtracts the given point from the receiving point. + func subtract(_ point: CGPoint) -> CGPoint { + CGPoint( + x: x - point.x, + y: y - point.y) + } + + /// Adds the given point from the receiving point. + func add(_ point: CGPoint) -> CGPoint { + CGPoint( + x: x + point.x, + y: y + point.y) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/StringExtensions.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/StringExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..8d33cee2499b7598a4281cb795621b093270a696 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Extensions/StringExtensions.swift @@ -0,0 +1,43 @@ +// +// StringExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import CoreGraphics +import Foundation + +extension String { + + var cgColor: CGColor { + let (red, green, blue) = hexColorComponents() + return .rgb(red, green, blue) + } + + var lottieColor: LottieColor { + let (red, green, blue) = hexColorComponents() + return .init(r: red, g: green, b: blue, a: 1.0) + } + + func hexColorComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat) { + var cString: String = trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if cString.hasPrefix("#") { + cString.remove(at: cString.startIndex) + } + + if (cString.count) != 6 { + return (red: 0, green: 0, blue: 0) + } + + var rgbValue: UInt64 = 0 + Scanner(string: cString).scanHexInt64(&rgbValue) + + return ( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/AnimationContext.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/AnimationContext.swift new file mode 100644 index 0000000000000000000000000000000000000000..8d6cba046c3bcbf8fdac192380a71b781386e197 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/AnimationContext.swift @@ -0,0 +1,92 @@ +// +// AnimationContext.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +import Foundation +import QuartzCore + +/// A completion block for animations. +/// - `true` is passed in if the animation completed playing. +/// - `false` is passed in if the animation was interrupted and did not complete playing. +public typealias LottieCompletionBlock = (_ completed: Bool) -> Void + +// MARK: - AnimationContext + +struct AnimationContext { + + init( + playFrom: AnimationFrameTime, + playTo: AnimationFrameTime, + closure: LottieCompletionBlock?) + { + self.playTo = playTo + self.playFrom = playFrom + self.closure = AnimationCompletionDelegate(completionBlock: closure) + } + + var playFrom: AnimationFrameTime + var playTo: AnimationFrameTime + var closure: AnimationCompletionDelegate + +} + +// MARK: Equatable + +extension AnimationContext: Equatable { + /// Whether or not the two given `AnimationContext`s are functionally equivalent + /// - This checks whether or not a completion handler was provided, + /// but does not check whether or not the two completion handlers are equivalent. + static func == (_ lhs: AnimationContext, _ rhs: AnimationContext) -> Bool { + lhs.playTo == rhs.playTo + && lhs.playFrom == rhs.playFrom + && (lhs.closure.completionBlock == nil) == (rhs.closure.completionBlock == nil) + } +} + +// MARK: - AnimationContextState + +enum AnimationContextState { + case playing + case cancelled + case complete +} + +// MARK: - AnimationCompletionDelegate + +class AnimationCompletionDelegate: NSObject, CAAnimationDelegate { + + // MARK: Lifecycle + + init(completionBlock: LottieCompletionBlock?) { + self.completionBlock = completionBlock + super.init() + } + + // MARK: Public + + public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + guard ignoreDelegate == false else { return } + animationState = flag ? .complete : .cancelled + if let animationLayer, let key = animationKey { + animationLayer.removeAnimation(forKey: key) + if flag { + animationLayer.currentFrame = (anim as! CABasicAnimation).toValue as! CGFloat + } + } + if let completionBlock { + completionBlock(flag) + } + } + + // MARK: Internal + + var animationLayer: RootAnimationLayer? + var animationKey: String? + var ignoreDelegate = false + var animationState: AnimationContextState = .playing + + let completionBlock: LottieCompletionBlock? +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/AnyEquatable.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/AnyEquatable.swift new file mode 100644 index 0000000000000000000000000000000000000000..e695eb8cee6073e10145b5ae7d5ee84a1a373a30 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/AnyEquatable.swift @@ -0,0 +1,22 @@ +// Created by miguel_jimenez on 8/2/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +// MARK: - AnyEquatable + +struct AnyEquatable { + private let value: Any + private let equals: (Any) -> Bool + + init(_ value: T) { + self.value = value + equals = { $0 as? T == value } + } +} + +// MARK: Equatable + +extension AnyEquatable: Equatable { + static func ==(lhs: AnyEquatable, rhs: AnyEquatable) -> Bool { + lhs.equals(rhs.value) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/Binding+Map.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/Binding+Map.swift new file mode 100644 index 0000000000000000000000000000000000000000..161c12a9f106efc849e5a54b72640925b2fa2aee --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/Binding+Map.swift @@ -0,0 +1,19 @@ +// Created by miguel_jimenez on 7/27/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +extension Binding { + + /// Helper to transform a `Binding` from one `Value` type to another. + func map(transform: @escaping (Value) -> Transformed) -> Binding { + .init { + transform(wrappedValue) + } set: { newValue in + guard let newValue = newValue as? Value else { return } + self.wrappedValue = newValue + } + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/View+ValueChanged.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/View+ValueChanged.swift new file mode 100644 index 0000000000000000000000000000000000000000..104029fafafc9d0c4c6e97196c7177dd0d1aafdb --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Helpers/View+ValueChanged.swift @@ -0,0 +1,35 @@ +// Created by miguel_jimenez on 7/26/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(Combine) && canImport(SwiftUI) +import Combine +import SwiftUI + +extension View { + /// A backwards compatible wrapper for iOS 14 `onChange` + @ViewBuilder + func valueChanged(value: T, onChange: @escaping (T) -> Void) -> some View { + #if compiler(>=5.9) + if #available(iOS 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, *) { + self.onChange(of: value) { _, newValue in + onChange(newValue) + } + } else if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + self.onChange(of: value, perform: onChange) + } else { + onReceive(Just(value)) { value in + onChange(value) + } + } + #else + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, *) { + self.onChange(of: value, perform: onChange) + } else { + onReceive(Just(value)) { value in + onChange(value) + } + } + #endif + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/InterpolatableExtensions.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/InterpolatableExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..7c7fc8b13ca3bcb3620571065f48aec477a58fe2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/InterpolatableExtensions.swift @@ -0,0 +1,140 @@ +// +// InterpolatableExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +extension LottieColor { + + // MARK: Lifecycle + + /// Initialize a new color with Hue Saturation and Value + init(h: Double, s: Double, v: Double, a: Double) { + let i = floor(h * 6) + let f = h * 6 - i + let p = v * (1 - s) + let q = v * (1 - f * s) + let t = v * (1 - (1 - f) * s) + + switch i.truncatingRemainder(dividingBy: 6) { + case 0: + r = v + g = t + b = p + + case 1: + r = q + g = v + b = p + + case 2: + r = p + g = v + b = t + + case 3: + r = p + g = q + b = v + + case 4: + r = t + g = p + b = v + + case 5: + r = v + g = p + b = q + + default: + r = 0 + g = 0 + b = 0 + } + self.a = a + } + + init(y: Double, u: Double, v: Double, a: Double) { + // From https://www.fourcc.org/fccyvrgb.php + r = y + 1.403 * v + g = y - 0.344 * u + b = y + 1.770 * u + self.a = a + } + + // MARK: Internal + + /// Hue Saturation Value of the color. + var hsva: (h: Double, s: Double, v: Double, a: Double) { + let maxValue = max(r, g, b) + let minValue = min(r, g, b) + + var h: Double, s: Double, v: Double = maxValue + + let d = maxValue - minValue + s = maxValue == 0 ? 0 : d / maxValue + + if maxValue == minValue { + h = 0 // achromatic + } else { + switch maxValue { + case r: h = (g - b) / d + (g < b ? 6 : 0) + case g: h = (b - r) / d + 2 + case b: h = (r - g) / d + 4 + default: h = maxValue + } + h = h / 6 + } + return (h: h, s: s, v: v, a: a) + } + + var yuv: (y: Double, u: Double, v: Double, a: Double) { + /// From https://www.fourcc.org/fccyvrgb.php + let y = 0.299 * r + 0.587 * g + 0.114 * b + let u = -0.14713 * r - 0.28886 * g + 0.436 * b + let v = 0.615 * r - 0.51499 * g - 0.10001 * b + return (y: y, u: u, v: v, a: a) + } + +} + +// MARK: - CurveVertex + Interpolatable + +extension CurveVertex: Interpolatable { + func interpolate(to: CurveVertex, amount: CGFloat) -> CurveVertex { + CurveVertex( + point: point.interpolate(to: to.point, amount: amount), + inTangent: inTangent.interpolate(to: to.inTangent, amount: amount), + outTangent: outTangent.interpolate(to: to.outTangent, amount: amount)) + } +} + +// MARK: - BezierPath + Interpolatable + +extension BezierPath: Interpolatable { + func interpolate(to: BezierPath, amount: CGFloat) -> BezierPath { + var newPath = BezierPath() + for i in 0.. TextDocument { + if amount == 1 { + return to + } + return self + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/KeyframeExtensions.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/KeyframeExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..e6e0c1811ff54bbc9b8877d7f70bc02cb39e42e3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/KeyframeExtensions.swift @@ -0,0 +1,46 @@ +// +// KeyframeExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +extension Keyframe where T: AnyInterpolatable { + func interpolate(to: Keyframe, progress: CGFloat) -> T { + value._interpolate( + to: to.value, + amount: progress, + spatialOutTangent: spatialOutTangent?.pointValue, + spatialInTangent: to.spatialInTangent?.pointValue) + } +} + +extension Keyframe { + /// Interpolates the keyTime into a value from 0-1 + func interpolatedProgress(_ to: Keyframe, keyTime: CGFloat) -> CGFloat { + let startTime = time + let endTime = to.time + if keyTime <= startTime { + return 0 + } + if endTime <= keyTime { + return 1 + } + + if isHold { + return 0 + } + + let outTanPoint = outTangent?.pointValue ?? .zero + let inTanPoint = to.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) + var progress: CGFloat = keyTime.remap(fromLow: startTime, fromHigh: endTime, toLow: 0, toHigh: 1) + if !outTanPoint.isZero || !inTanPoint.equalTo(CGPoint(x: 1, y: 1)) { + /// Cubic interpolation + progress = progress.cubicBezierInterpolate(.zero, outTanPoint, inTanPoint, CGPoint(x: 1, y: 1)) + } + return progress + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/KeyframeInterpolator.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/KeyframeInterpolator.swift new file mode 100644 index 0000000000000000000000000000000000000000..93ba7378ed105a578f439d5b592b58324bcf9ac9 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Interpolatable/KeyframeInterpolator.swift @@ -0,0 +1,250 @@ +// +// KeyframeInterpolator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/15/19. +// + +import CoreGraphics +import Foundation + +// MARK: - KeyframeInterpolator + +/// A value provider that produces a value at Time from a group of keyframes +final class KeyframeInterpolator: ValueProvider where ValueType: AnyInterpolatable { + + // MARK: Lifecycle + + init(keyframes: ContiguousArray>) { + self.keyframes = keyframes + } + + // MARK: Internal + + let keyframes: ContiguousArray> + + var valueType: Any.Type { + ValueType.self + } + + var storage: ValueProviderStorage { + .closure { [self] frame in + // First set the keyframe span for the frame. + updateSpanIndices(frame: frame) + lastUpdatedFrame = frame + // If only one keyframe return its value + let progress: CGFloat + let value: ValueType + + if + let leading = leadingKeyframe, + let trailing = trailingKeyframe + { + /// We have leading and trailing keyframe. + progress = leading.interpolatedProgress(trailing, keyTime: frame) + value = leading.interpolate(to: trailing, progress: progress) + } else if let leading = leadingKeyframe { + progress = 0 + value = leading.value + } else if let trailing = trailingKeyframe { + progress = 1 + value = trailing.value + } else { + /// Satisfy the compiler. + progress = 0 + value = keyframes[0].value + } + return value + } + } + + /// Returns true to trigger a frame update for this interpolator. + /// + /// An interpolator will be asked if it needs to update every frame. + /// If the interpolator needs updating it will be asked to compute its value for + /// the given frame. + /// + /// Cases a keyframe should not be updated: + /// - If time is in span and leading keyframe is hold + /// - If time is after the last keyframe. + /// - If time is before the first keyframe + /// + /// Cases for updating a keyframe: + /// - If time is in the span, and is not a hold + /// - If time is outside of the span, and there are more keyframes + /// - If a value delegate is set + /// - If leading and trailing are both nil. + func hasUpdate(frame: CGFloat) -> Bool { + if lastUpdatedFrame == nil { + return true + } + + if + let leading = leadingKeyframe, + trailingKeyframe == nil, + leading.time < frame + { + /// Frame is after bounds of keyframes + return false + } + if + let trailing = trailingKeyframe, + leadingKeyframe == nil, + frame < trailing.time + { + /// Frame is before bounds of keyframes + return false + } + if + let leading = leadingKeyframe, + let trailing = trailingKeyframe, + leading.isHold, + leading.time < frame, + frame < trailing.time + { + return false + } + return true + } + + // MARK: Fileprivate + + fileprivate var lastUpdatedFrame: CGFloat? + + fileprivate var leadingIndex: Int? = nil + fileprivate var trailingIndex: Int? = nil + fileprivate var leadingKeyframe: Keyframe? = nil + fileprivate var trailingKeyframe: Keyframe? = nil + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + fileprivate func updateSpanIndices(frame: CGFloat) { + guard keyframes.count > 0 else { + leadingIndex = nil + trailingIndex = nil + leadingKeyframe = nil + trailingKeyframe = nil + return + } + + // This function searches through the array to find the span of two keyframes + // that contain the current time. + // + // We could use Array.first(where:) but that would search through the entire array + // each frame. + // Instead we track the last used index and search either forwards or + // backwards from there. This reduces the iterations and complexity from + // + // O(n), where n is the length of the sequence to + // O(n), where n is the number of items after or before the last used index. + // + + if keyframes.count == 1 { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0 + trailingIndex = nil + leadingKeyframe = keyframes[0] + trailingKeyframe = nil + return + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if + leadingIndex == nil, + trailingIndex == nil + { + if frame < keyframes[0].time { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0 + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0 + trailingIndex = 1 + } + } + + if + let currentTrailing = trailingIndex, + keyframes[currentTrailing].time <= frame + { + /// Time is after the current span. Iterate forward. + var newLeading = currentTrailing + var keyframeFound = false + while !keyframeFound { + leadingIndex = newLeading + trailingIndex = keyframes.validIndex(newLeading + 1) + + guard let trailing = trailingIndex else { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true + continue + } + if frame < keyframes[trailing].time { + /// Keyframe in current span. + keyframeFound = true + continue + } + /// Advance the array. + newLeading = trailing + } + + } else if + let currentLeading = leadingIndex, + frame < keyframes[currentLeading].time + { + /// Time is before the current span. Iterate backwards + var newTrailing = currentLeading + + var keyframeFound = false + while !keyframeFound { + leadingIndex = keyframes.validIndex(newTrailing - 1) + trailingIndex = newTrailing + + guard let leading = leadingIndex else { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true + continue + } + if keyframes[leading].time <= frame { + /// Keyframe in current span. + keyframeFound = true + continue + } + /// Step back + newTrailing = leading + } + } + if let keyFrame = leadingIndex { + leadingKeyframe = keyframes[keyFrame] + } else { + leadingKeyframe = nil + } + + if let keyFrame = trailingIndex { + trailingKeyframe = keyframes[keyFrame] + } else { + trailingKeyframe = nil + } + } +} + +extension Array { + + fileprivate func validIndex(_ index: Int) -> Int? { + if 0 <= index, index < endIndex { + return index + } + return nil + } + +} + +extension ContiguousArray { + + fileprivate func validIndex(_ index: Int) -> Int? { + if 0 <= index, index < endIndex { + return index + } + return nil + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/LottieAnimationSource.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/LottieAnimationSource.swift new file mode 100644 index 0000000000000000000000000000000000000000..1e96491512312c85e3476e230c3bc4b524a99d62 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/LottieAnimationSource.swift @@ -0,0 +1,51 @@ +// Created by Cal Stephens on 7/26/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +// MARK: - LottieAnimationSource + +/// A data source for a Lottie animation. +/// Either a `LottieAnimation` loaded from a `.json` file, +/// or a `DotLottieFile` loaded from a `.lottie` file. +public enum LottieAnimationSource: Sendable { + /// A `LottieAnimation` loaded from a `.json` file + case lottieAnimation(LottieAnimation) + + /// A `DotLottieFile` loaded from a `.lottie` file + case dotLottieFile(DotLottieFile) +} + +extension LottieAnimationSource { + /// The default animation displayed by this data source + var animation: LottieAnimation? { + switch self { + case .lottieAnimation(let animation): + animation + case .dotLottieFile: + dotLottieAnimation?.animation + } + } + + /// The `DotLottieFile.Animation`, if this is a dotLottie animation + var dotLottieAnimation: DotLottieFile.Animation? { + switch self { + case .lottieAnimation: + nil + case .dotLottieFile(let dotLottieFile): + dotLottieFile.animation() + } + } +} + +extension LottieAnimation { + /// This animation represented as a `LottieAnimationSource` + public var animationSource: LottieAnimationSource { + .lottieAnimation(self) + } +} + +extension DotLottieFile { + /// This animation represented as a `LottieAnimationSource` + public var animationSource: LottieAnimationSource { + .dotLottieFile(self) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/BezierPath.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/BezierPath.swift new file mode 100644 index 0000000000000000000000000000000000000000..8183a47ea7f37263f98792ef95d9f3e13b4eabd8 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/BezierPath.swift @@ -0,0 +1,500 @@ +// +// Shape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import CoreGraphics + +// MARK: - BezierPath + +/// A container that holds instructions for creating a single, unbroken Bezier Path. +struct BezierPath { + + // MARK: Lifecycle + + /// Initializes a new Bezier Path. + init(startPoint: CurveVertex) { + elements = [PathElement(vertex: startPoint)] + length = 0 + closed = false + } + + init() { + elements = [] + length = 0 + closed = false + } + + // MARK: Internal + + /// The elements of the path + private(set) var elements: [PathElement] + + /// If the path is closed or not. + private(set) var closed: Bool + + /// The total length of the path. + private(set) var length: CGFloat + + mutating func moveToStartPoint(_ vertex: CurveVertex) { + elements = [PathElement(vertex: vertex)] + length = 0 + } + + mutating func addVertex(_ vertex: CurveVertex) { + guard let previous = elements.last else { + addElement(PathElement(vertex: vertex)) + return + } + addElement(previous.pathElementTo(vertex)) + } + + mutating func addCurve(toPoint: CGPoint, outTangent: CGPoint, inTangent: CGPoint) { + guard let previous = elements.last else { return } + let newVertex = CurveVertex(inTangent, toPoint, toPoint) + updateVertex( + CurveVertex(previous.vertex.inTangent, previous.vertex.point, outTangent), + atIndex: elements.endIndex - 1, + remeasure: false) + addVertex(newVertex) + } + + mutating func addLine(toPoint: CGPoint) { + guard let previous = elements.last else { return } + let newVertex = CurveVertex(point: toPoint, inTangentRelative: .zero, outTangentRelative: .zero) + updateVertex( + CurveVertex(previous.vertex.inTangent, previous.vertex.point, previous.vertex.point), + atIndex: elements.endIndex - 1, + remeasure: false) + addVertex(newVertex) + } + + mutating func close() { + closed = true + } + + mutating func addElement(_ pathElement: PathElement) { + elements.append(pathElement) + length = length + pathElement.length + } + + mutating func updateVertex(_ vertex: CurveVertex, atIndex: Int, remeasure: Bool) { + if remeasure { + var newElement: PathElement + if atIndex > 0 { + let previousElement = elements[atIndex - 1] + newElement = previousElement.pathElementTo(vertex) + } else { + newElement = PathElement(vertex: vertex) + } + elements[atIndex] = newElement + + if atIndex + 1 < elements.count { + let nextElement = elements[atIndex + 1] + elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex) + } + + } else { + let oldElement = elements[atIndex] + elements[atIndex] = oldElement.updateVertex(newVertex: vertex) + } + } + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + func trim(fromLength: CGFloat, toLength: CGFloat, offsetLength: CGFloat) -> [BezierPath] { + guard elements.count > 1 else { + return [] + } + + if fromLength == toLength { + return [] + } + + /// Normalize lengths to the curve length. + var start = (fromLength + offsetLength).truncatingRemainder(dividingBy: length) + var end = (toLength + offsetLength).truncatingRemainder(dividingBy: length) + + if start < 0 { + start = length + start + } + + if end < 0 { + end = length + end + } + + if start == length { + start = 0 + } + if end == 0 { + end = length + } + + if + start == 0 && end == length || + start == end || + start == length && end == 0 + { + /// The trim encompasses the entire path. Return. + return [self] + } + + if start > end { + // Start is greater than end. Two paths are returned. + return trimPathAtLengths(positions: [(start: 0, end: end), (start: start, end: length)]) + } + + return trimPathAtLengths(positions: [(start: start, end: end)]) + } + + // MARK: Private + + private func trimPathAtLengths(positions: [(start: CGFloat, end: CGFloat)]) -> [BezierPath] { + guard positions.count > 0 else { + return [] + } + var remainingPositions = positions + + var trim = remainingPositions.remove(at: 0) + + var paths = [BezierPath]() + + var runningLength: CGFloat = 0 + var finishedTrimming = false + var pathElements = elements + + var currentPath = BezierPath() + var i = 0 + + while !finishedTrimming { + if pathElements.count <= i { + /// Do this for rounding errors + paths.append(currentPath) + finishedTrimming = true + continue + } + /// Loop through and add elements within start->end range. + /// Get current element + let element = pathElements[i] + + /// Calculate new running length. + let newLength = runningLength + element.length + + if newLength < trim.start { + /// Element is not included in the trim, continue. + runningLength = newLength + i = i + 1 + /// Increment index, we are done with this element. + continue + } + + if newLength == trim.start { + /// Current element IS the start element. + /// For start we want to add a zero length element. + currentPath.moveToStartPoint(element.vertex) + runningLength = newLength + i = i + 1 + /// Increment index, we are done with this element. + continue + } + + if runningLength < trim.start, trim.start < newLength, currentPath.elements.count == 0 { + /// The start of the trim is between this element and the previous, trim. + /// Get previous element. + let previousElement = pathElements[i - 1] + /// Trim it + let trimLength = trim.start - runningLength + let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) + /// Add the right span start. + currentPath.moveToStartPoint(trimResults.rightSpan.start.vertex) + + pathElements[i] = trimResults.rightSpan.end + pathElements[i - 1] = trimResults.rightSpan.start + runningLength = runningLength + trimResults.leftSpan.end.length + /// Dont increment index or the current length, the end of this path can be within this span. + continue + } + + if trim.start < newLength, newLength < trim.end { + /// Element lies within the trim span. + currentPath.addElement(element) + runningLength = newLength + i = i + 1 + continue + } + + if newLength == trim.end { + /// Element is the end element. + /// The element could have a new length if it's added right after the start node. + currentPath.addElement(element) + /// We are done with this span. + runningLength = newLength + i = i + 1 + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + if runningLength < trim.end, trim.end < newLength { + /// New element must be cut for end. + /// Get previous element. + let previousElement = pathElements[i - 1] + /// Trim it + let trimLength = trim.end - runningLength + let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) + /// Add the left span end. + + currentPath.updateVertex(trimResults.leftSpan.start.vertex, atIndex: currentPath.elements.count - 1, remeasure: false) + currentPath.addElement(trimResults.leftSpan.end) + + pathElements[i] = trimResults.rightSpan.end + pathElements[i - 1] = trimResults.rightSpan.start + runningLength = runningLength + trimResults.leftSpan.end.length + /// Dont increment index or the current length, the start of the next path can be within this span. + /// We are done with this span. + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + paths.append(currentPath) + currentPath = BezierPath() + if remainingPositions.count > 0 { + trim = remainingPositions.remove(at: 0) + } else { + finishedTrimming = true + } + } + return paths + } + +} + +// MARK: Codable + +extension BezierPath: Codable { + + // MARK: Lifecycle + + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer + + if let keyedContainer = try? decoder.container(keyedBy: BezierPath.CodingKeys.self) { + container = keyedContainer + } else { + var unkeyedContainer = try decoder.unkeyedContainer() + container = try unkeyedContainer.nestedContainer(keyedBy: BezierPath.CodingKeys.self) + } + + closed = try container.decodeIfPresent(Bool.self, forKey: .closed) ?? true + + var vertexContainer = try container.nestedUnkeyedContainer(forKey: .vertices) + var inPointsContainer = try container.nestedUnkeyedContainer(forKey: .inPoints) + var outPointsContainer = try container.nestedUnkeyedContainer(forKey: .outPoints) + + guard vertexContainer.count == inPointsContainer.count, inPointsContainer.count == outPointsContainer.count else { + /// Will throw an error if vertex, inpoints, and outpoints are not the same length. + /// This error is to be expected. + throw DecodingError.dataCorruptedError( + forKey: CodingKeys.vertices, + in: container, + debugDescription: "Vertex data does not match In Tangents and Out Tangents") + } + + guard let count = vertexContainer.count, count > 0 else { + length = 0 + elements = [] + return + } + + var decodedElements = [PathElement]() + + /// Create first point + let firstVertex = CurveVertex( + point: try vertexContainer.decode(CGPoint.self), + inTangentRelative: try inPointsContainer.decode(CGPoint.self), + outTangentRelative: try outPointsContainer.decode(CGPoint.self)) + var previousElement = PathElement(vertex: firstVertex) + decodedElements.append(previousElement) + + var totalLength: CGFloat = 0 + while !vertexContainer.isAtEnd { + /// Get the next vertex data. + let vertex = CurveVertex( + point: try vertexContainer.decode(CGPoint.self), + inTangentRelative: try inPointsContainer.decode(CGPoint.self), + outTangentRelative: try outPointsContainer.decode(CGPoint.self)) + let pathElement = previousElement.pathElementTo(vertex) + decodedElements.append(pathElement) + previousElement = pathElement + totalLength = totalLength + pathElement.length + } + if closed { + // Don't use an out tangent for the closing point, since the + // closing point is exactly equal to the starting point. + let closeVertex = CurveVertex( + point: firstVertex.point, + inTangentRelative: firstVertex.inTangentRelative, + outTangentRelative: .zero) + + let closeElement = previousElement.pathElementTo(closeVertex) + decodedElements.append(closeElement) + totalLength = totalLength + closeElement.length + } + length = totalLength + elements = decodedElements + } + + // MARK: Internal + + /// The BezierPath container is encoded and decoded from the JSON format + /// that defines points for a lottie animation. + /// + /// { + /// "c" = Bool + /// "i" = [[Double]], + /// "o" = [[Double]], + /// "v" = [[Double]] + /// } + /// + + enum CodingKeys: String, CodingKey { + case closed = "c" + case inPoints = "i" + case outPoints = "o" + case vertices = "v" + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: BezierPath.CodingKeys.self) + try container.encode(closed, forKey: .closed) + + var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices) + var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints) + var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints) + + /// If closed path, ignore the final element. + let finalIndex = closed ? elements.endIndex - 1 : elements.endIndex + for i in 0.. 0 else { + length = 0 + elements = [] + return + } + + var decodedElements = [PathElement]() + let firstVertexDictionary = vertexDictionaries.removeFirst() + let firstInPointsDictionary = inPointsDictionaries.removeFirst() + let firstOutPointsDictionary = outPointsDictionaries.removeFirst() + let firstVertex = CurveVertex( + point: try CGPoint(value: firstVertexDictionary), + inTangentRelative: try CGPoint(value: firstInPointsDictionary), + outTangentRelative: try CGPoint(value: firstOutPointsDictionary)) + var previousElement = PathElement(vertex: firstVertex) + decodedElements.append(previousElement) + + var totalLength: CGFloat = 0 + while vertexDictionaries.count > 0 { + let vertexDictionary = vertexDictionaries.removeFirst() + let inPointsDictionary = inPointsDictionaries.removeFirst() + let outPointsDictionary = outPointsDictionaries.removeFirst() + let vertex = CurveVertex( + point: try CGPoint(value: vertexDictionary), + inTangentRelative: try CGPoint(value: inPointsDictionary), + outTangentRelative: try CGPoint(value: outPointsDictionary)) + let pathElement = previousElement.pathElementTo(vertex) + decodedElements.append(pathElement) + previousElement = pathElement + totalLength = totalLength + pathElement.length + } + if closed { + // Don't use an out tangent for the closing point, since the + // closing point is exactly equal to the starting point. + let closeVertex = CurveVertex( + point: firstVertex.point, + inTangentRelative: firstVertex.inTangentRelative, + outTangentRelative: .zero) + + let closeElement = previousElement.pathElementTo(closeVertex) + decodedElements.append(closeElement) + totalLength = totalLength + closeElement.length + } + + length = totalLength + elements = decodedElements + } + +} + +extension BezierPath { + + func cgPath() -> CGPath { + let cgPath = CGMutablePath() + + var previousElement: PathElement? + for element in elements { + if let previous = previousElement { + if previous.vertex.outTangentRelative.isZero, element.vertex.inTangentRelative.isZero { + cgPath.addLine(to: element.vertex.point) + } else { + cgPath.addCurve(to: element.vertex.point, control1: previous.vertex.outTangent, control2: element.vertex.inTangent) + } + } else { + cgPath.move(to: element.vertex.point) + } + previousElement = element + } + if closed { + cgPath.closeSubpath() + } + return cgPath + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/BezierPathRoundExtension.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/BezierPathRoundExtension.swift new file mode 100644 index 0000000000000000000000000000000000000000..7a0c1186df7b0e71b11dff7374432ff6c858b03b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/BezierPathRoundExtension.swift @@ -0,0 +1,158 @@ +// +// BezierPathRoundExtension.swift +// Lottie +// +// Created by Duolingo on 11/1/22. +// + +import CoreGraphics +import Foundation + +// Adapted to Swift from lottie-web & lottie-android: + +// Rounded corner algorithm: +// Iterate through each vertex. +// If a vertex is a sharp corner, it rounds it. +// If a vertex has control points, it is already rounded, so it does nothing. +// +// To round a vertex: +// Split the vertex into two. +// Move vertex 1 directly towards the previous vertex. +// Set vertex 1's in control point to itself so it is not rounded on that side. +// Extend vertex 1's out control point towards the original vertex. +// +// Repeat for vertex 2: +// Move vertex 2 directly towards the next vertex. +// Set vertex 2's out point to itself so it is not rounded on that side. +// Extend vertex 2's in control point towards the original vertex. +// +// The distance that the vertices and control points are moved are relative to the +// shape's vertex distances and the roundedness set in the animation. + +extension CompoundBezierPath { + /// Round corners of a compound bezier + func roundCorners(radius: CGFloat) -> CompoundBezierPath { + var newPaths = [BezierPath]() + for path in paths { + newPaths.append( + path.roundCorners(radius: radius)) + } + + return CompoundBezierPath(paths: newPaths) + } +} + +extension BezierPath { + /// Computes a new `BezierPath` with each corner rounded based on the given `radius` + func roundCorners(radius: CGFloat) -> BezierPath { + var newPath = BezierPath() + var uniquePath = BezierPath() + + var currentVertex: CurveVertex + var closestVertex: CurveVertex + var distance: CGFloat + var newPosPerc: CGFloat + var closestIndex: Int + + var iX: CGFloat + var iY: CGFloat + var vX: CGFloat + var vY: CGFloat + var oX: CGFloat + var oY: CGFloat + + var startIndex = 0 + + let TANGENT_LENGTH = 0.5519 + + // If start and end are the same we close the path + if + elements[0].vertex.point == elements[elements.count - 1].vertex.point, + elements[0].vertex.inTangent == elements[elements.count - 1].vertex.inTangent, + elements[0].vertex.outTangent == elements[elements.count - 1].vertex.outTangent + { + startIndex = 1 + newPath.close() + } + + guard elements.count - startIndex > 1 else { + return self + } + + for i in startIndex.. 1 + { + self.init(x: array[0], y: array[1]) + } else { + throw InitializableError.invalidInput() + } + } + + // MARK: Private + + private enum CodingKeys: String { + case x + case y + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/ColorExtension.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/ColorExtension.swift new file mode 100644 index 0000000000000000000000000000000000000000..4fe60b1a06c1de5729f17ea3fa15ba6a3d8f3fa0 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/ColorExtension.swift @@ -0,0 +1,101 @@ +// +// LottieColor.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics + +// MARK: - LottieColor + Codable + +extension LottieColor: Codable { + + // MARK: Lifecycle + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + var r1: Double = + if !container.isAtEnd { + try container.decode(Double.self) + } else { + 0 + } + + var g1: Double = + if !container.isAtEnd { + try container.decode(Double.self) + } else { + 0 + } + + var b1: Double = + if !container.isAtEnd { + try container.decode(Double.self) + } else { + 0 + } + + if r1 > 1, g1 > 1, b1 > 1 { + r1 = r1 / 255 + g1 = g1 / 255 + b1 = b1 / 255 + } + r = r1 + g = g1 + b = b1 + + // The Lottie JSON schema supports alpha values in theory, as the fourth value in this array. + // We intentionally do not support this, though, for consistency with Lottie on other platforms. + a = 1 + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(r) + try container.encode(g) + try container.encode(b) + try container.encode(a) + } + +} + +// MARK: - LottieColor + AnyInitializable + +extension LottieColor: AnyInitializable { + + init(value: Any) throws { + guard var array = value as? [Double] else { + throw InitializableError.invalidInput() + } + var r: Double = array.count > 0 ? array.removeFirst() : 0 + var g: Double = array.count > 0 ? array.removeFirst() : 0 + var b: Double = array.count > 0 ? array.removeFirst() : 0 + if r > 1, g > 1, b > 1 { + r /= 255 + g /= 255 + b /= 255 + } + self.r = r + self.g = g + self.b = b + + // The Lottie JSON schema supports alpha values in theory, as the fourth value in this array. + // We intentionally do not support this, though, for consistency with Lottie on other platforms. + a = 1 + } + +} + +extension LottieColor { + static var clearColor: CGColor { + .rgba(0, 0, 0, 0) + } + + var cgColorValue: CGColor { + .rgba(CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/CompoundBezierPath.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/CompoundBezierPath.swift new file mode 100644 index 0000000000000000000000000000000000000000..b0aa268aa44ab87177720e10fb13b18dba9fc07c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/CompoundBezierPath.swift @@ -0,0 +1,167 @@ +// +// CompoundBezierPath.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import CoreGraphics +import Foundation + +/// A collection of BezierPath objects that can be trimmed and added. +/// +struct CompoundBezierPath { + + // MARK: Lifecycle + + init() { + paths = [] + length = 0 + } + + init(path: BezierPath) { + paths = [path] + length = path.length + } + + init(paths: [BezierPath], length: CGFloat) { + self.paths = paths + self.length = length + } + + init(paths: [BezierPath]) { + self.paths = paths + var l: CGFloat = 0 + for path in paths { + l = l + path.length + } + length = l + } + + // MARK: Internal + + let paths: [BezierPath] + + let length: CGFloat + + func addPath(path: BezierPath) -> CompoundBezierPath { + var newPaths = paths + newPaths.append(path) + return CompoundBezierPath(paths: newPaths, length: length + path.length) + } + + func combine(_ compoundBezier: CompoundBezierPath) -> CompoundBezierPath { + var newPaths = paths + newPaths.append(contentsOf: compoundBezier.paths) + return CompoundBezierPath(paths: newPaths, length: length + compoundBezier.length) + } + + func trim(fromPosition: CGFloat, toPosition: CGFloat, offset: CGFloat, trimSimultaneously: Bool) -> CompoundBezierPath { + if fromPosition == toPosition { + return CompoundBezierPath() + } + + if trimSimultaneously { + /// Trim each path individually. + var newPaths = [BezierPath]() + for path in paths { + newPaths.append(contentsOf: path.trim( + fromLength: fromPosition * path.length, + toLength: toPosition * path.length, + offsetLength: offset * path.length)) + } + return CompoundBezierPath(paths: newPaths) + } + + /// Normalize lengths to the curve length. + var startPosition = (fromPosition + offset).truncatingRemainder(dividingBy: 1) + var endPosition = (toPosition + offset).truncatingRemainder(dividingBy: 1) + + if startPosition < 0 { + startPosition = 1 + startPosition + } + + if endPosition < 0 { + endPosition = 1 + endPosition + } + + if startPosition == 1 { + startPosition = 0 + } + if endPosition == 0 { + endPosition = 1 + } + + if + startPosition == 0 && endPosition == 1 || + startPosition == endPosition || + startPosition == 1 && endPosition == 0 + { + /// The trim encompasses the entire path. Return. + return self + } + + var positions: [(start: CGFloat, end: CGFloat)] = + if endPosition < startPosition { + [ + (start: 0, end: endPosition * length), + (start: startPosition * length, end: length), + ] + } else { + [(start: startPosition * length, end: endPosition * length)] + } + + var compoundPath = CompoundBezierPath() + var trim = positions.remove(at: 0) + var pathStartPosition: CGFloat = 0 + + var finishedTrimming = false + var i = 0 + + while !finishedTrimming { + if paths.count <= i { + /// Rounding errors + finishedTrimming = true + continue + } + let path = paths[i] + + let pathEndPosition = pathStartPosition + path.length + + if pathEndPosition < trim.start { + /// Path is not included in the trim, continue. + pathStartPosition = pathEndPosition + i = i + 1 + continue + + } else if trim.start <= pathStartPosition, pathEndPosition <= trim.end { + /// Full Path is inside of trim. Add full path. + compoundPath = compoundPath.addPath(path: path) + } else { + if + let trimPath = path.trim( + fromLength: trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, + toLength: trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length, + offsetLength: 0).first + { + compoundPath = compoundPath.addPath(path: trimPath) + } + } + + if trim.end <= pathEndPosition { + /// We are done with the current trim. + /// Advance trim but remain on the same path in case the next trim overlaps it. + if positions.count > 0 { + trim = positions.remove(at: 0) + } else { + finishedTrimming = true + } + } else { + pathStartPosition = pathEndPosition + i = i + 1 + } + } + return compoundPath + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/CurveVertex.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/CurveVertex.swift new file mode 100644 index 0000000000000000000000000000000000000000..32afadb0a0676212465e77b2866f371ceff34728 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/CurveVertex.swift @@ -0,0 +1,184 @@ +// +// CurveVertex.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/11/19. +// + +import CoreGraphics +import Foundation + +/// A single vertex with an in and out tangent +struct CurveVertex { + + // MARK: Lifecycle + + /// Initializes a curve point with absolute values + init(_ inTangent: CGPoint, _ point: CGPoint, _ outTangent: CGPoint) { + self.point = point + self.inTangent = inTangent + self.outTangent = outTangent + } + + /// Initializes a curve point with relative values + init(point: CGPoint, inTangentRelative: CGPoint, outTangentRelative: CGPoint) { + self.point = point + inTangent = point.add(inTangentRelative) + outTangent = point.add(outTangentRelative) + } + + /// Initializes a curve point with absolute values + init(point: CGPoint, inTangent: CGPoint, outTangent: CGPoint) { + self.point = point + self.inTangent = inTangent + self.outTangent = outTangent + } + + // MARK: Internal + + let point: CGPoint + + let inTangent: CGPoint + let outTangent: CGPoint + + var inTangentRelative: CGPoint { + inTangent.subtract(point) + } + + var outTangentRelative: CGPoint { + outTangent.subtract(point) + } + + func reversed() -> CurveVertex { + CurveVertex(point: point, inTangent: outTangent, outTangent: inTangent) + } + + func translated(_ translation: CGPoint) -> CurveVertex { + CurveVertex(point: point + translation, inTangent: inTangent + translation, outTangent: outTangent + translation) + } + + /// Trims a path defined by two Vertices at a specific position, from 0 to 1 + /// + /// The path can be visualized below. + /// + /// F is fromVertex. + /// V is the vertex of the receiver. + /// P is the position from 0-1. + /// O is the outTangent of fromVertex. + /// F====O=========P=======I====V + /// + /// After trimming the curve can be visualized below. + /// + /// S is the returned Start vertex. + /// E is the returned End vertex. + /// T is the trim point. + /// TI and TO are the new tangents for the trimPoint + /// NO and NI are the new tangents for the startPoint and endPoints + /// S==NO=========TI==T==TO=======NI==E + func splitCurve(toVertex: CurveVertex, position: CGFloat) -> + (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) + { + /// If position is less than or equal to 0, trim at start. + if position <= 0 { + return ( + start: CurveVertex(point: point, inTangentRelative: inTangentRelative, outTangentRelative: .zero), + trimPoint: CurveVertex(point: point, inTangentRelative: .zero, outTangentRelative: outTangentRelative), + end: toVertex) + } + + /// If position is greater than or equal to 1, trim at end. + if position >= 1 { + return ( + start: self, + trimPoint: CurveVertex( + point: toVertex.point, + inTangentRelative: toVertex.inTangentRelative, + outTangentRelative: .zero), + end: CurveVertex( + point: toVertex.point, + inTangentRelative: .zero, + outTangentRelative: toVertex.outTangentRelative)) + } + + if outTangentRelative.isZero, toVertex.inTangentRelative.isZero { + /// If both tangents are zero, then span to be trimmed is a straight line. + let trimPoint = point.interpolate(to: toVertex.point, amount: position) + return ( + start: self, + trimPoint: CurveVertex(point: trimPoint, inTangentRelative: .zero, outTangentRelative: .zero), + end: toVertex) + } + /// Cutting by amount gives incorrect length.... + /// One option is to cut by a stride until it gets close then edge it down. + /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. + /// This is where the historical trim path bugs come from. + let a = point.interpolate(to: outTangent, amount: position) + let b = outTangent.interpolate(to: toVertex.inTangent, amount: position) + let c = toVertex.inTangent.interpolate(to: toVertex.point, amount: position) + let d = a.interpolate(to: b, amount: position) + let e = b.interpolate(to: c, amount: position) + let f = d.interpolate(to: e, amount: position) + return ( + start: CurveVertex(point: point, inTangent: inTangent, outTangent: a), + trimPoint: CurveVertex(point: f, inTangent: d, outTangent: e), + end: CurveVertex(point: toVertex.point, inTangent: c, outTangent: toVertex.outTangent)) + } + + /// Trims a curve of a known length to a specific length and returns the points. + /// + /// There is not a performant yet accurate way to cut a curve to a specific length. + /// This calls splitCurve(toVertex: position:) to split the curve and then measures + /// the length of the new curve. The function then iterates through the samples, + /// adjusting the position of the cut for a more precise cut. + /// Usually a single iteration is enough to get within 0.5 points of the desired + /// length. + /// + /// This function should probably live in PathElement, since it deals with curve + /// lengths. + func trimCurve(toVertex: CurveVertex, atLength: CGFloat, curveLength: CGFloat, maxSamples: Int, accuracy: CGFloat = 1) -> + (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) + { + var currentPosition = atLength / curveLength + var results = splitCurve(toVertex: toVertex, position: currentPosition) + + if maxSamples == 0 { + return results + } + + for _ in 1...maxSamples { + let length = results.start.distanceTo(results.trimPoint) + let lengthDiff = atLength - length + /// Check if length is correct. + if lengthDiff < accuracy { + return results + } + let diffPosition = max(min((currentPosition / length) * lengthDiff, currentPosition * 0.5), currentPosition * -0.5) + currentPosition = diffPosition + currentPosition + results = splitCurve(toVertex: toVertex, position: currentPosition) + } + return results + } + + /// The distance from the receiver to the provided vertex. + /// + /// For lines (zeroed tangents) the distance between the two points is measured. + /// For curves the curve is iterated over by sample count and the points are measured. + /// This is ~99% accurate at a sample count of 30 + func distanceTo(_ toVertex: CurveVertex, sampleCount: Int = 25) -> CGFloat { + if outTangentRelative.isZero, toVertex.inTangentRelative.isZero { + /// Return a linear distance. + return point.distanceTo(toVertex.point) + } + + var distance: CGFloat = 0 + + var previousPoint = point + for i in 0.. PathElement { + PathElement(length: vertex.distanceTo(toVertex), vertex: toVertex) + } + + func updateVertex(newVertex: CurveVertex) -> PathElement { + PathElement(length: length, vertex: newVertex) + } + + /// Splits an element span defined by the receiver and fromElement to a position 0-1 + func splitElementAtPosition(fromElement: PathElement, atLength: CGFloat) -> + (leftSpan: (start: PathElement, end: PathElement), rightSpan: (start: PathElement, end: PathElement)) + { + /// Trim the span. Start and trim go into the first, trim and end go into second. + let trimResults = fromElement.vertex.trimCurve(toVertex: vertex, atLength: atLength, curveLength: length, maxSamples: 3) + + /// Create the elements for the break + let spanAStart = PathElement( + length: fromElement.length, + vertex: CurveVertex( + point: fromElement.vertex.point, + inTangent: fromElement.vertex.inTangent, + outTangent: trimResults.start.outTangent)) + /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. + let spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint) + + let spanBStart = PathElement(vertex: trimResults.trimPoint) + let spanBEnd = spanBStart.pathElementTo(trimResults.end) + return ( + leftSpan: (start: spanAStart, end: spanAEnd), + rightSpan: (start: spanBStart, end: spanBEnd)) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/UnitBezier.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/UnitBezier.swift new file mode 100644 index 0000000000000000000000000000000000000000..8b14fa1525af3823cb0d1e67e8d7296d7ebc3d85 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/UnitBezier.swift @@ -0,0 +1,115 @@ +// Copyright (C) 2008 Apple Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import CoreGraphics +import Foundation + +/// Defines a cubic-bezier where the endpoints are (0, 0) and (1, 1) +/// +/// The main use case is computing the progress of an animation at a given percent completion. For instance, +/// for a linear animation, the expected progress at `0.5` is `0.5`. +/// +/// - Note: This is a Swift port of [Apple's WebKit code]( +/// http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h +/// ) +/// +struct UnitBezier { + + // MARK: Lifecycle + + init(controlPoint1: CGPoint, controlPoint2: CGPoint) { + cx = 3.0 * controlPoint1.x + bx = 3.0 * (controlPoint2.x - controlPoint1.x) - cx + ax = 1.0 - cx - bx + cy = 3.0 * controlPoint1.y + by = 3.0 * (controlPoint2.y - controlPoint1.y) - cy + ay = 1.0 - cy - by + } + + // MARK: Internal + + /// Computes the progress `y` value for a given `x` value + func value(for x: CGFloat, epsilon: CGFloat) -> CGFloat { + sampleCurveY(solveCurveX(x, epsilon: epsilon)) + } + + // MARK: Private + + private let ax: CGFloat + private let bx: CGFloat + private let cx: CGFloat + private let ay: CGFloat + private let by: CGFloat + private let cy: CGFloat + + /// Compute `x(t)` for a given `t` + private func sampleCurveX(_ t: CGFloat) -> CGFloat { + // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + ((ax * t + bx) * t + cx) * t + } + + /// Compute `y(t)` for a given `t` + private func sampleCurveY(_ t: CGFloat) -> CGFloat { + ((ay * t + by) * t + cy) * t + } + + /// Compute `x'(t)` for a given `t` + private func sampleCurveDerivativeX(_ t: CGFloat) -> CGFloat { + (3.0 * ax * t + 2.0 * bx) * t + cx + } + + /// Given an `x` value solve for the parametric value `t` + private func solveCurveX(_ x: CGFloat, epsilon: CGFloat) -> CGFloat { + var t0, t1, t2, x2, d2: CGFloat + + // First try a few iterations of Newton-Raphson -- normally very fast. + t2 = x + for _ in 0..<8 { + x2 = sampleCurveX(t2) - x + guard abs(x2) >= epsilon else { return t2 } + d2 = sampleCurveDerivativeX(t2) + guard abs(d2) >= 1e-6 else { break } + t2 = t2 - x2 / d2 + } + + // Fall back to the bisection method for reliability. + t0 = 0.0 + t1 = 1.0 + t2 = x + guard t2 >= t0 else { return t0 } + guard t2 <= t1 else { return t1 } + + while t0 < t1 { + x2 = sampleCurveX(t2) + guard abs(x2 - x) >= epsilon else { return t2 } + if x > x2 { + t0 = t2 + } else { + t1 = t2 + } + t2 = (t1 - t0) * 0.5 + t0 + } + + return t2 + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/VectorsExtensions.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/VectorsExtensions.swift new file mode 100644 index 0000000000000000000000000000000000000000..626fd33ec4a2ba66a9e64f84b256e0944c67110f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Private/Utility/Primitives/VectorsExtensions.swift @@ -0,0 +1,363 @@ +// +// Vector.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import CoreGraphics +import Foundation +import QuartzCore + +// MARK: - LottieVector1D + Codable + +/// Single value container. Needed because lottie sometimes wraps a Double in an array. +extension LottieVector1D: Codable { + + // MARK: Lifecycle + + public init(from decoder: Decoder) throws { + /// Try to decode an array of doubles + do { + var container = try decoder.unkeyedContainer() + value = try container.decode(Double.self) + } catch { + value = try decoder.singleValueContainer().decode(Double.self) + } + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(value) + } + + // MARK: Internal + + var cgFloatValue: CGFloat { + CGFloat(value) + } + +} + +// MARK: - LottieVector1D + AnyInitializable + +extension LottieVector1D: AnyInitializable { + + init(value: Any) throws { + if + let array = value as? [Double], + let double = array.first + { + self.value = double + } else if let double = value as? Double { + self.value = double + } else { + throw InitializableError.invalidInput() + } + } + +} + +extension Double { + var vectorValue: LottieVector1D { + LottieVector1D(self) + } +} + +// MARK: - LottieVector2D + +/// Needed for decoding json {x: y:} to a CGPoint +public struct LottieVector2D: Codable, Hashable, Sendable { + + // MARK: Lifecycle + + init(x: Double, y: Double) { + self.x = x + self.y = y + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LottieVector2D.CodingKeys.self) + + do { + let xValue: [Double] = try container.decode([Double].self, forKey: .x) + x = xValue[0] + } catch { + x = try container.decode(Double.self, forKey: .x) + } + + do { + let yValue: [Double] = try container.decode([Double].self, forKey: .y) + y = yValue[0] + } catch { + y = try container.decode(Double.self, forKey: .y) + } + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: LottieVector2D.CodingKeys.self) + try container.encode(x, forKey: .x) + try container.encode(y, forKey: .y) + } + + // MARK: Internal + + var x: Double + var y: Double + + var pointValue: CGPoint { + CGPoint(x: x, y: y) + } + + // MARK: Private + + private enum CodingKeys: String, CodingKey { + case x + case y + } +} + +// MARK: AnyInitializable + +extension LottieVector2D: AnyInitializable { + + init(value: Any) throws { + guard let dictionary = value as? [String: Any] else { + throw InitializableError.invalidInput() + } + + if + let array = dictionary[CodingKeys.x.rawValue] as? [Double], + let double = array.first + { + x = double + } else if let double = dictionary[CodingKeys.x.rawValue] as? Double { + x = double + } else { + throw InitializableError.invalidInput() + } + if + let array = dictionary[CodingKeys.y.rawValue] as? [Double], + let double = array.first + { + y = double + } else if let double = dictionary[CodingKeys.y.rawValue] as? Double { + y = double + } else { + throw InitializableError.invalidInput() + } + } +} + +extension CGPoint { + var vector2dValue: LottieVector2D { + LottieVector2D(x: Double(x), y: Double(y)) + } +} + +// MARK: - LottieVector3D + Codable + +/// A three dimensional vector. +/// These vectors are encoded and decoded from [Double] + +extension LottieVector3D: Codable { + + // MARK: Lifecycle + + init(x: CGFloat, y: CGFloat, z: CGFloat) { + self.x = Double(x) + self.y = Double(y) + self.z = Double(z) + } + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + if !container.isAtEnd { + x = try container.decode(Double.self) + } else { + x = 0 + } + + if !container.isAtEnd { + y = try container.decode(Double.self) + } else { + y = 0 + } + + if !container.isAtEnd { + z = try container.decode(Double.self) + } else { + z = 0 + } + } + + // MARK: Public + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(x) + try container.encode(y) + try container.encode(z) + } + +} + +// MARK: - LottieVector3D + AnyInitializable + +extension LottieVector3D: AnyInitializable { + + init(value: Any) throws { + guard var array = value as? [Double] else { + throw InitializableError.invalidInput() + } + x = array.count > 0 ? array.removeFirst() : 0 + y = array.count > 0 ? array.removeFirst() : 0 + z = array.count > 0 ? array.removeFirst() : 0 + } + +} + +extension LottieVector3D { + public var pointValue: CGPoint { + CGPoint(x: x, y: y) + } + + public var sizeValue: CGSize { + CGSize(width: x, height: y) + } +} + +extension CGPoint { + var vector3dValue: LottieVector3D { + LottieVector3D(x: x, y: y, z: 0) + } +} + +extension CGSize { + var vector3dValue: LottieVector3D { + LottieVector3D(x: width, y: height, z: 1) + } +} + +extension CATransform3D { + + enum Axis { + case x, y, z + } + + static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { + let mCos = cos(skewAxis.toRadians()) + let mSin = sin(skewAxis.toRadians()) + let aTan = tan(skew.toRadians()) + + let transform1 = CATransform3D( + m11: mCos, + m12: mSin, + m13: 0, + m14: 0, + m21: -mSin, + m22: mCos, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1) + + let transform2 = CATransform3D( + m11: 1, + m12: 0, + m13: 0, + m14: 0, + m21: aTan, + m22: 1, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1) + + let transform3 = CATransform3D( + m11: mCos, + m12: -mSin, + m13: 0, + m14: 0, + m21: mSin, + m22: mCos, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1) + return CATransform3DConcat(transform3, CATransform3DConcat(transform2, transform1)) + } + + static func makeTransform( + anchor: CGPoint, + position: CGPoint, + scale: CGSize, + rotationX: CGFloat, + rotationY: CGFloat, + rotationZ: CGFloat, + skew: CGFloat?, + skewAxis: CGFloat?) + -> CATransform3D + { + if let skew, let skewAxis { + return CATransform3DMakeTranslation(position.x, position.y, 0) + .rotated(rotationX, axis: .x) + .rotated(rotationY, axis: .y) + .rotated(rotationZ, axis: .z) + .skewed(skew: -skew, skewAxis: skewAxis) + .scaled(scale * 0.01) + .translated(anchor * -1) + } + return CATransform3DMakeTranslation(position.x, position.y, 0) + .rotated(rotationX, axis: .x) + .rotated(rotationY, axis: .y) + .rotated(rotationZ, axis: .z) + .scaled(scale * 0.01) + .translated(anchor * -1) + } + + func rotated(_ degrees: CGFloat, axis: Axis) -> CATransform3D { + CATransform3DRotate( + self, + degrees.toRadians(), + axis == .x ? 1 : 0, + axis == .y ? 1 : 0, + axis == .z ? 1 : 0) + } + + func translated(_ translation: CGPoint) -> CATransform3D { + CATransform3DTranslate(self, translation.x, translation.y, 0) + } + + func scaled(_ scale: CGSize) -> CATransform3D { + CATransform3DScale(self, scale.width, scale.height, 1) + } + + func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { + CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self) + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimation.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimation.swift new file mode 100644 index 0000000000000000000000000000000000000000..0893eeeeabae9f1a49024fa82250d4d58e87ce9d --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimation.swift @@ -0,0 +1,179 @@ +// +// LottieAnimation.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +// MARK: - CoordinateSpace + +public enum CoordinateSpace: Int, Codable, Sendable { + case type2d + case type3d +} + +// MARK: - LottieAnimation + +/// The `LottieAnimation` model is the top level model object in Lottie. +/// +/// A `LottieAnimation` holds all of the animation data backing a Lottie Animation. +/// Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). +public final class LottieAnimation: Codable, Sendable, DictionaryInitializable { + + // MARK: Lifecycle + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LottieAnimation.CodingKeys.self) + version = try container.decode(String.self, forKey: .version) + type = try container.decodeIfPresent(CoordinateSpace.self, forKey: .type) ?? .type2d + startFrame = try container.decode(AnimationFrameTime.self, forKey: .startFrame) + endFrame = try container.decode(AnimationFrameTime.self, forKey: .endFrame) + framerate = try container.decode(Double.self, forKey: .framerate) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) + glyphs = try container.decodeIfPresent([Glyph].self, forKey: .glyphs) + fonts = try container.decodeIfPresent(FontList.self, forKey: .fonts) + assetLibrary = try container.decodeIfPresent(AssetLibrary.self, forKey: .assetLibrary) + markers = try container.decodeIfPresent([Marker].self, forKey: .markers) + + if let markers { + var markerMap: [String: Marker] = [:] + for marker in markers { + markerMap[marker.name] = marker + } + self.markerMap = markerMap + } else { + markerMap = nil + } + } + + public init(dictionary: [String: Any]) throws { + version = try dictionary.value(for: CodingKeys.version) + if + let typeRawValue = dictionary[CodingKeys.type.rawValue] as? Int, + let type = CoordinateSpace(rawValue: typeRawValue) + { + self.type = type + } else { + type = .type2d + } + startFrame = try dictionary.value(for: CodingKeys.startFrame) + endFrame = try dictionary.value(for: CodingKeys.endFrame) + framerate = try dictionary.value(for: CodingKeys.framerate) + width = try dictionary.value(for: CodingKeys.width) + height = try dictionary.value(for: CodingKeys.height) + let layerDictionaries: [[String: Any]] = try dictionary.value(for: CodingKeys.layers) + layers = try [LayerModel].fromDictionaries(layerDictionaries) + if let glyphDictionaries = dictionary[CodingKeys.glyphs.rawValue] as? [[String: Any]] { + glyphs = try glyphDictionaries.map { try Glyph(dictionary: $0) } + } else { + glyphs = nil + } + if let fontsDictionary = dictionary[CodingKeys.fonts.rawValue] as? [String: Any] { + fonts = try FontList(dictionary: fontsDictionary) + } else { + fonts = nil + } + if let assetLibraryDictionaries = dictionary[CodingKeys.assetLibrary.rawValue] as? [[String: Any]] { + assetLibrary = try AssetLibrary(value: assetLibraryDictionaries) + } else { + assetLibrary = nil + } + if let markerDictionaries = dictionary[CodingKeys.markers.rawValue] as? [[String: Any]] { + let markers = try markerDictionaries.map { try Marker(dictionary: $0) } + var markerMap: [String: Marker] = [:] + for marker in markers { + markerMap[marker.name] = marker + } + self.markers = markers + self.markerMap = markerMap + } else { + markers = nil + markerMap = nil + } + } + + // MARK: Public + + /// The start time of the composition in frameTime. + public let startFrame: AnimationFrameTime + + /// The end time of the composition in frameTime. + public let endFrame: AnimationFrameTime + + /// The frame rate of the composition. + public let framerate: Double + + /// Return all marker names, in order, or an empty list if none are specified + public var markerNames: [String] { + guard let markers else { return [] } + return markers.map { $0.name } + } + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case version = "v" + case type = "ddd" + case startFrame = "ip" + case endFrame = "op" + case framerate = "fr" + case width = "w" + case height = "h" + case layers + case glyphs = "chars" + case fonts + case assetLibrary = "assets" + case markers + } + + /// The version of the JSON Schema. + let version: String + + /// The coordinate space of the composition. + let type: CoordinateSpace + + /// The height of the composition in points. + let width: Double + + /// The width of the composition in points. + let height: Double + + /// The list of animation layers + let layers: [LayerModel] + + /// The list of glyphs used for text rendering + let glyphs: [Glyph]? + + /// The list of fonts used for text rendering + let fonts: FontList? + + /// Asset Library + let assetLibrary: AssetLibrary? + + /// Markers + let markers: [Marker]? + let markerMap: [String: Marker]? + + /// The marker to use if "reduced motion" is enabled. + /// Supported marker names are case insensitive, and include: + /// - reduced motion + /// - reducedMotion + /// - reduced_motion + /// - reduced-motion + var reducedMotionMarker: Marker? { + let allowedReducedMotionMarkerNames = Set([ + "reduced motion", + "reduced_motion", + "reduced-motion", + "reducedmotion", + ]) + + return markers?.first(where: { marker in + allowedReducedMotionMarkerNames.contains(marker.name.lowercased()) + }) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationHelpers.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationHelpers.swift new file mode 100644 index 0000000000000000000000000000000000000000..337dc9a863154e647a040c671c450ea98fe7d9e3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationHelpers.swift @@ -0,0 +1,320 @@ +// +// AnimationPublic.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import CoreGraphics +import Foundation + +extension LottieAnimation { + + /// A closure for an Animation download. The closure is passed `nil` if there was an error. + public typealias DownloadClosure = (LottieAnimation?) -> Void + + /// The duration in seconds of the animation. + public var duration: TimeInterval { + Double(endFrame - startFrame) / framerate + } + + /// The natural bounds in points of the animation. + public var bounds: CGRect { + CGRect(x: 0, y: 0, width: width, height: height) + } + + /// The natural size in points of the animation. + public var size: CGSize { + CGSize(width: width, height: height) + } + + // MARK: Animation (Loading) + + /// Loads an animation model from a bundle by its name. Returns `nil` if an animation is not found. + /// + /// - Parameter name: The name of the json file without the json extension. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the animation is located. Defaults to `Bundle.main` + /// - Parameter subdirectory: A subdirectory in the bundle in which the animation is located. Optional. + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LottieAnimationCache.shared`. Optional. + /// + /// - Returns: Deserialized `LottieAnimation`. Optional. + public static func named( + _ name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared) + -> LottieAnimation? + { + /// Create a cache key for the animation. + let cacheKey = bundle.bundlePath + (subdirectory ?? "") + "/" + name + + /// Check cache for animation + if + let animationCache, + let animation = animationCache.animation(forKey: cacheKey) + { + /// If found, return the animation. + return animation + } + + do { + /// Decode animation. + let json = try bundle.getAnimationData(name, subdirectory: subdirectory) + let animation = try LottieAnimation.from(data: json) + animationCache?.setAnimation(animation, forKey: cacheKey) + return animation + } catch { + /// Decoding error. + LottieLogger.shared.warn("Error when decoding animation \"\(name)\": \(error)") + return nil + } + } + + /// Loads an animation from a specific filepath. + /// - Parameter filepath: The absolute filepath of the animation to load. EG "/User/Me/starAnimation.json" + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LottieAnimationCache.shared`. Optional. + /// + /// - Returns: Deserialized `LottieAnimation`. Optional. + public static func filepath( + _ filepath: String, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared) + -> LottieAnimation? + { + /// Check cache for animation + if + let animationCache, + let animation = animationCache.animation(forKey: filepath) + { + return animation + } + + do { + /// Decode the animation. + let json = try Data(contentsOf: URL(fileURLWithPath: filepath)) + let animation = try LottieAnimation.from(data: json) + animationCache?.setAnimation(animation, forKey: filepath) + return animation + } catch { + LottieLogger.shared.warn(""" + Failed to load animation from filepath \(filepath) + with underlying error: \(error.localizedDescription) + """) + return nil + } + } + + /// Loads an animation model from the asset catalog by its name. Returns `nil` if an animation is not found. + /// - Parameter name: The name of the json file in the asset catalog. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the animation is located. Defaults to `Bundle.main` + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LottieAnimationCache.shared` Optional. + /// - Returns: Deserialized `LottieAnimation`. Optional. + public static func asset( + _ name: String, + bundle: Bundle = Bundle.main, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared) + -> LottieAnimation? + { + /// Create a cache key for the animation. + let cacheKey = bundle.bundlePath + "/" + name + + /// Check cache for animation + if + let animationCache, + let animation = animationCache.animation(forKey: cacheKey) + { + /// If found, return the animation. + return animation + } + + do { + /// Load jsonData from Asset + let json = try Data(assetName: name, in: bundle) + /// Decode animation. + let animation = try LottieAnimation.from(data: json) + animationCache?.setAnimation(animation, forKey: cacheKey) + return animation + } catch { + LottieLogger.shared.warn(""" + Failed to load animation with asset name \(name) + in \(bundle.bundlePath) + with underlying error: \(error.localizedDescription) + """) + return nil + } + } + + /// Loads a Lottie animation from a `Data` object containing a JSON animation. + /// + /// - Parameter data: The object to load the animation from. + /// - Parameter strategy: How the data should be decoded. Defaults to using the strategy set in `LottieConfiguration.shared`. + /// - Returns: Deserialized `LottieAnimation`. Optional. + /// + public static func from( + data: Data, + strategy: DecodingStrategy = LottieConfiguration.shared.decodingStrategy) + throws -> LottieAnimation + { + switch strategy { + case .legacyCodable: + return try JSONDecoder().decode(LottieAnimation.self, from: data) + case .dictionaryBased: + let json = try JSONSerialization.jsonObject(with: data) + guard let dict = json as? [String: Any] else { + throw InitializableError.invalidInput() + } + return try LottieAnimation(dictionary: dict) + } + } + + /// Loads a Lottie animation asynchronously from the URL. + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LottieAnimationCache.shared`. Optional. + /// + public static func loadedFrom( + url: URL, + session: URLSession = .shared, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared) + async -> LottieAnimation? + { + await withCheckedContinuation { continuation in + LottieAnimation.loadedFrom( + url: url, + session: session, + closure: { result in + continuation.resume(returning: result) + }, + animationCache: animationCache) + } + } + + /// Loads a Lottie animation asynchronously from the URL. + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter closure: A closure to be called when the animation has loaded. + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LottieAnimationCache.shared`. Optional. + /// + public static func loadedFrom( + url: URL, + session: URLSession = .shared, + closure: @escaping LottieAnimation.DownloadClosure, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared) + { + if let animationCache, let animation = animationCache.animation(forKey: url.absoluteString) { + closure(animation) + } else { + let task = session.dataTask(with: url) { data, _, error in + guard error == nil, let jsonData = data else { + DispatchQueue.main.async { + closure(nil) + } + return + } + do { + let animation = try LottieAnimation.from(data: jsonData) + DispatchQueue.main.async { + animationCache?.setAnimation(animation, forKey: url.absoluteString) + closure(animation) + } + } catch { + DispatchQueue.main.async { + closure(nil) + } + } + } + task.resume() + } + } + + // MARK: Animation (Helpers) + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Progress Time for the marker named. Returns nil if no marker found. + public func progressTime(forMarker named: String) -> AnimationProgressTime? { + guard let markers = markerMap, let marker = markers[named] else { + return nil + } + return progressTime(forFrame: marker.frameTime) + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Frame Time for the marker named. Returns nil if no marker found. + public func frameTime(forMarker named: String) -> AnimationFrameTime? { + guard let markers = markerMap, let marker = markers[named] else { + return nil + } + return marker.frameTime + } + + /// Markers are a way to describe a point in time and a duration by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// - Returns: The duration frame time for the marker, or `nil` if no marker found. + public func durationFrameTime(forMarker named: String) -> AnimationFrameTime? { + guard let marker = markerMap?[named] else { + return nil + } + return marker.durationFrameTime + } + + /// Converts Frame Time (Seconds * Framerate) into Progress Time + /// (optionally clamped to between 0 and 1). + public func progressTime( + forFrame frameTime: AnimationFrameTime, + clamped: Bool = true) + -> AnimationProgressTime + { + let progressTime = ((frameTime - startFrame) / (endFrame - startFrame)) + + if clamped { + return progressTime.clamp(0, 1) + } else { + return progressTime + } + } + + /// Converts Progress Time (0 to 1) into Frame Time (Seconds * Framerate) + public func frameTime(forProgress progressTime: AnimationProgressTime) -> AnimationFrameTime { + ((endFrame - startFrame) * progressTime) + startFrame + } + + /// Converts Frame Time (Seconds * Framerate) into Time (Seconds) + public func time(forFrame frameTime: AnimationFrameTime) -> TimeInterval { + Double(frameTime - startFrame) / framerate + } + + /// Converts Time (Seconds) into Frame Time (Seconds * Framerate) + public func frameTime(forTime time: TimeInterval) -> AnimationFrameTime { + CGFloat(time * framerate) + startFrame + } +} + +// MARK: - Foundation.Bundle + Sendable + +/// Necessary to suppress warnings like: +/// ``` +/// Non-sendable type 'Bundle' exiting main actor-isolated context in call to non-isolated +/// static method 'named(_:bundle:subdirectory:dotLottieCache:)' cannot cross actor boundary +/// ``` +/// This retroactive conformance is safe because Sendable is a marker protocol that doesn't +/// include any runtime component. Multiple modules in the same package graph can provide this +/// conformance without causing any conflicts. +/// +// swiftlint:disable:next no_unchecked_sendable +extension Foundation.Bundle: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationLayer.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationLayer.swift new file mode 100644 index 0000000000000000000000000000000000000000..e686665995a7415ab780c94f3b278d546b1002ea --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationLayer.swift @@ -0,0 +1,1522 @@ +// +// LottieAnimationLayer.swift +// Lottie +// + +import QuartzCore + +// MARK: - LottieAnimationLayer + +/// A CALayer subclass for rendering Lottie animations. +/// - Also available as a SwiftUI view (`LottieView`) and a UIView subclass (`LottieAnimationView`) +public class LottieAnimationLayer: CALayer { + + // MARK: Lifecycle + + /// Initializes a LottieAnimationLayer with an animation. + public init( + animation: LottieAnimation?, + imageProvider: AnimationImageProvider? = nil, + textProvider: AnimationKeypathTextProvider = DefaultTextProvider(), + fontProvider: AnimationFontProvider = DefaultFontProvider(), + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + self.animation = animation + self.imageProvider = imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = textProvider + self.fontProvider = fontProvider + self.configuration = configuration + screenScale = 1 + self.logger = logger + super.init() + makeAnimationLayer(usingEngine: configuration.renderingEngine) + if let animation { + frame = animation.bounds + } + } + + /// Initializes an LottieAnimationLayer with a .lottie file. + public init( + dotLottie: DotLottieFile?, + animationId: String? = nil, + textProvider: AnimationKeypathTextProvider = DefaultTextProvider(), + fontProvider: AnimationFontProvider = DefaultFontProvider(), + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + let dotLottieAnimation = dotLottie?.animation(for: animationId) + animation = dotLottieAnimation?.animation + imageProvider = dotLottie?.imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = textProvider + self.fontProvider = fontProvider + self.configuration = configuration + screenScale = 1 + self.logger = logger + super.init() + loopMode = dotLottieAnimation?.configuration.loopMode ?? .playOnce + animationSpeed = CGFloat(dotLottieAnimation?.configuration.speed ?? 1) + makeAnimationLayer(usingEngine: configuration.renderingEngine) + if let animation { + frame = animation.bounds + } + } + + public init( + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + animation = nil + imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + textProvider = DefaultTextProvider() + fontProvider = DefaultFontProvider() + self.configuration = configuration + screenScale = 1 + self.logger = logger + super.init() + } + + /// Called by CoreAnimation to create a shadow copy of this layer + /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + override init(layer: Any) { + guard let typedLayer = layer as? Self else { + fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))") + } + + animation = typedLayer.animation + imageProvider = typedLayer.imageProvider + textProvider = typedLayer.textProvider + fontProvider = typedLayer.fontProvider + logger = typedLayer.logger + screenScale = typedLayer.screenScale + configuration = typedLayer.configuration + super.init(layer: typedLayer) + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Open + + /// Plays the animation from its current state to the end. + /// + /// - Parameter completion: An optional completion closure to be called when the animation completes playing. + open func play(completion: LottieCompletionBlock? = nil) { + guard let animation else { return } + + defer { + currentPlaybackMode = .playing(.fromProgress(nil, toProgress: 1, loopMode: loopMode)) + } + + if shouldOverrideWithReducedMotionAnimation { + playReducedMotionAnimation(completion: completion) + return + } + + /// Build a context for the animation. + let context = AnimationContext( + playFrom: CGFloat(animation.startFrame), + playTo: CGFloat(animation.endFrame), + closure: completion) + removeCurrentAnimationIfNecessary() + addNewAnimationForContext(context) + } + + /// Plays the animation from a progress (0-1) to a progress (0-1). + /// + /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress. + /// - Parameter toProgress: The end progress of the animation. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + fromProgress: AnimationProgressTime? = nil, + toProgress: AnimationProgressTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + guard let animation else { return } + + defer { + currentPlaybackMode = .playing(.fromProgress(fromProgress, toProgress: toProgress, loopMode: loopMode ?? self.loopMode)) + } + + if shouldOverrideWithReducedMotionAnimation { + playReducedMotionAnimation(completion: completion) + return + } + + removeCurrentAnimationIfNecessary() + if let loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + let context = AnimationContext( + playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress), + playTo: animation.frameTime(forProgress: toProgress), + closure: completion) + addNewAnimationForContext(context) + } + + /// Plays the animation from a start frame to an end frame in the animation's framerate. + /// + /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame. + /// - Parameter toFrame: The end frame of the animation. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + fromFrame: AnimationFrameTime? = nil, + toFrame: AnimationFrameTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + defer { + currentPlaybackMode = .playing(.fromFrame(fromFrame, toFrame: toFrame, loopMode: loopMode ?? self.loopMode)) + } + + if shouldOverrideWithReducedMotionAnimation { + playReducedMotionAnimation(completion: completion) + return + } + + removeCurrentAnimationIfNecessary() + if let loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + + let context = AnimationContext( + playFrom: fromFrame ?? currentFrame, + playTo: toFrame, + closure: completion) + addNewAnimationForContext(context) + } + + /// Plays the animation from a named marker to another marker. + /// + /// Markers are point in time that are encoded into the Animation data and assigned + /// a name. + /// + /// NOTE: If markers are not found the play command will exit. + /// + /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the + /// animation will start at the current progress. + /// - Parameter toMarker: The end marker for the animation playback. + /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the + /// end marker represents the end of the section to play, it should be to true. If the provided end marker + /// represents the beginning of the next section, it should be false. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + fromMarker: String? = nil, + toMarker: String, + playEndMarkerFrame: Bool = true, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + defer { + currentPlaybackMode = .playing(.fromMarker( + fromMarker, + toMarker: toMarker, + playEndMarkerFrame: playEndMarkerFrame, + loopMode: loopMode ?? self.loopMode)) + } + + if shouldOverrideWithReducedMotionAnimation { + playReducedMotionAnimation(completion: completion) + return + } + + guard let animation, let markers = animation.markerMap, let to = markers[toMarker] else { + return + } + + removeCurrentAnimationIfNecessary() + if let loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + + let fromTime: CGFloat = + if let fromName = fromMarker, let from = markers[fromName] { + CGFloat(from.frameTime) + } else { + currentFrame + } + + let playTo = playEndMarkerFrame ? CGFloat(to.frameTime) : CGFloat(to.frameTime) - 1 + let context = AnimationContext( + playFrom: fromTime, + playTo: playTo, + closure: completion) + addNewAnimationForContext(context) + } + + /// Plays the animation from a named marker to the end of the marker's duration. + /// + /// A marker is a point in time with an associated duration that is encoded into the + /// animation data and assigned a name. + /// + /// NOTE: If marker is not found the play command will exit. + /// + /// - Parameter marker: The start marker for the animation playback. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + marker: String, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + guard let from = animation?.markerMap?[marker] else { + return + } + + defer { + currentPlaybackMode = .playing(.marker(marker, loopMode: loopMode ?? self.loopMode)) + } + + if shouldOverrideWithReducedMotionAnimation { + playReducedMotionAnimation(completion: completion) + return + } + + play( + fromFrame: from.frameTime, + toFrame: from.frameTime + from.durationFrameTime, + loopMode: loopMode, + completion: completion) + } + + /// Plays the given markers sequentially in order. + /// + /// A marker is a point in time with an associated duration that is encoded into the + /// animation data and assigned a name. Multiple markers can be played sequentially + /// to create programmable animations. + /// + /// If a marker is not found, it will be skipped. + /// + /// If a marker doesn't have a duration value, it will play with a duration of 0 + /// (effectively being skipped). + /// + /// If another animation is played (by calling any `play` method) while this + /// marker sequence is playing, the marker sequence will be cancelled. + /// + /// - Parameter markers: The list of markers to play sequentially. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + markers: [String], + completion: LottieCompletionBlock? = nil) + { + guard !markers.isEmpty else { return } + + defer { + currentPlaybackMode = .playing(.markers(markers)) + } + + if shouldOverrideWithReducedMotionAnimation { + playReducedMotionAnimation(completion: nil) + return + } + + let markerToPlay = markers[0] + let followingMarkers = Array(markers.dropFirst()) + + guard animation?.markerMap?[markerToPlay] != nil else { + play(markers: followingMarkers, completion: completion) + return + } + + play(marker: markerToPlay, loopMode: .playOnce, completion: { [weak self] completed in + // If the completion handler is called with `completed: false` (which typically means + // that another animation was played by calling some `play` method), + // we should cancel the marker sequence and not play the next marker. + guard completed, let self else { + completion?(false) + return + } + + if followingMarkers.isEmpty { + // If we don't have any more markers to play, then the marker sequence has completed. + completion?(completed) + } else { + play(markers: followingMarkers, completion: completion) + } + }) + } + + /// Stops the animation and resets the layer to its start frame. + /// + /// The completion closure will be called with `false` + open func stop() { + removeCurrentAnimation() + currentFrame = 0 + } + + /// Pauses the animation in its current state. + /// + /// The completion closure will be called with `false` + open func pause() { + pause(at: .currentFrame) + } + + /// Pauses the animation at a given state. + open func pause(at state: LottiePlaybackMode.PausedState) { + switch state { + case .currentFrame: + removeCurrentAnimation() + + case .progress(let animationProgressTime): + currentProgress = animationProgressTime + + case .frame(let animationFrameTime): + currentFrame = animationFrameTime + + case .time(let timeInterval): + currentTime = timeInterval + + case .marker(let name, let position): + guard let from = animation?.markerMap?[name] else { + return + } + + switch position { + case .start: + currentTime = from.frameTime + case .end: + currentTime = from.frameTime + from.durationFrameTime + } + } + + currentPlaybackMode = .paused(at: state) + } + + @available(*, deprecated, renamed: "setPlaybackMode(_:completion:)", message: "Will be removed in a future major release.") + open func play( + _ playbackMode: LottiePlaybackMode, + animationCompletionHandler: LottieCompletionBlock? = nil) + { + setPlaybackMode(playbackMode, completion: animationCompletionHandler) + } + + /// Applies the given `LottiePlaybackMode` to this layer. + /// - Parameter playbackMode: The playback mode to apply + /// - Parameter completion: A closure that is called after + /// an animation triggered by this method completes. + open func setPlaybackMode( + _ playbackMode: LottiePlaybackMode, + completion: LottieCompletionBlock? = nil) + { + switch playbackMode { + case .paused(at: let state): + pause(at: state) + + case .playing(let mode): + play(mode, completion: completion) + + case .progress(let progress): + pause(at: .progress(progress)) + + case .frame(let frame): + pause(at: .frame(frame)) + + case .time(let time): + pause(at: .time(time)) + + case .pause: + pause(at: .currentFrame) + + case .fromProgress(let from, let to, let loopMode): + play(.fromProgress(from, toProgress: to, loopMode: loopMode), completion: completion) + + case .fromFrame(let from, let to, let loopMode): + play(.fromFrame(from, toFrame: to, loopMode: loopMode), completion: completion) + + case .fromMarker(let from, let to, let playEndMarkerFrame, let loopMode): + play(.fromMarker(from, toMarker: to, playEndMarkerFrame: playEndMarkerFrame, loopMode: loopMode), completion: completion) + + case .marker(let name, let loopMode): + play(.marker(name, loopMode: loopMode), completion: completion) + + case .markers(let names): + play(.markers(names), completion: completion) + } + } + + /// Applies the given `LottiePlaybackMode` to this layer. + /// - Parameter playbackMode: The playback mode to apply + /// - Parameter completion: A closure that is called after + /// an animation triggered by this method completes. + open func play(_ playbackMode: LottiePlaybackMode.PlaybackMode, completion: LottieCompletionBlock? = nil) { + switch playbackMode { + case .fromProgress(let from, let to, let loopMode): + play( + fromProgress: from, + toProgress: to, + loopMode: loopMode, + completion: completion) + + case .fromFrame(let from, let to, let loopMode): + play( + fromFrame: from, + toFrame: to, + loopMode: loopMode, + completion: completion) + + case .fromMarker(let from, let to, let playEndMarkerFrame, let loopMode): + play( + fromMarker: from, + toMarker: to, + playEndMarkerFrame: playEndMarkerFrame, + loopMode: loopMode, + completion: completion) + + case .marker(let name, loopMode: let loopMode): + play(marker: name, loopMode: loopMode, completion: completion) + + case .markers(let names): + play(markers: names, completion: completion) + } + } + + // MARK: Public + + /// The current `LottiePlaybackMode` that is being used + public private(set) var currentPlaybackMode: LottiePlaybackMode? + + /// Value Providers that have been registered using `setValueProvider(_:keypath:)` + public private(set) var valueProviders = [AnimationKeypath: AnyValueProvider]() + + /// A closure called when the animation layer has been loaded. + /// Will inform the receiver the type of rendering engine that is used for the layer. + public var animationLayerDidLoad: ((_ animationLayer: LottieAnimationLayer, _ renderingEngine: RenderingEngineOption) -> Void)? + + /// The configuration that this `LottieAnimationView` uses when playing its animation + public var configuration: LottieConfiguration { + didSet { + if configuration.renderingEngine != oldValue.renderingEngine { + makeAnimationLayer(usingEngine: configuration.renderingEngine) + } + } + } + + /// The underlying CALayer created to display the content. + /// Use this property to change CALayer props like the content's transform, anchor point, etc. + public var animationLayer: CALayer? { rootAnimationLayer } + + public var screenScale: CGFloat { + didSet { + rootAnimationLayer?.renderScale = screenScale + } + } + + /// Describes the behavior of an AnimationView when the app is moved to the background. + /// + /// The default for the Main Thread animation engine is `pause`, + /// which pauses the animation when the application moves to + /// the background. This prevents the animation from consuming CPU + /// resources when not on-screen. The completion block is called with + /// `false` for completed. + /// + /// The default for the Core Animation engine is `continuePlaying`, + /// since the Core Animation engine does not have any CPU overhead. + public var backgroundBehavior: LottieBackgroundBehavior { + get { + let currentBackgroundBehavior = _backgroundBehavior ?? .default(for: currentRenderingEngine ?? .mainThread) + + if + currentRenderingEngine == .mainThread, + _backgroundBehavior == .continuePlaying + { + logger.assertionFailure(""" + `LottieBackgroundBehavior.continuePlaying` should not be used with the Main Thread + rendering engine, since this would waste CPU resources on playing an animation + that is not visible. Consider using a different background mode, or switching to + the Core Animation rendering engine (which does not have any CPU overhead). + """) + } + + return currentBackgroundBehavior + } + set { + _backgroundBehavior = newValue + } + } + + /// Sets the animation backing the animation layer. Setting this will clear the + /// layer's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + public var animation: LottieAnimation? { + didSet { + makeAnimationLayer(usingEngine: configuration.renderingEngine) + + if let animation { + animationLoaded?(self, animation) + } + } + } + + /// A closure that is called when `self.animation` is loaded. When setting this closure, + /// it is called immediately if `self.animation` is non-nil. + /// + /// When initializing a `LottieAnimationView`, the animation will either be loaded + /// synchronously (when loading a `LottieAnimation` from a .json file on disk) + /// or asynchronously (when loading a `DotLottieFile` from disk, or downloading + /// an animation from a URL). This closure is called in both cases once the + /// animation is loaded and applied, so can be a useful way to configure this + /// `LottieAnimationView` regardless of which initializer was used. For example: + /// + /// ``` + /// let animationView: LottieAnimationView + /// + /// if loadDotLottieFile { + /// // Loads the .lottie file asynchronously + /// animationView = LottieAnimationView(dotLottieName: "animation") + /// } else { + /// // Loads the .json file synchronously + /// animationView = LottieAnimationView(name: "animation") + /// } + /// + /// animationView.animationLoaded = { animationView, animation in + /// // If using a .lottie file, this is called once the file finishes loading. + /// // If using a .json file, this is called immediately (since the animation is loaded synchronously). + /// animationView.play() + /// } + /// ``` + public var animationLoaded: ((_ animationLayer: LottieAnimationLayer, _ animation: LottieAnimation) -> Void)? { + didSet { + if let animation { + animationLoaded?(self, animation) + } + } + } + + /// Sets the image provider for the animation layer. An image provider provides the + /// animation with its required image data. + /// + /// Setting this will cause the animation to reload its image contents. + public var imageProvider: AnimationImageProvider { + didSet { + rootAnimationLayer?.imageProvider = imageProvider.cachedImageProvider + reloadImages() + } + } + + /// Sets the text provider for animation layer. A text provider provides the + /// animation with values for text layers + public var textProvider: AnimationKeypathTextProvider { + didSet { + rootAnimationLayer?.textProvider = textProvider + } + } + + /// Sets the text provider for animation layer. A text provider provides the + /// animation with values for text layers + public var fontProvider: AnimationFontProvider { + didSet { + rootAnimationLayer?.fontProvider = fontProvider + } + } + + /// Whether or not the animation is masked to the bounds. Defaults to true. + public var maskAnimationToBounds = true { + didSet { + animationLayer?.masksToBounds = maskAnimationToBounds + } + } + + /// Returns `true` if the animation is currently playing. + public var isAnimationPlaying: Bool { + guard let animationLayer = rootAnimationLayer else { + return false + } + + if let valueFromLayer = animationLayer.isAnimationPlaying { + return valueFromLayer + } else { + return animationLayer.animation(forKey: activeAnimationName) != nil + } + } + + /// Sets the loop behavior for `play` calls. Defaults to `playOnce` + public var loopMode: LottieLoopMode = .playOnce { + didSet { + updateInFlightAnimation() + } + } + + /// When `true` the animation layer will rasterize its contents when not animating. + /// Rasterizing will improve performance of static animations. + /// + /// Note: this will not produce crisp results at resolutions above the animations natural resolution. + /// + /// Defaults to `false` + public var shouldRasterizeWhenIdle = false { + didSet { + updateRasterizationState() + } + } + + /// Sets the current animation time with a Progress Time + /// + /// Note: Setting this will stop the current animation, if any. + /// Note 2: If `animation` is nil, setting this will fallback to 0 + public var currentProgress: AnimationProgressTime { + set { + if let animation { + currentFrame = animation.frameTime(forProgress: newValue) + currentPlaybackMode = .paused(at: .progress(newValue)) + } else { + currentFrame = 0 + } + } + get { + if let animation { + animation.progressTime(forFrame: currentFrame) + } else { + 0 + } + } + } + + /// Sets the current animation time with a time in seconds. + /// + /// Note: Setting this will stop the current animation, if any. + /// Note 2: If `animation` is nil, setting this will fallback to 0 + public var currentTime: TimeInterval { + set { + if let animation { + currentFrame = animation.frameTime(forTime: newValue) + currentPlaybackMode = .paused(at: .time(newValue)) + } else { + currentFrame = 0 + } + } + get { + if let animation { + animation.time(forFrame: currentFrame) + } else { + 0 + } + } + } + + /// Sets the current animation time with a frame in the animations framerate. + /// + /// Note: Setting this will stop the current animation, if any. + public var currentFrame: AnimationFrameTime { + set { + removeCurrentAnimationIfNecessary() + updateAnimationFrame(newValue) + currentPlaybackMode = .paused(at: .frame(currentFrame)) + } + get { + rootAnimationLayer?.currentFrame ?? 0 + } + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationFrame: AnimationFrameTime { + isAnimationPlaying ? rootAnimationLayer?.presentation()?.currentFrame ?? currentFrame : currentFrame + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationProgress: AnimationProgressTime { + if let animation { + return animation.progressTime(forFrame: realtimeAnimationFrame) + } + return 0 + } + + /// Sets the speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat = 1 { + didSet { + updateInFlightAnimation() + } + } + + /// When `true` the animation will play back at the framerate encoded in the + /// `LottieAnimation` model. When `false` the animation will play at the framerate + /// of the device. + /// + /// Defaults to false + public var respectAnimationFrameRate = false { + didSet { + rootAnimationLayer?.respectAnimationFrameRate = respectAnimationFrameRate + } + } + + /// The rendering engine currently being used by this layer. + /// - This will only be `nil` in cases where the configuration is `automatic` + /// but a `RootAnimationLayer` hasn't been constructed yet + public var currentRenderingEngine: RenderingEngine? { + switch configuration.renderingEngine { + case .specific(let engine): + return engine + + case .automatic: + guard let animationLayer else { + return nil + } + + if animationLayer is CoreAnimationLayer { + return .coreAnimation + } else { + return .mainThread + } + } + } + + /// Whether or not the Main Thread rendering engine should use `forceDisplayUpdate()` + /// when rendering each individual frame. + /// - The main thread rendering engine implements optimizations to decrease the amount + /// of properties that have to be re-rendered on each frame. There are some cases + /// where this can result in bugs / incorrect behavior, so we allow it to be disabled. + /// - Forcing a full render on every frame will decrease performance, and is not recommended + /// except as a workaround to a bug in the main thread rendering engine. + /// - Has no effect when using the Core Animation rendering engine. + public var mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame = false { + didSet { + (rootAnimationLayer as? MainThreadAnimationLayer)?.forceDisplayUpdateOnEachFrame + = mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame + } + } + + /// Sets the lottie file backing the animation layer. Setting this will clear the + /// layer's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + /// The loopMode, animationSpeed and imageProvider will be set according + /// to lottie file settings + /// - Parameters: + /// - animationId: Internal animation id to play. Optional + /// Defaults to play first animation in file. + /// - dotLottieFile: Lottie file to play + public func loadAnimation( + _ animationId: String? = nil, + from dotLottieFile: DotLottieFile) + { + guard let dotLottieAnimation = dotLottieFile.animation(for: animationId) else { return } + loadAnimation(dotLottieAnimation) + } + + /// Sets the lottie file backing the animation layer. Setting this will clear the + /// layer's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + /// The loopMode, animationSpeed and imageProvider will be set according + /// to lottie file settings + /// - Parameters: + /// - atIndex: Internal animation index to play. + /// Defaults to play first animation in file. + /// - dotLottieFile: Lottie file to play + public func loadAnimation( + atIndex index: Int, + from dotLottieFile: DotLottieFile) + { + guard let dotLottieAnimation = dotLottieFile.animation(at: index) else { return } + loadAnimation(dotLottieAnimation) + } + + /// Reloads the images supplied to the animation from the `imageProvider` + public func reloadImages() { + rootAnimationLayer?.reloadImages() + } + + /// Forces the LottieAnimationView to redraw its contents. + public func forceDisplayUpdate() { + rootAnimationLayer?.forceDisplayUpdate() + } + + /// Sets a ValueProvider for the specified keypath. The value provider will be set + /// on all properties that match the keypath. + /// + /// Nearly all properties of a Lottie animation can be changed at runtime using a + /// combination of `Animation Keypaths` and `Value Providers`. + /// Setting a ValueProvider on a keypath will cause the animation to update its + /// contents and read the new Value Provider. + /// + /// A value provider provides a typed value on a frame by frame basis. + /// + /// - Parameter valueProvider: The new value provider for the properties. + /// - Parameter keypath: The keypath used to search for properties. + /// + /// Example: + /// ``` + /// /// A keypath that finds the color value for all `Fill 1` nodes. + /// let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color") + /// /// A Color Value provider that returns a reddish color. + /// let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1)) + /// /// Set the provider on the animationView. + /// animationView.setValueProvider(redValueProvider, keypath: fillKeypath) + /// ``` + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + guard let animationLayer = rootAnimationLayer else { return } + + valueProviders[keypath] = valueProvider + animationLayer.setValueProvider(valueProvider, keypath: keypath) + } + + /// Reads the value of a property specified by the Keypath. + /// Returns nil if no property is found. + /// + /// - Parameter for: The keypath used to search for the property. + /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + rootAnimationLayer?.getValue(for: keypath, atFrame: atFrame) + } + + /// Reads the original value of a property specified by the Keypath. + /// This will ignore any value providers and can be useful when implementing a value providers that makes change to the original value from the animation. + /// Returns nil if no property is found. + /// + /// - Parameter for: The keypath used to search for the property. + /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + public func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + rootAnimationLayer?.getOriginalValue(for: keypath, atFrame: atFrame) + } + + /// Logs all child keypaths. + public func logHierarchyKeypaths() { + rootAnimationLayer?.logHierarchyKeypaths() + } + + /// Computes and returns a list of all child keypaths in the current animation. + /// The returned list is the same as the log output of `logHierarchyKeypaths()` + public func allHierarchyKeypaths() -> [String] { + rootAnimationLayer?.allHierarchyKeypaths() ?? [] + } + + /// Converts a CGRect from the LottieAnimationView's coordinate space into the + /// coordinate space of the layer found at Keypath. + /// + /// If no layer is found, nil is returned + /// + /// - Parameter rect: The CGRect to convert. + /// - Parameter toLayerAt: The keypath used to find the layer. + public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? { + guard let animationLayer = rootAnimationLayer else { return nil } + guard let keypath else { + return convert(rect, to: animationLayer) + } + guard let sublayer = animationLayer.layer(for: keypath) else { + return nil + } + setNeedsLayout() + layoutIfNeeded() + forceDisplayUpdate() + return animationLayer.convert(rect, to: sublayer) + } + + /// Converts a CGPoint from the LottieAnimationView's coordinate space into the + /// coordinate space of the layer found at Keypath. + /// + /// If no layer is found, nil is returned + /// + /// - Parameter point: The CGPoint to convert. + /// - Parameter toLayerAt: The keypath used to find the layer. + public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? { + guard let animationLayer = rootAnimationLayer else { return nil } + guard let keypath else { + return convert(point, to: animationLayer) + } + guard let sublayer = animationLayer.layer(for: keypath) else { + return nil + } + setNeedsLayout() + layoutIfNeeded() + forceDisplayUpdate() + return animationLayer.convert(point, to: sublayer) + } + + /// Sets the enabled state of all animator nodes found with the keypath search. + /// This can be used to interactively enable / disable parts of the animation. + /// + /// - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree. + /// - Parameter keypath: The keypath used to find the node(s). + public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) { + guard let animationLayer = rootAnimationLayer else { return } + let nodes = animationLayer.animatorNodes(for: keypath) + if let nodes { + for node in nodes { + node.isEnabled = isEnabled + } + forceDisplayUpdate() + } + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Progress Time for the marker named. Returns nil if no marker found. + public func progressTime(forMarker named: String) -> AnimationProgressTime? { + guard let animation else { + return nil + } + return animation.progressTime(forMarker: named) + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Frame Time for the marker named. Returns nil if no marker found. + public func frameTime(forMarker named: String) -> AnimationFrameTime? { + guard let animation else { + return nil + } + return animation.frameTime(forMarker: named) + } + + /// Markers are a way to describe a point in time and a duration by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// - Returns: The duration frame time for the marker, or `nil` if no marker found. + public func durationFrameTime(forMarker named: String) -> AnimationFrameTime? { + guard let animation else { + return nil + } + return animation.durationFrameTime(forMarker: named) + } + + public func updateAnimationForBackgroundState() { + if let currentContext = animationContext { + switch backgroundBehavior { + case .stop: + removeCurrentAnimation() + updateAnimationFrame(currentContext.playFrom) + + case .pause: + removeCurrentAnimation() + + case .pauseAndRestore: + currentContext.closure.ignoreDelegate = true + removeCurrentAnimation() + /// Keep the stale context around for when the app enters the foreground. + animationContext = currentContext + + case .forceFinish: + removeCurrentAnimation() + updateAnimationFrame(currentContext.playTo) + + case .continuePlaying: + break + } + } + } + + public func updateAnimationForForegroundState(wasWaitingToPlayAnimation: Bool) { + if let currentContext = animationContext { + if wasWaitingToPlayAnimation { + addNewAnimationForContext(currentContext) + } else if backgroundBehavior == .pauseAndRestore { + /// Restore animation from saved state + updateInFlightAnimation() + } + } + } + + // MARK: Internal + + var rootAnimationLayer: RootAnimationLayer? = nil + + /// Context describing the animation that is currently playing in this `LottieAnimationView` + /// - When non-nil, an animation is currently playing in this layer. Otherwise, + /// the layer is paused on a specific frame. + fileprivate(set) var animationContext: AnimationContext? + + var hasAnimationContext: Bool { + animationContext != nil + } + + /// Set animation name from Interface Builder + var animationName: String? { + didSet { + animation = animationName.flatMap { + LottieAnimation.named($0, animationCache: nil) + } + } + } + + /// Updates the animation frame. Does not affect any current animations + func updateAnimationFrame(_ newFrame: CGFloat) { + // In performance tests, we have to wrap the animation layer setup + // in a `CATransaction` in order for the layers to be deallocated at + // the correct time. The `CATransaction`s in this method interfere + // with the ones managed by the performance test, and aren't actually + // necessary in a headless environment, so we disable them. + if TestHelpers.performanceTestsAreRunning { + rootAnimationLayer?.currentFrame = newFrame + rootAnimationLayer?.forceDisplayUpdate() + return + } + + CATransaction.begin() + CATransaction.setCompletionBlock { + self.rootAnimationLayer?.forceDisplayUpdate() + } + CATransaction.setDisableActions(true) + rootAnimationLayer?.currentFrame = newFrame + CATransaction.commit() + } + + /// Updates an in flight animation. + func updateInFlightAnimation() { + guard let animationContext else { return } + + guard animationContext.closure.animationState != .complete else { + // Tried to re-add an already completed animation. Cancel. + self.animationContext = nil + return + } + + /// Tell existing context to ignore its closure + animationContext.closure.ignoreDelegate = true + + /// Make a new context, stealing the completion block from the previous. + let newContext = AnimationContext( + playFrom: animationContext.playFrom, + playTo: animationContext.playTo, + closure: animationContext.closure.completionBlock) + + /// Remove current animation, and freeze the current frame. + let pauseFrame = realtimeAnimationFrame + rootAnimationLayer?.removeAnimation(forKey: activeAnimationName) + rootAnimationLayer?.currentFrame = pauseFrame + + addNewAnimationForContext(newContext) + } + + func updateRasterizationState() { + if isAnimationPlaying { + animationLayer?.shouldRasterize = false + } else { + animationLayer?.shouldRasterize = shouldRasterizeWhenIdle + } + } + + func loadAnimation(_ animationSource: LottieAnimationSource?) { + switch animationSource { + case .lottieAnimation(let animation): + self.animation = animation + case .dotLottieFile(let dotLottieFile): + loadAnimation(from: dotLottieFile) + case nil: + animation = nil + } + } + + // MARK: Fileprivate + + fileprivate var _activeAnimationName: String = LottieAnimationLayer.animationName + fileprivate var animationID = 0 + + fileprivate var activeAnimationName: String { + switch rootAnimationLayer?.primaryAnimationKey { + case .specific(let animationKey): + animationKey + case .managed, nil: + _activeAnimationName + } + } + + /// Stops the current in flight animation and freezes the animation in its current state. + fileprivate func removeCurrentAnimation() { + guard animationContext != nil else { return } + let pauseFrame = realtimeAnimationFrame + animationLayer?.removeAnimation(forKey: activeAnimationName) + updateAnimationFrame(pauseFrame) + animationContext = nil + } + + fileprivate func makeAnimationLayer(usingEngine renderingEngine: RenderingEngineOption) { + /// Disable the default implicit crossfade animation Core Animation creates + /// when adding or removing sublayers. + actions = ["sublayers": NSNull()] + + /// Remove current animation if any + removeCurrentAnimation() + + if let oldAnimation = animationLayer { + oldAnimation.removeFromSuperlayer() + rootAnimationLayer = nil + } + + guard let animation else { + return + } + let rootAnimationLayer: RootAnimationLayer? = + switch renderingEngine { + case .automatic: + makeAutomaticEngineLayer(for: animation) + case .specific(.coreAnimation): + makeCoreAnimationLayer(for: animation) + case .specific(.mainThread): + makeMainThreadAnimationLayer(for: animation) + } + + guard let animationLayer = rootAnimationLayer else { + return + } + + animationLayer.lottieAnimationLayer = self + + for (keypath, valueProvider) in valueProviders { + animationLayer.setValueProvider(valueProvider, keypath: keypath) + } + + animationLayerDidLoad?(self, renderingEngine) + + animationLayer.renderScale = screenScale + + addSublayer(animationLayer) + self.rootAnimationLayer = animationLayer + reloadImages() + animationLayer.setNeedsDisplay() + setNeedsLayout() + currentFrame = CGFloat(animation.startFrame) + } + + fileprivate func makeMainThreadAnimationLayer(for animation: LottieAnimation) -> MainThreadAnimationLayer { + let mainThreadAnimationLayer = MainThreadAnimationLayer( + animation: animation, + imageProvider: imageProvider.cachedImageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + maskAnimationToBounds: maskAnimationToBounds, + logger: logger) + + mainThreadAnimationLayer.forceDisplayUpdateOnEachFrame = mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame + return mainThreadAnimationLayer + } + + fileprivate func makeCoreAnimationLayer(for animation: LottieAnimation) -> CoreAnimationLayer? { + do { + let coreAnimationLayer = try CoreAnimationLayer( + animation: animation, + imageProvider: imageProvider.cachedImageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + maskAnimationToBounds: maskAnimationToBounds, + compatibilityTrackerMode: .track, + logger: logger) + + coreAnimationLayer.didSetUpAnimation = { [logger] compatibilityIssues in + logger.assert( + compatibilityIssues.isEmpty, + "Encountered Core Animation compatibility issues while setting up animation:\n" + + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n\n" + + """ + This animation cannot be rendered correctly by the Core Animation engine. + To resolve this issue, you can use `RenderingEngineOption.automatic`, which automatically falls back + to the Main Thread rendering engine when necessary, or just use `RenderingEngineOption.mainThread`. + + """) + } + + return coreAnimationLayer + } catch { + // This should never happen, because we initialize the `CoreAnimationLayer` with + // `CompatibilityTracker.Mode.track` (which reports errors in `didSetUpAnimation`, + // not by throwing). + logger.assertionFailure("Encountered unexpected error \(error)") + return nil + } + } + + fileprivate func makeAutomaticEngineLayer(for animation: LottieAnimation) -> CoreAnimationLayer? { + do { + // Attempt to set up the Core Animation layer. This can either throw immediately in `init`, + // or throw an error later in `CALayer.display()` that will be reported in `didSetUpAnimation`. + let coreAnimationLayer = try CoreAnimationLayer( + animation: animation, + imageProvider: imageProvider.cachedImageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + maskAnimationToBounds: maskAnimationToBounds, + compatibilityTrackerMode: .abort, + logger: logger) + + coreAnimationLayer.didSetUpAnimation = { [weak self] issues in + self?.automaticEngineLayerDidSetUpAnimation(issues) + } + + return coreAnimationLayer + } catch { + if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error { + automaticEngineLayerDidSetUpAnimation([compatibilityIssue]) + } else { + // This should never happen, because we expect `CoreAnimationLayer` to only throw + // `CompatibilityTracker.Error.encounteredCompatibilityIssue` errors. + logger.assertionFailure("Encountered unexpected error \(error)") + automaticEngineLayerDidSetUpAnimation([]) + } + + return nil + } + } + + /// Handles any compatibility issues with the Core Animation engine + /// by falling back to the Main Thread engine + fileprivate func automaticEngineLayerDidSetUpAnimation(_ compatibilityIssues: [CompatibilityIssue]) { + // If there weren't any compatibility issues, then there's nothing else to do + if compatibilityIssues.isEmpty { + return + } + + logger.warn( + "Encountered Core Animation compatibility issue while setting up animation:\n" + + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n" + + """ + This animation may have additional compatibility issues, but animation setup was cancelled early to avoid wasted work. + + Automatically falling back to Main Thread rendering engine. This fallback comes with some additional performance + overhead, which can be reduced by manually specifying that this animation should always use the Main Thread engine. + + """) + + let animationContext = animationContext + let currentFrame = currentFrame + + // Disable the completion handler delegate before tearing down the `CoreAnimationLayer` + // and building the `MainThreadAnimationLayer`. Otherwise deinitializing the + // `CoreAnimationLayer` would trigger the animation completion handler even though + // the animation hasn't even started playing yet. + animationContext?.closure.ignoreDelegate = true + + makeAnimationLayer(usingEngine: .mainThread) + + // Set up the Main Thread animation layer using the same configuration that + // was being used by the previous Core Animation layer + self.currentFrame = currentFrame + + if let animationContext { + // `AnimationContext.closure` (`AnimationCompletionDelegate`) is a reference type + // that is the animation layer's `CAAnimationDelegate`, and holds a reference to + // the animation layer. Reusing a single instance across different animation layers + // can cause the animation setup to fail, so we create a copy of the `animationContext`: + addNewAnimationForContext(AnimationContext( + playFrom: animationContext.playFrom, + playTo: animationContext.playTo, + closure: animationContext.closure.completionBlock)) + } + } + + /// Removes the current animation and pauses the animation at the current frame + /// if necessary before setting up a new animation. + /// - This is not necessary with the Core Animation engine, and skipping + /// this step lets us avoid building the animations twice (once paused + /// and once again playing) + /// - This method should only be called immediately before setting up another + /// animation -- otherwise this LottieAnimationView could be put in an inconsistent state. + fileprivate func removeCurrentAnimationIfNecessary() { + switch currentRenderingEngine { + case .mainThread: + removeCurrentAnimation() + case .coreAnimation, nil: + // We still need to remove the `animationContext`, since it should only be present + // when an animation is actually playing. Without this calling `removeCurrentAnimationIfNecessary()` + // and then setting the animation to a specific paused frame would put this + // `LottieAnimationView` in an inconsistent state. + animationContext = nil + } + } + + /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits. + fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) { + guard let animationlayer = rootAnimationLayer, let animation else { + return + } + + self.animationContext = animationContext + + animationID = animationID + 1 + _activeAnimationName = LottieAnimationLayer.animationName + String(animationID) + + if let coreAnimationLayer = animationlayer as? CoreAnimationLayer { + var animationContext = animationContext + + // Core Animation doesn't natively support negative speed values, + // so instead we can swap `playFrom` / `playTo` + if animationSpeed < 0 { + let temp = animationContext.playFrom + animationContext.playFrom = animationContext.playTo + animationContext.playTo = temp + } + + var timingConfiguration = CoreAnimationLayer.CAMediaTimingConfiguration( + autoreverses: loopMode.caAnimationConfiguration.autoreverses, + repeatCount: loopMode.caAnimationConfiguration.repeatCount, + speed: abs(Float(animationSpeed))) + + // The animation should start playing from the `currentFrame`, + // if `currentFrame` is included in the time range being played. + let lowerBoundTime = min(animationContext.playFrom, animationContext.playTo) + let upperBoundTime = max(animationContext.playFrom, animationContext.playTo) + if (lowerBoundTime ..< upperBoundTime).contains(round(currentFrame)) { + // We have to configure this differently depending on the loop mode: + switch loopMode { + // When playing exactly once (and not looping), we can just set the + // `playFrom` time to be the `currentFrame`. Since the animation duration + // is based on `playFrom` and `playTo`, this automatically truncates the + // duration (so the animation stops playing at `playFrom`). + // - Don't do this if the animation is already at that frame + // (e.g. playing from 100% to 0% when the animation is already at 0%) + // since that would cause the animation to not play at all. + case .playOnce: + if animationContext.playTo != currentFrame { + animationContext.playFrom = currentFrame + } + + // When looping, we specifically _don't_ want to affect the duration of the animation, + // since that would affect the duration of all subsequent loops. We just want to adjust + // the duration of the _first_ loop. Instead of setting `playFrom`, we just add a `timeOffset` + // so the first loop begins at `currentTime` but all subsequent loops are the standard duration. + default: + if animationSpeed < 0 { + timingConfiguration.timeOffset = animation.time(forFrame: animationContext.playFrom) - currentTime + } else { + timingConfiguration.timeOffset = currentTime - animation.time(forFrame: animationContext.playFrom) + } + } + } + + // If attempting to play a zero-duration animation, just pause on that single frame instead + if animationContext.playFrom == animationContext.playTo { + currentFrame = animationContext.playTo + animationContext.closure.completionBlock?(true) + return + } + + coreAnimationLayer.playAnimation(configuration: .init( + animationContext: animationContext, + timingConfiguration: timingConfiguration)) + + return + } + + /// At this point there is no animation on animationLayer and its state is set. + + let framerate = animation.framerate + + let playFrom = animationContext.playFrom.clamp(animation.startFrame, animation.endFrame) + let playTo = animationContext.playTo.clamp(animation.startFrame, animation.endFrame) + + let duration = ((max(playFrom, playTo) - min(playFrom, playTo)) / CGFloat(framerate)) + + let playingForward: Bool = + ( + (animationSpeed > 0 && playFrom < playTo) || + (animationSpeed < 0 && playTo < playFrom)) + + var startFrame = currentFrame.clamp(min(playFrom, playTo), max(playFrom, playTo)) + if startFrame == playTo { + startFrame = playFrom + } + + let timeOffset: TimeInterval = playingForward + ? Double(startFrame - min(playFrom, playTo)) / framerate + : Double(max(playFrom, playTo) - startFrame) / framerate + + let layerAnimation = CABasicAnimation(keyPath: "currentFrame") + layerAnimation.fromValue = playFrom + layerAnimation.toValue = playTo + layerAnimation.speed = Float(animationSpeed) + layerAnimation.duration = TimeInterval(duration) + layerAnimation.fillMode = CAMediaTimingFillMode.both + layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount + layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses + + layerAnimation.isRemovedOnCompletion = false + if timeOffset != 0 { + let currentLayerTime = convertTime(CACurrentMediaTime(), from: nil) + layerAnimation.beginTime = currentLayerTime - (timeOffset * 1 / Double(abs(animationSpeed))) + } + layerAnimation.delegate = animationContext.closure + animationContext.closure.animationLayer = animationlayer + animationContext.closure.animationKey = activeAnimationName + + animationlayer.add(layerAnimation, forKey: activeAnimationName) + updateRasterizationState() + } + + // MARK: Private + + static private let animationName = "Lottie" + + private let logger: LottieLogger + + /// The `LottieBackgroundBehavior` that was specified manually by setting `self.backgroundBehavior` + private var _backgroundBehavior: LottieBackgroundBehavior? + + /// Whether or not the current animation should be overridden with + /// the marker matching the current "reduced motion" mode. + private var shouldOverrideWithReducedMotionAnimation: Bool { + reducedMotionMarker != nil + } + + /// The marker that corresponds to the current "reduced motion" mode. + private var reducedMotionMarker: Marker? { + switch configuration.reducedMotionOption.currentReducedMotionMode { + case .standardMotion: + nil + case .reducedMotion: + animation?.reducedMotionMarker + } + } + + private func loadAnimation(_ dotLottieAnimation: DotLottieFile.Animation) { + loopMode = dotLottieAnimation.configuration.loopMode + animationSpeed = CGFloat(dotLottieAnimation.configuration.speed) + + if let imageProvider = dotLottieAnimation.configuration.imageProvider { + self.imageProvider = imageProvider + } + + animation = dotLottieAnimation.animation + } + + /// Plays the marker that corresponds to the current "reduced motion" mode if present. + private func playReducedMotionAnimation(completion: LottieCompletionBlock?) { + guard let reducedMotionMarker else { return } + + // `play(marker:)` calls the `play(fromFrame:toFrame:)` method which calls this + // `playReducedMotionAnimation` method when `shouldOverrideWithReducedMotionAnimation` + // is `true`. To prevent infinite recursion, disable the reduced motion functionality + // until the end of this function. + let currentConfiguration = configuration + configuration.reducedMotionOption = .standardMotion + defer { configuration = currentConfiguration } + + play(marker: reducedMotionMarker.name, completion: completion) + } + +} + +// MARK: - LottieLoopMode + caAnimationConfiguration + +extension LottieLoopMode { + /// The `CAAnimation` configuration that reflects this mode + var caAnimationConfiguration: (repeatCount: Float, autoreverses: Bool) { + switch self { + case .playOnce: + (repeatCount: 1, autoreverses: false) + case .loop: + (repeatCount: .greatestFiniteMagnitude, autoreverses: false) + case .autoReverse: + (repeatCount: .greatestFiniteMagnitude, autoreverses: true) + case .repeat(let amount): + (repeatCount: amount, autoreverses: false) + case .repeatBackwards(let amount): + (repeatCount: amount, autoreverses: true) + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationView.swift new file mode 100644 index 0000000000000000000000000000000000000000..0ddec0ba3c84bf0eee2893b32ad134353b2a0597 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationView.swift @@ -0,0 +1,1054 @@ +// +// LottieAnimationView.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import QuartzCore + +// MARK: - LottieBackgroundBehavior + +/// Describes the behavior of an AnimationView when the app is moved to the background. +public enum LottieBackgroundBehavior { + /// Stop the animation and reset it to the beginning of its current play time. The completion block is called. + case stop + + /// Pause the animation in its current state. The completion block is called. + case pause + + /// Pause the animation and restart it when the application moves to the foreground. + /// The completion block is stored and called when the animation completes. + /// - This is the default when using the Main Thread rendering engine. + case pauseAndRestore + + /// Stops the animation and sets it to the end of its current play time. The completion block is called. + case forceFinish + + /// The animation continues playing in the background. + /// - This is the default when using the Core Animation rendering engine. + /// Playing an animation using the Core Animation engine doesn't come with any CPU overhead, + /// so using `.continuePlaying` avoids the need to stop and then resume the animation + /// (which does come with some CPU overhead). + /// - This mode should not be used with the Main Thread rendering engine. + case continuePlaying + + // MARK: Public + + /// The default background behavior, based on the rendering engine being used to play the animation. + /// - Playing an animation using the Main Thread rendering engine comes with CPU overhead, + /// so the animation should be paused or stopped when the `LottieAnimationView` is not visible. + /// - Playing an animation using the Core Animation rendering engine does not come with any + /// CPU overhead, so these animations do not need to be paused in the background. + public static func `default`(for renderingEngine: RenderingEngine) -> LottieBackgroundBehavior { + switch renderingEngine { + case .mainThread: + .pauseAndRestore + case .coreAnimation: + .continuePlaying + } + } +} + +// MARK: - LottieLoopMode + +/// Defines animation loop behavior +public enum LottieLoopMode: Hashable { + /// Animation is played once then stops. + case playOnce + /// Animation will loop from beginning to end until stopped. + case loop + /// Animation will play forward, then backwards and loop until stopped. + case autoReverse + /// Animation will loop from beginning to end up to defined amount of times. + case `repeat`(Float) + /// Animation will play forward, then backwards a defined amount of times. + case repeatBackwards(Float) +} + +// MARK: Equatable + +extension LottieLoopMode: Equatable { + public static func == (lhs: LottieLoopMode, rhs: LottieLoopMode) -> Bool { + switch (lhs, rhs) { + case (.repeat(let lhsAmount), .repeat(let rhsAmount)), + (.repeatBackwards(let lhsAmount), .repeatBackwards(let rhsAmount)): + lhsAmount == rhsAmount + case (.playOnce, .playOnce), + (.loop, .loop), + (.autoReverse, .autoReverse): + true + default: + false + } + } +} + +// MARK: - LottieAnimationView + +/// A UIView subclass for rendering Lottie animations. +/// - Also available as a SwiftUI view (`LottieView`) and a CALayer subclass (`LottieAnimationLayer`) +@IBDesignable +open class LottieAnimationView: LottieAnimationViewBase { + + // MARK: Lifecycle + + // MARK: - Public (Initializers) + + /// Initializes an AnimationView with an animation. + public init( + animation: LottieAnimation?, + imageProvider: AnimationImageProvider? = nil, + textProvider: AnimationKeypathTextProvider = DefaultTextProvider(), + fontProvider: AnimationFontProvider = DefaultFontProvider(), + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + lottieAnimationLayer = LottieAnimationLayer( + animation: animation, + imageProvider: imageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + configuration: configuration, + logger: logger) + self.logger = logger + super.init(frame: .zero) + commonInit() + if let animation { + frame = animation.bounds + } + } + + /// Initializes an AnimationView with a .lottie file. + public init( + dotLottie: DotLottieFile?, + animationId: String? = nil, + textProvider: AnimationKeypathTextProvider = DefaultTextProvider(), + fontProvider: AnimationFontProvider = DefaultFontProvider(), + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + lottieAnimationLayer = LottieAnimationLayer( + dotLottie: dotLottie, + animationId: animationId, + textProvider: textProvider, + fontProvider: fontProvider, + configuration: configuration, + logger: logger) + self.logger = logger + super.init(frame: .zero) + commonInit() + if let animation { + frame = animation.bounds + } + } + + public init( + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + lottieAnimationLayer = LottieAnimationLayer(configuration: configuration, logger: logger) + self.logger = logger + super.init(frame: .zero) + commonInit() + } + + public override init(frame: CGRect) { + lottieAnimationLayer = LottieAnimationLayer( + animation: nil, + imageProvider: BundleImageProvider(bundle: Bundle.main, searchPath: nil), + textProvider: DefaultTextProvider(), + fontProvider: DefaultFontProvider(), + configuration: .shared, + logger: .shared) + logger = .shared + super.init(frame: frame) + commonInit() + } + + required public init?(coder aDecoder: NSCoder) { + lottieAnimationLayer = LottieAnimationLayer( + animation: nil, + imageProvider: BundleImageProvider(bundle: Bundle.main, searchPath: nil), + textProvider: DefaultTextProvider(), + fontProvider: DefaultFontProvider(), + configuration: .shared, + logger: .shared) + logger = .shared + super.init(coder: aDecoder) + commonInit() + } + + convenience init( + animationSource: LottieAnimationSource?, + imageProvider: AnimationImageProvider? = nil, + textProvider: AnimationKeypathTextProvider = DefaultTextProvider(), + fontProvider: AnimationFontProvider = DefaultFontProvider(), + configuration: LottieConfiguration = .shared, + logger: LottieLogger = .shared) + { + switch animationSource { + case .lottieAnimation(let animation): + self.init( + animation: animation, + imageProvider: imageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + configuration: configuration, + logger: logger) + + case .dotLottieFile(let dotLottieFile): + self.init( + dotLottie: dotLottieFile, + textProvider: textProvider, + fontProvider: fontProvider, + configuration: configuration, + logger: logger) + + case nil: + self.init( + animation: nil, + imageProvider: imageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + configuration: configuration, + logger: logger) + } + } + + // MARK: Open + + /// Applies the given `LottiePlaybackMode` to this layer. + /// - Parameter completion: A closure that is called after + /// an animation triggered by this method completes. + open func play(_ mode: LottiePlaybackMode.PlaybackMode, completion: LottieCompletionBlock? = nil) { + lottieAnimationLayer.play(mode, completion: completion) + } + + /// Plays the animation from its current state to the end. + /// + /// - Parameter completion: An optional completion closure to be called when the animation completes playing. + open func play(completion: LottieCompletionBlock? = nil) { + lottieAnimationLayer.play(completion: completion) + } + + /// Plays the animation from a progress (0-1) to a progress (0-1). + /// + /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress. + /// - Parameter toProgress: The end progress of the animation. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + fromProgress: AnimationProgressTime? = nil, + toProgress: AnimationProgressTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.play(fromProgress: fromProgress, toProgress: toProgress, loopMode: loopMode, completion: completion) + } + + /// Plays the animation from a start frame to an end frame in the animation's framerate. + /// + /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame. + /// - Parameter toFrame: The end frame of the animation. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + fromFrame: AnimationFrameTime? = nil, + toFrame: AnimationFrameTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.play(fromFrame: fromFrame, toFrame: toFrame, loopMode: loopMode, completion: completion) + } + + /// Plays the animation from a named marker to another marker. + /// + /// Markers are point in time that are encoded into the Animation data and assigned + /// a name. + /// + /// NOTE: If markers are not found the play command will exit. + /// + /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the + /// animation will start at the current progress. + /// - Parameter toMarker: The end marker for the animation playback. + /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the + /// end marker represents the end of the section to play, it should be to true. If the provided end marker + /// represents the beginning of the next section, it should be false. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + fromMarker: String? = nil, + toMarker: String, + playEndMarkerFrame: Bool = true, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.play( + fromMarker: fromMarker, + toMarker: toMarker, + playEndMarkerFrame: playEndMarkerFrame, + loopMode: loopMode, + completion: completion) + } + + /// Plays the animation from a named marker to the end of the marker's duration. + /// + /// A marker is a point in time with an associated duration that is encoded into the + /// animation data and assigned a name. + /// + /// NOTE: If marker is not found the play command will exit. + /// + /// - Parameter marker: The start marker for the animation playback. + /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + marker: String, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.play(marker: marker, loopMode: loopMode, completion: completion) + } + + /// Plays the given markers sequentially in order. + /// + /// A marker is a point in time with an associated duration that is encoded into the + /// animation data and assigned a name. Multiple markers can be played sequentially + /// to create programmable animations. + /// + /// If a marker is not found, it will be skipped. + /// + /// If a marker doesn't have a duration value, it will play with a duration of 0 + /// (effectively being skipped). + /// + /// If another animation is played (by calling any `play` method) while this + /// marker sequence is playing, the marker sequence will be cancelled. + /// + /// - Parameter markers: The list of markers to play sequentially. + /// - Parameter completion: An optional completion closure to be called when the animation stops. + open func play( + markers: [String], + completion: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.play(markers: markers, completion: completion) + } + + /// Stops the animation and resets the view to its start frame. + /// + /// The completion closure will be called with `false` + open func stop() { + lottieAnimationLayer.stop() + } + + /// Pauses the animation in its current state. + /// + /// The completion closure will be called with `false` + open func pause() { + lottieAnimationLayer.pause() + } + + @available(*, deprecated, renamed: "setPlaybackMode(_:completion:)", message: "Will be removed in a future major release.") + open func play( + _ playbackMode: LottiePlaybackMode, + animationCompletionHandler: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.setPlaybackMode(playbackMode, completion: animationCompletionHandler) + } + + /// Applies the given `LottiePlaybackMode` to this layer. + /// - Parameter completion: A closure that is called after + /// an animation triggered by this method completes. + open func setPlaybackMode( + _ playbackMode: LottiePlaybackMode, + completion: LottieCompletionBlock? = nil) + { + lottieAnimationLayer.setPlaybackMode(playbackMode, completion: completion) + } + + // MARK: Public + + /// Whether or not transform and position changes of the view should animate alongside + /// any existing animation context. + /// - Defaults to `true` which will grab the current animation context and animate position and + /// transform changes matching the current context's curve and duration. + /// `false` will cause transform and position changes to happen unanimated + public var animateLayoutChangesWithCurrentCoreAnimationContext = true + + /// The configuration that this `LottieAnimationView` uses when playing its animation + public var configuration: LottieConfiguration { + get { lottieAnimationLayer.configuration } + set { lottieAnimationLayer.configuration = newValue } + } + + /// Value Providers that have been registered using `setValueProvider(_:keypath:)` + public var valueProviders: [AnimationKeypath: AnyValueProvider] { + lottieAnimationLayer.valueProviders + } + + /// Describes the behavior of an AnimationView when the app is moved to the background. + /// + /// The default for the Main Thread animation engine is `pause`, + /// which pauses the animation when the application moves to + /// the background. This prevents the animation from consuming CPU + /// resources when not on-screen. The completion block is called with + /// `false` for completed. + /// + /// The default for the Core Animation engine is `continuePlaying`, + /// since the Core Animation engine does not have any CPU overhead. + public var backgroundBehavior: LottieBackgroundBehavior { + get { lottieAnimationLayer.backgroundBehavior } + set { lottieAnimationLayer.backgroundBehavior = newValue } + } + + /// Sets the animation backing the animation view. Setting this will clear the + /// view's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + public var animation: LottieAnimation? { + get { lottieAnimationLayer.animation } + set { lottieAnimationLayer.animation = newValue } + } + + /// A closure that is called when `self.animation` is loaded. When setting this closure, + /// it is called immediately if `self.animation` is non-nil. + /// + /// When initializing a `LottieAnimationView`, the animation will either be loaded + /// synchronously (when loading a `LottieAnimation` from a .json file on disk) + /// or asynchronously (when loading a `DotLottieFile` from disk, or downloading + /// an animation from a URL). This closure is called in both cases once the + /// animation is loaded and applied, so can be a useful way to configure this + /// `LottieAnimationView` regardless of which initializer was used. For example: + /// + /// ``` + /// let animationView: LottieAnimationView + /// + /// if loadDotLottieFile { + /// // Loads the .lottie file asynchronously + /// animationView = LottieAnimationView(dotLottieName: "animation") + /// } else { + /// // Loads the .json file synchronously + /// animationView = LottieAnimationView(name: "animation") + /// } + /// + /// animationView.animationLoaded = { animationView, animation in + /// // If using a .lottie file, this is called once the file finishes loading. + /// // If using a .json file, this is called immediately (since the animation is loaded synchronously). + /// animationView.play() + /// } + /// ``` + public var animationLoaded: ((_ animationView: LottieAnimationView, _ animation: LottieAnimation) -> Void)? { + didSet { + if let animation { + animationLoaded?(self, animation) + } + } + } + + /// Sets the image provider for the animation view. An image provider provides the + /// animation with its required image data. + /// + /// Setting this will cause the animation to reload its image contents. + public var imageProvider: AnimationImageProvider { + get { lottieAnimationLayer.imageProvider } + set { lottieAnimationLayer.imageProvider = newValue } + } + + /// Sets the text provider for animation view. A text provider provides the + /// animation with values for text layers + public var textProvider: AnimationKeypathTextProvider { + get { lottieAnimationLayer.textProvider } + set { lottieAnimationLayer.textProvider = newValue } + } + + /// Sets the text provider for animation view. A text provider provides the + /// animation with values for text layers + public var fontProvider: AnimationFontProvider { + get { lottieAnimationLayer.fontProvider } + set { lottieAnimationLayer.fontProvider = newValue } + } + + /// Whether or not the animation is masked to the bounds. Defaults to true. + public var maskAnimationToBounds: Bool { + get { lottieAnimationLayer.maskAnimationToBounds } + set { lottieAnimationLayer.maskAnimationToBounds = newValue } + } + + /// Returns `true` if the animation is currently playing. + public var isAnimationPlaying: Bool { + lottieAnimationLayer.isAnimationPlaying + } + + /// Returns `true` if the animation will start playing when this view is added to a window. + public var isAnimationQueued: Bool { + lottieAnimationLayer.hasAnimationContext && waitingToPlayAnimation + } + + /// Sets the loop behavior for `play` calls. Defaults to `playOnce` + public var loopMode: LottieLoopMode { + get { lottieAnimationLayer.loopMode } + set { lottieAnimationLayer.loopMode = newValue } + } + + /// When `true` the animation view will rasterize its contents when not animating. + /// Rasterizing will improve performance of static animations. + /// + /// Note: this will not produce crisp results at resolutions above the animations natural resolution. + /// + /// Defaults to `false` + public var shouldRasterizeWhenIdle: Bool { + get { lottieAnimationLayer.shouldRasterizeWhenIdle } + set { lottieAnimationLayer.shouldRasterizeWhenIdle = newValue } + } + + /// Sets the current animation time with a Progress Time + /// + /// Note: Setting this will stop the current animation, if any. + /// Note 2: If `animation` is nil, setting this will fallback to 0 + public var currentProgress: AnimationProgressTime { + get { lottieAnimationLayer.currentProgress } + set { lottieAnimationLayer.currentProgress = newValue } + } + + /// Sets the current animation time with a time in seconds. + /// + /// Note: Setting this will stop the current animation, if any. + /// Note 2: If `animation` is nil, setting this will fallback to 0 + public var currentTime: TimeInterval { + get { lottieAnimationLayer.currentTime } + set { lottieAnimationLayer.currentTime = newValue } + } + + /// Sets the current animation time with a frame in the animations framerate. + /// + /// Note: Setting this will stop the current animation, if any. + public var currentFrame: AnimationFrameTime { + get { lottieAnimationLayer.currentFrame } + set { lottieAnimationLayer.currentFrame = newValue } + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationFrame: AnimationFrameTime { + lottieAnimationLayer.realtimeAnimationFrame + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationProgress: AnimationProgressTime { + lottieAnimationLayer.realtimeAnimationProgress + } + + /// Sets the speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat { + get { lottieAnimationLayer.animationSpeed } + set { lottieAnimationLayer.animationSpeed = newValue } + } + + /// When `true` the animation will play back at the framerate encoded in the + /// `LottieAnimation` model. When `false` the animation will play at the framerate + /// of the device. + /// + /// Defaults to false + public var respectAnimationFrameRate: Bool { + get { lottieAnimationLayer.respectAnimationFrameRate } + set { lottieAnimationLayer.respectAnimationFrameRate = newValue } + } + + /// Controls the cropping of an Animation. Setting this property will crop the animation + /// to the current views bounds by the viewport frame. The coordinate space is specified + /// in the animation's coordinate space. + /// + /// Animatable. + public var viewportFrame: CGRect? { + didSet { + // This is really ugly, but is needed to trigger a layout pass within an animation block. + // Typically this happens automatically, when layout objects are UIView based. + // The animation layer is a CALayer which will not implicitly grab the animation + // duration of a UIView animation block. + // + // By setting bounds and then resetting bounds the UIView animation block's + // duration and curve are captured and added to the layer. This is used in the + // layout block to animate the animationLayer's position and size. + let rect = bounds + bounds = CGRect.zero + bounds = rect + setNeedsLayout() + } + } + + override public var intrinsicContentSize: CGSize { + if let animation = lottieAnimationLayer.animation { + return animation.bounds.size + } + return .zero + } + + /// The rendering engine currently being used by this view. + /// - This will only be `nil` in cases where the configuration is `automatic` + /// but a `RootAnimationLayer` hasn't been constructed yet + public var currentRenderingEngine: RenderingEngine? { + lottieAnimationLayer.currentRenderingEngine + } + + /// The current `LottiePlaybackMode` that is being used + public var currentPlaybackMode: LottiePlaybackMode? { + lottieAnimationLayer.currentPlaybackMode + } + + /// Whether or not the Main Thread rendering engine should use `forceDisplayUpdate()` + /// when rendering each individual frame. + /// - The main thread rendering engine implements optimizations to decrease the amount + /// of properties that have to be re-rendered on each frame. There are some cases + /// where this can result in bugs / incorrect behavior, so we allow it to be disabled. + /// - Forcing a full render on every frame will decrease performance, and is not recommended + /// except as a workaround to a bug in the main thread rendering engine. + /// - Has no effect when using the Core Animation rendering engine. + public var mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame: Bool { + get { lottieAnimationLayer.mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame } + set { lottieAnimationLayer.mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame = newValue } + } + + /// Sets the lottie file backing the animation view. Setting this will clear the + /// view's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + /// The loopMode, animationSpeed and imageProvider will be set according + /// to lottie file settings + /// - Parameters: + /// - animationId: Internal animation id to play. Optional + /// Defaults to play first animation in file. + /// - dotLottieFile: Lottie file to play + public func loadAnimation( + _ animationId: String? = nil, + from dotLottieFile: DotLottieFile) + { + lottieAnimationLayer.loadAnimation(animationId, from: dotLottieFile) + } + + /// Sets the lottie file backing the animation view. Setting this will clear the + /// view's contents, completion blocks and current state. The new animation will + /// be loaded up and set to the beginning of its timeline. + /// The loopMode, animationSpeed and imageProvider will be set according + /// to lottie file settings + /// - Parameters: + /// - atIndex: Internal animation index to play. Optional + /// Defaults to play first animation in file. + /// - dotLottieFile: Lottie file to play + public func loadAnimation( + atIndex index: Int, + from dotLottieFile: DotLottieFile) + { + lottieAnimationLayer.loadAnimation(atIndex: index, from: dotLottieFile) + } + + /// Reloads the images supplied to the animation from the `imageProvider` + public func reloadImages() { + lottieAnimationLayer.reloadImages() + } + + /// Forces the LottieAnimationView to redraw its contents. + public func forceDisplayUpdate() { + lottieAnimationLayer.forceDisplayUpdate() + } + + /// Sets a ValueProvider for the specified keypath. The value provider will be set + /// on all properties that match the keypath. + /// + /// Nearly all properties of a Lottie animation can be changed at runtime using a + /// combination of `Animation Keypaths` and `Value Providers`. + /// Setting a ValueProvider on a keypath will cause the animation to update its + /// contents and read the new Value Provider. + /// + /// A value provider provides a typed value on a frame by frame basis. + /// + /// - Parameter valueProvider: The new value provider for the properties. + /// - Parameter keypath: The keypath used to search for properties. + /// + /// Example: + /// ``` + /// /// A keypath that finds the color value for all `Fill 1` nodes. + /// let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color") + /// /// A Color Value provider that returns a reddish color. + /// let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1)) + /// /// Set the provider on the animationView. + /// animationView.setValueProvider(redValueProvider, keypath: fillKeypath) + /// ``` + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + lottieAnimationLayer.setValueProvider(valueProvider, keypath: keypath) + } + + /// Reads the value of a property specified by the Keypath. + /// Returns nil if no property is found. + /// + /// - Parameter for: The keypath used to search for the property. + /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + lottieAnimationLayer.getValue(for: keypath, atFrame: atFrame) + } + + /// Reads the original value of a property specified by the Keypath. + /// This will ignore any value providers and can be useful when implementing a value providers that makes change to the original value from the animation. + /// Returns nil if no property is found. + /// + /// - Parameter for: The keypath used to search for the property. + /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + public func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + lottieAnimationLayer.getOriginalValue(for: keypath, atFrame: atFrame) + } + + /// Logs all child keypaths. + /// Logs the result of `allHierarchyKeypaths()` to the `LottieLogger`. + public func logHierarchyKeypaths() { + lottieAnimationLayer.logHierarchyKeypaths() + } + + /// Computes and returns a list of all child keypaths in the current animation. + /// The returned list is the same as the log output of `logHierarchyKeypaths()` + public func allHierarchyKeypaths() -> [String] { + lottieAnimationLayer.allHierarchyKeypaths() + } + + /// Searches for the nearest child layer to the first Keypath and adds the subview + /// to that layer. The subview will move and animate with the child layer. + /// Furthermore the subview will be in the child layers coordinate space. + /// + /// Note: if no layer is found for the keypath, then nothing happens. + /// + /// - Parameter subview: The subview to add to the found animation layer. + /// - Parameter keypath: The keypath used to find the animation layer. + /// + /// Example: + /// ``` + /// /// A keypath that finds `Layer 1` + /// let layerKeypath = AnimationKeypath(keypath: "Layer 1") + /// + /// /// Wrap the custom view in an `AnimationSubview` + /// let subview = AnimationSubview() + /// subview.addSubview(customView) + /// + /// /// Set the provider on the animationView. + /// animationView.addSubview(subview, forLayerAt: layerKeypath) + /// ``` + public func addSubview(_ subview: AnimationSubview, forLayerAt keypath: AnimationKeypath) { + guard let sublayer = lottieAnimationLayer.rootAnimationLayer?.layer(for: keypath) else { + return + } + setNeedsLayout() + layoutIfNeeded() + lottieAnimationLayer.forceDisplayUpdate() + addSubview(subview) + if let subViewLayer = subview.viewLayer { + sublayer.addSublayer(subViewLayer) + } + } + + /// Converts a CGRect from the LottieAnimationView's coordinate space into the + /// coordinate space of the layer found at Keypath. + /// + /// If no layer is found, nil is returned + /// + /// - Parameter rect: The CGRect to convert. + /// - Parameter toLayerAt: The keypath used to find the layer. + public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? { + let convertedRect = lottieAnimationLayer.convert(rect, toLayerAt: keypath) + setNeedsLayout() + layoutIfNeeded() + return convertedRect + } + + /// Converts a CGPoint from the LottieAnimationView's coordinate space into the + /// coordinate space of the layer found at Keypath. + /// + /// If no layer is found, nil is returned + /// + /// - Parameter point: The CGPoint to convert. + /// - Parameter toLayerAt: The keypath used to find the layer. + public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? { + let convertedRect = lottieAnimationLayer.convert(point, toLayerAt: keypath) + setNeedsLayout() + layoutIfNeeded() + return convertedRect + } + + /// Sets the enabled state of all animator nodes found with the keypath search. + /// This can be used to interactively enable / disable parts of the animation. + /// + /// - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree. + /// - Parameter keypath: The keypath used to find the node(s). + public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) { + lottieAnimationLayer.setNodeIsEnabled(isEnabled: isEnabled, keypath: keypath) + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Progress Time for the marker named. Returns nil if no marker found. + public func progressTime(forMarker named: String) -> AnimationProgressTime? { + lottieAnimationLayer.progressTime(forMarker: named) + } + + /// Markers are a way to describe a point in time by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// Returns the Frame Time for the marker named. Returns nil if no marker found. + public func frameTime(forMarker named: String) -> AnimationFrameTime? { + lottieAnimationLayer.frameTime(forMarker: named) + } + + /// Markers are a way to describe a point in time and a duration by a key name. + /// + /// Markers are encoded into animation JSON. By using markers a designer can mark + /// playback points for a developer to use without having to worry about keeping + /// track of animation frames. If the animation file is updated, the developer + /// does not need to update playback code. + /// + /// - Returns: The duration frame time for the marker, or `nil` if no marker found. + public func durationFrameTime(forMarker named: String) -> AnimationFrameTime? { + lottieAnimationLayer.durationFrameTime(forMarker: named) + } + + // MARK: Internal + + /// The backing CALayer for this animation view. + let lottieAnimationLayer: LottieAnimationLayer + + var animationLayer: RootAnimationLayer? { + lottieAnimationLayer.rootAnimationLayer + } + + /// Set animation name from Interface Builder + @IBInspectable var animationName: String? { + didSet { + lottieAnimationLayer.animation = animationName.flatMap { LottieAnimation.named($0, animationCache: nil) + } + } + } + + override func commonInit() { + super.commonInit() + lottieAnimationLayer.screenScale = screenScale + viewLayer?.addSublayer(lottieAnimationLayer) + + lottieAnimationLayer.animationLoaded = { [weak self] _, animation in + guard let self else { return } + animationLoaded?(self, animation) + invalidateIntrinsicContentSize() + setNeedsLayout() + } + + lottieAnimationLayer.animationLayerDidLoad = { [weak self] _, _ in + guard let self else { return } + invalidateIntrinsicContentSize() + setNeedsLayout() + } + } + + override func layoutAnimation() { + guard let animation = lottieAnimationLayer.animation, let animationLayer = lottieAnimationLayer.animationLayer else { return } + + var position = animation.bounds.center + let xform: CATransform3D + var shouldForceUpdates = false + + if let viewportFrame { + shouldForceUpdates = contentMode == .redraw + + let compAspect = viewportFrame.size.width / viewportFrame.size.height + let viewAspect = bounds.size.width / bounds.size.height + let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height + let compDimension = compAspect > viewAspect ? viewportFrame.size.width : viewportFrame.size.height + let scale = dominantDimension / compDimension + + let viewportOffset = animation.bounds.center - viewportFrame.center + xform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1), viewportOffset.x, viewportOffset.y, 0) + position = bounds.center + } else { + switch contentMode { + case .scaleToFill: + position = bounds.center + xform = CATransform3DMakeScale( + bounds.size.width / animation.size.width, + bounds.size.height / animation.size.height, + 1) + case .scaleAspectFit: + position = bounds.center + let compAspect = animation.size.width / animation.size.height + let viewAspect = bounds.size.width / bounds.size.height + let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height + let compDimension = compAspect > viewAspect ? animation.size.width : animation.size.height + let scale = dominantDimension / compDimension + xform = CATransform3DMakeScale(scale, scale, 1) + case .scaleAspectFill: + position = bounds.center + let compAspect = animation.size.width / animation.size.height + let viewAspect = bounds.size.width / bounds.size.height + let scaleWidth = compAspect < viewAspect + let dominantDimension = scaleWidth ? bounds.size.width : bounds.size.height + let compDimension = scaleWidth ? animation.size.width : animation.size.height + let scale = dominantDimension / compDimension + xform = CATransform3DMakeScale(scale, scale, 1) + case .redraw: + shouldForceUpdates = true + xform = CATransform3DIdentity + case .center: + position = bounds.center + xform = CATransform3DIdentity + case .top: + position.x = bounds.center.x + xform = CATransform3DIdentity + case .bottom: + position.x = bounds.center.x + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + case .left: + position.y = bounds.center.y + xform = CATransform3DIdentity + case .right: + position.y = bounds.center.y + position.x = bounds.maxX - animation.bounds.midX + xform = CATransform3DIdentity + case .topLeft: + xform = CATransform3DIdentity + case .topRight: + position.x = bounds.maxX - animation.bounds.midX + xform = CATransform3DIdentity + case .bottomLeft: + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + case .bottomRight: + position.x = bounds.maxX - animation.bounds.midX + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + + #if canImport(UIKit) + @unknown default: + logger.assertionFailure("unsupported contentMode: \(contentMode.rawValue)") + xform = CATransform3DIdentity + #endif + } + } + + // UIView Animation does not implicitly set CAAnimation time or timing fuctions. + // If layout is changed in an animation we must get the current animation duration + // and timing function and then manually create a CAAnimation to match the UIView animation. + // If layout is changed without animation, explicitly set animation duration to 0.0 + // inside CATransaction to avoid unwanted artifacts. + /// Check if any animation exist on the view's layer, and match it. + if + let key = lottieAnimationLayer.animationKeys()?.first, + let animation = lottieAnimationLayer.animation(forKey: key), + animateLayoutChangesWithCurrentCoreAnimationContext + { + // The layout is happening within an animation block. Grab the animation data. + + let positionKey = "LayoutPositionAnimation" + let transformKey = "LayoutTransformAnimation" + animationLayer.removeAnimation(forKey: positionKey) + animationLayer.removeAnimation(forKey: transformKey) + + let positionAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "position") + positionAnimation.keyPath = "position" + positionAnimation.isAdditive = false + positionAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).position + positionAnimation.toValue = position + positionAnimation.isRemovedOnCompletion = true + + let xformAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "transform") + xformAnimation.keyPath = "transform" + xformAnimation.isAdditive = false + xformAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).transform + xformAnimation.toValue = xform + xformAnimation.isRemovedOnCompletion = true + + animationLayer.position = position + animationLayer.transform = xform + animationLayer.anchorPoint = lottieAnimationLayer.anchorPoint + animationLayer.add(positionAnimation, forKey: positionKey) + animationLayer.add(xformAnimation, forKey: transformKey) + } else { + // In performance tests, we have to wrap the animation view setup + // in a `CATransaction` in order for the layers to be deallocated at + // the correct time. The `CATransaction`s in this method interfere + // with the ones managed by the performance test, and aren't actually + // necessary in a headless environment, so we disable them. + if TestHelpers.performanceTestsAreRunning { + animationLayer.position = position + animationLayer.transform = xform + } else { + CATransaction.begin() + CATransaction.setAnimationDuration(0.0) + CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear)) + animationLayer.position = position + animationLayer.transform = xform + CATransaction.commit() + } + } + + if shouldForceUpdates { + lottieAnimationLayer.forceDisplayUpdate() + } + } + + func updateRasterizationState() { + lottieAnimationLayer.updateRasterizationState() + } + + /// Updates the animation frame. Does not affect any current animations + func updateAnimationFrame(_ newFrame: CGFloat) { + lottieAnimationLayer.updateAnimationFrame(newFrame) + } + + @objc + override func animationWillMoveToBackground() { + updateAnimationForBackgroundState() + } + + @objc + override func animationWillEnterForeground() { + updateAnimationForForegroundState() + } + + override func animationMovedToWindow() { + /// Don't update any state if the `superview` is `nil` + /// When A viewA owns superViewB, it removes the superViewB from the window. At this point, viewA still owns superViewB and triggers the viewA method: -didmovetowindow + guard superview != nil else { return } + + if window != nil { + updateAnimationForForegroundState() + } else { + updateAnimationForBackgroundState() + } + } + + func updateInFlightAnimation() { + lottieAnimationLayer.updateInFlightAnimation() + } + + func loadAnimation(_ animationSource: LottieAnimationSource?) { + lottieAnimationLayer.loadAnimation(animationSource) + } + + // MARK: Fileprivate + + fileprivate var waitingToPlayAnimation = false + + fileprivate func updateAnimationForBackgroundState() { + lottieAnimationLayer.updateAnimationForBackgroundState() + } + + fileprivate func updateAnimationForForegroundState() { + let wasWaitingToPlayAnimation = waitingToPlayAnimation + if waitingToPlayAnimation { + waitingToPlayAnimation = false + } + lottieAnimationLayer.updateAnimationForForegroundState(wasWaitingToPlayAnimation: wasWaitingToPlayAnimation) + } + + // MARK: Private + + private let logger: LottieLogger +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationViewInitializers.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationViewInitializers.swift new file mode 100644 index 0000000000000000000000000000000000000000..919cc934f2436422f9ce03e62aebff0c2a97d0a2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieAnimationViewInitializers.swift @@ -0,0 +1,226 @@ +// +// AnimationViewInitializers.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import Foundation + +extension LottieAnimationView { + + // MARK: Lifecycle + + /// Loads a Lottie animation from a JSON file in the supplied bundle. + /// + /// - Parameter name: The string name of the lottie animation with no file extension provided. + /// - Parameter bundle: The bundle in which the animation is located. Defaults to the Main bundle. + /// - Parameter subdirectory: A subdirectory in the bundle in which the animation is located. Optional. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the supplied bundle for images. + public convenience init( + name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared, + configuration: LottieConfiguration = .shared) + { + let animation = LottieAnimation.named(name, bundle: bundle, subdirectory: subdirectory, animationCache: animationCache) + let provider = imageProvider ?? BundleImageProvider(bundle: bundle, searchPath: nil) + self.init(animation: animation, imageProvider: provider, configuration: configuration) + } + + /// Loads a Lottie animation from a JSON file in a specific path on disk. + /// + /// - Parameter name: The absolute path of the Lottie Animation. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the supplied filepath for images. + public convenience init( + filePath: String, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared, + configuration: LottieConfiguration = .shared) + { + let animation = LottieAnimation.filepath(filePath, animationCache: animationCache) + let provider = imageProvider ?? + FilepathImageProvider(filepath: URL(fileURLWithPath: filePath).deletingLastPathComponent().path) + self.init(animation: animation, imageProvider: provider, configuration: configuration) + } + + /// Loads a Lottie animation asynchronously from the URL + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the main bundle for images. + /// - Parameter closure: A closure to be called when the animation has loaded. + public convenience init( + url: URL, + imageProvider: AnimationImageProvider? = nil, + session: URLSession = .shared, + closure: @escaping LottieAnimationView.DownloadClosure, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared, + configuration: LottieConfiguration = .shared) + { + if let animationCache, let animation = animationCache.animation(forKey: url.absoluteString) { + self.init(animation: animation, imageProvider: imageProvider, configuration: configuration) + closure(nil) + } else { + self.init(animation: nil, imageProvider: imageProvider, configuration: configuration) + + LottieAnimation.loadedFrom(url: url, session: session, closure: { animation in + if let animation { + self.animation = animation + closure(nil) + } else { + closure(LottieDownloadError.downloadFailed) + } + }, animationCache: animationCache) + } + } + + /// Loads a Lottie animation from a JSON file located in the Asset catalog of the supplied bundle. + /// - Parameter name: The string name of the lottie animation in the asset catalog. + /// - Parameter bundle: The bundle in which the animation is located. + /// Defaults to the Main bundle. + /// - Parameter imageProvider: An image provider for the animation's image data. + /// If none is supplied Lottie will search in the supplied bundle for images. + public convenience init( + asset name: String, + bundle: Bundle = Bundle.main, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LottieAnimationCache.shared, + configuration: LottieConfiguration = .shared) + { + let animation = LottieAnimation.asset(name, bundle: bundle, animationCache: animationCache) + let provider = imageProvider ?? BundleImageProvider(bundle: bundle, searchPath: nil) + self.init(animation: animation, imageProvider: provider, configuration: configuration) + } + + // MARK: DotLottie + + /// Loads a Lottie animation from a .lottie file in the supplied bundle. + /// + /// - Parameter dotLottieName: The name of the lottie file without the lottie extension. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main` + /// - Parameter subdirectory: A subdirectory in the bundle in which the lottie is located. Optional. + /// - Parameter animationId: Animation id to play. Optional + /// - Parameter completion: A closure that is called when the .lottie file is finished loading + /// Defaults to first animation in file + public convenience init( + dotLottieName name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + animationId: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + configuration: LottieConfiguration = .shared, + completion: ((LottieAnimationView, Error?) -> Void)? = nil) + { + self.init(dotLottie: nil, animationId: animationId, configuration: configuration) + DotLottieFile.named(name, bundle: bundle, subdirectory: subdirectory, dotLottieCache: dotLottieCache) { result in + switch result { + case .success(let dotLottieFile): + self.loadAnimation(animationId, from: dotLottieFile) + completion?(self, nil) + + case .failure(let error): + completion?(self, error) + } + } + } + + /// Loads a Lottie from a .lottie file in a specific path on disk. + /// + /// - Parameter dotLottieFilePath: The absolute path of the Lottie file. + /// - Parameter animationId: Animation id to play. Optional + /// - Parameter completion: A closure that is called when the .lottie file is finished loading + /// Defaults to first animation in file + public convenience init( + dotLottieFilePath filePath: String, + animationId: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + configuration: LottieConfiguration = .shared, + completion: ((LottieAnimationView, Error?) -> Void)? = nil) + { + self.init(dotLottie: nil, animationId: animationId, configuration: configuration) + DotLottieFile.loadedFrom(filepath: filePath, dotLottieCache: dotLottieCache) { result in + switch result { + case .success(let dotLottieFile): + self.loadAnimation(animationId, from: dotLottieFile) + completion?(self, nil) + + case .failure(let error): + completion?(self, error) + } + } + } + + /// Loads a Lottie file asynchronously from the URL + /// + /// - Parameter dotLottieUrl: The url to load the lottie file from. + /// - Parameter animationId: Animation id to play. Optional. Defaults to first animation in file. + /// - Parameter completion: A closure to be called when the animation has loaded. + public convenience init( + dotLottieUrl url: URL, + animationId: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + configuration: LottieConfiguration = .shared, + session: URLSession = .shared, + completion: ((LottieAnimationView, Error?) -> Void)? = nil) + { + if let dotLottieCache, let lottie = dotLottieCache.file(forKey: url.absoluteString) { + self.init(dotLottie: lottie, animationId: animationId, configuration: configuration) + completion?(self, nil) + } else { + self.init(dotLottie: nil, configuration: configuration) + DotLottieFile.loadedFrom(url: url, session: session, dotLottieCache: dotLottieCache) { result in + switch result { + case .success(let lottie): + self.loadAnimation(animationId, from: lottie) + completion?(self, nil) + + case .failure(let error): + completion?(self, error) + } + } + } + } + + /// Loads a Lottie from a .lottie file located in the Asset catalog of the supplied bundle. + /// - Parameter name: The string name of the lottie file in the asset catalog. + /// - Parameter bundle: The bundle in which the file is located. Defaults to the Main bundle. + /// - Parameter animationId: Animation id to play. Optional + /// - Parameter completion: A closure that is called when the .lottie file is finished loading + /// Defaults to first animation in file + public convenience init( + dotLottieAsset name: String, + bundle: Bundle = Bundle.main, + animationId: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + configuration: LottieConfiguration = .shared, + completion: ((LottieAnimationView, Error?) -> Void)? = nil) + { + self.init(dotLottie: nil, animationId: animationId, configuration: configuration) + DotLottieFile.asset(named: name, bundle: bundle, dotLottieCache: dotLottieCache) { result in + switch result { + case .success(let dotLottieFile): + self.loadAnimation(animationId, from: dotLottieFile) + completion?(self, nil) + + case .failure(let error): + completion?(self, error) + } + } + } + + // MARK: Public + + public typealias DownloadClosure = (Error?) -> Void + +} + +// MARK: - LottieDownloadError + +enum LottieDownloadError: Error { + case downloadFailed +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottiePlaybackMode.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottiePlaybackMode.swift new file mode 100644 index 0000000000000000000000000000000000000000..e33372405b739dfa358c929169dc4794747f9116 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottiePlaybackMode.swift @@ -0,0 +1,260 @@ +// Created by Cal Stephens on 8/3/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +import Foundation + +// MARK: - LottiePlaybackMode + +/// Configuration for how a Lottie animation should be played +public enum LottiePlaybackMode: Hashable { + + /// The animation is paused at the given state (e.g. paused at a specific frame) + case paused(at: PausedState) + + /// The animation is playing using the given playback mode (e.g. looping from the start to the end) + case playing(_ mode: PlaybackMode) + + @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.") + case progress(_ progress: AnimationProgressTime) + + @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.") + case frame(_ frame: AnimationFrameTime) + + @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.") + case time(_ time: TimeInterval) + + @available(*, deprecated, renamed: "LottiePlaybackMode.paused(at:)", message: "Will be removed in a future major release.") + case pause + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + case fromProgress(_ fromProgress: AnimationProgressTime?, toProgress: AnimationProgressTime, loopMode: LottieLoopMode) + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + case fromFrame(_ fromFrame: AnimationFrameTime?, toFrame: AnimationFrameTime, loopMode: LottieLoopMode) + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + case fromMarker( + _ fromMarker: String?, + toMarker: String, + playEndMarkerFrame: Bool = true, + loopMode: LottieLoopMode) + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + case marker(_ marker: String, loopMode: LottieLoopMode) + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + case markers(_ markers: [String]) + + // MARK: Public + + public enum PausedState: Hashable { + /// Any existing animation will be paused at the current frame. + case currentFrame + + /// The animation is paused at the given progress value, + /// a value between 0.0 (0% progress) and 1.0 (100% progress). + case progress(_ progress: AnimationProgressTime) + + /// The animation is paused at the given frame of the animation. + case frame(_ frame: AnimationFrameTime) + + /// The animation is paused at the given time value from the start of the animation. + case time(_ time: TimeInterval) + + /// Pauses the animation at a given marker and position + case marker(_ name: String, position: LottieMarkerPosition = .start) + } + + public enum PlaybackMode: Hashable { + /// Plays the animation from a progress (0-1) to a progress (0-1). + /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress. + /// - Parameter toProgress: The end progress of the animation. + /// - Parameter loopMode: The loop behavior of the animation. + case fromProgress( + _ fromProgress: AnimationProgressTime?, + toProgress: AnimationProgressTime, + loopMode: LottieLoopMode) + + /// The animation plays from the given `fromFrame` to the given `toFrame`. + /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame. + /// - Parameter toFrame: The end frame of the animation. + /// - Parameter loopMode: The loop behavior of the animation. + case fromFrame( + _ fromFrame: AnimationFrameTime?, + toFrame: AnimationFrameTime, + loopMode: LottieLoopMode) + + /// Plays the animation from a named marker to another marker. + /// + /// Markers are point in time that are encoded into the Animation data and assigned a name. + /// + /// NOTE: If markers are not found the play command will exit. + /// + /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the + /// animation will start at the current progress. + /// - Parameter toMarker: The end marker for the animation playback. + /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the + /// end marker represents the end of the section to play, it should be to true. If the provided end marker + /// represents the beginning of the next section, it should be false. + /// - Parameter loopMode: The loop behavior of the animation. + case fromMarker( + _ fromMarker: String?, + toMarker: String, + playEndMarkerFrame: Bool = true, + loopMode: LottieLoopMode) + + /// Plays the animation from a named marker to the end of the marker's duration. + /// + /// A marker is a point in time with an associated duration that is encoded into the + /// animation data and assigned a name. + /// + /// NOTE: If marker is not found the play command will exit. + /// + /// - Parameter marker: The start marker for the animation playback. + /// - Parameter loopMode: The loop behavior of the animation. + case marker( + _ marker: String, + loopMode: LottieLoopMode) + + /// Plays the given markers sequentially in order. + /// + /// A marker is a point in time with an associated duration that is encoded into the + /// animation data and assigned a name. Multiple markers can be played sequentially + /// to create programmable animations. + /// + /// If a marker is not found, it will be skipped. + /// + /// If a marker doesn't have a duration value, it will play with a duration of 0 + /// (effectively being skipped). + /// + /// If another animation is played (by calling any `play` method) while this + /// marker sequence is playing, the marker sequence will be cancelled. + /// + /// - Parameter markers: The list of markers to play sequentially. + case markers(_ markers: [String]) + } + +} + +extension LottiePlaybackMode { + public static var paused: Self { + .paused(at: .currentFrame) + } + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + public static func toProgress(_ toProgress: AnimationProgressTime, loopMode: LottieLoopMode) -> LottiePlaybackMode { + .playing(.fromProgress(nil, toProgress: toProgress, loopMode: loopMode)) + } + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + public static func toFrame(_ toFrame: AnimationFrameTime, loopMode: LottieLoopMode) -> LottiePlaybackMode { + .playing(.fromFrame(nil, toFrame: toFrame, loopMode: loopMode)) + } + + @available(*, deprecated, renamed: "LottiePlaybackMode.playing(_:)", message: "Will be removed in a future major release.") + public static func toMarker( + _ toMarker: String, + playEndMarkerFrame: Bool = true, + loopMode: LottieLoopMode) + -> LottiePlaybackMode + { + .playing(.fromMarker(nil, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: loopMode)) + } +} + +extension LottiePlaybackMode.PlaybackMode { + /// Plays the animation from the current progress to a progress value (0-1). + /// - Parameter toProgress: The end progress of the animation. + /// - Parameter loopMode: The loop behavior of the animation. + public static func toProgress(_ toProgress: AnimationProgressTime, loopMode: LottieLoopMode) -> Self { + .fromProgress(nil, toProgress: toProgress, loopMode: loopMode) + } + + /// Plays the animation from the current frame to the given frame. + /// - Parameter toFrame: The end frame of the animation. + /// - Parameter loopMode: The loop behavior of the animation. + public static func toFrame(_ toFrame: AnimationFrameTime, loopMode: LottieLoopMode) -> Self { + .fromFrame(nil, toFrame: toFrame, loopMode: loopMode) + } + + /// Plays the animation from the current frame to some marker. + /// + /// Markers are point in time that are encoded into the Animation data and assigned a name. + /// + /// NOTE: If the marker isn't found the play command will exit. + /// + /// - Parameter toMarker: The end marker for the animation playback. + /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the + /// end marker represents the end of the section to play, it should be to true. If the provided end marker + /// represents the beginning of the next section, it should be false. + /// - Parameter loopMode: The loop behavior of the animation. + public static func toMarker( + _ toMarker: String, + playEndMarkerFrame: Bool = true, + loopMode: LottieLoopMode) + -> Self + { + .fromMarker(nil, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: loopMode) + } +} + +// MARK: - LottieMarkerPosition + +/// The position within a marker. +public enum LottieMarkerPosition: Hashable { + case start + case end +} + +extension LottiePlaybackMode { + /// Returns a copy of this `PlaybackMode` with the `LottieLoopMode` updated to the given value + func loopMode(_ updatedLoopMode: LottieLoopMode) -> LottiePlaybackMode { + switch self { + case .playing(let playbackMode): + .playing(playbackMode.loopMode(updatedLoopMode)) + + case .fromProgress(let fromProgress, toProgress: let toProgress, _): + .playing(.fromProgress( + fromProgress, + toProgress: toProgress, + loopMode: updatedLoopMode)) + + case .fromFrame(let fromFrame, toFrame: let toFrame, _): + .playing(.fromFrame( + fromFrame, + toFrame: toFrame, + loopMode: updatedLoopMode)) + + case .fromMarker(let fromMarker, let toMarker, let playEndMarkerFrame, _): + .playing(.fromMarker( + fromMarker, + toMarker: toMarker, + playEndMarkerFrame: playEndMarkerFrame, + loopMode: updatedLoopMode)) + + case .marker(let marker, _): + .playing(.marker(marker, loopMode: updatedLoopMode)) + + case .pause, .paused, .progress(_), .time(_), .frame(_), .markers: + self + } + } +} + +extension LottiePlaybackMode.PlaybackMode { + /// Returns a copy of this `PlaybackMode` with the `LottieLoopMode` updated to the given value + func loopMode(_ updatedLoopMode: LottieLoopMode) -> LottiePlaybackMode.PlaybackMode { + switch self { + case .fromProgress(let fromProgress, let toProgress, _): + .fromProgress(fromProgress, toProgress: toProgress, loopMode: updatedLoopMode) + case .fromFrame(let fromFrame, let toFrame, _): + .fromFrame(fromFrame, toFrame: toFrame, loopMode: updatedLoopMode) + case .fromMarker(let fromMarker, let toMarker, let playEndMarkerFrame, _): + .fromMarker(fromMarker, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: updatedLoopMode) + case .marker(let marker, _): + .marker(marker, loopMode: updatedLoopMode) + case .markers: + self + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieView.swift new file mode 100644 index 0000000000000000000000000000000000000000..aae74dfd469886b0d35206de42ea80df68886bc2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Animation/LottieView.swift @@ -0,0 +1,630 @@ +// Created by Bryn Bodayle on 1/20/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +// MARK: - LottieView + +/// A wrapper which exposes Lottie's `LottieAnimationView` to SwiftUI +public struct LottieView: UIViewConfiguringSwiftUIView { + + // MARK: Lifecycle + + /// Creates a `LottieView` that displays the given animation + public init(animation: LottieAnimation?) where Placeholder == EmptyView { + localAnimation = animation.map(LottieAnimationSource.lottieAnimation) + placeholder = nil + } + + /// Initializes a `LottieView` with the provided `DotLottieFile` for display. + /// + /// - Important: Avoid using this initializer with the `SynchronouslyBlockingCurrentThread` APIs. + /// If decompression of a `.lottie` file is necessary, prefer using the `.init(_ loadAnimation:)` + /// initializer, which takes an asynchronous closure: + /// ``` + /// LottieView { + /// try await DotLottieFile.named(name) + /// } + /// ``` + public init(dotLottieFile: DotLottieFile?) where Placeholder == EmptyView { + localAnimation = dotLottieFile.map(LottieAnimationSource.dotLottieFile) + placeholder = nil + } + + /// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimation`. + /// The `loadAnimation` closure is called exactly once in `onAppear`. + /// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`. + public init(_ loadAnimation: @escaping () async throws -> LottieAnimation?) where Placeholder == EmptyView { + self.init(loadAnimation, placeholder: EmptyView.init) + } + + /// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimation`. + /// The `loadAnimation` closure is called exactly once in `onAppear`. + /// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`. + /// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`. + public init( + _ loadAnimation: @escaping () async throws -> LottieAnimation?, + @ViewBuilder placeholder: @escaping (() -> Placeholder)) + { + self.init { + try await loadAnimation().map(LottieAnimationSource.lottieAnimation) + } placeholder: { + placeholder() + } + } + + /// Creates a `LottieView` that asynchronously loads and displays the given `DotLottieFile`. + /// The `loadDotLottieFile` closure is called exactly once in `onAppear`. + /// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`. + /// You can use the `DotLottieFile` static methods API which use Swift concurrency to load your `.lottie` files: + /// ``` + /// LottieView { + /// try await DotLottieFile.named(name) + /// } + /// ``` + public init(_ loadDotLottieFile: @escaping () async throws -> DotLottieFile?) where Placeholder == EmptyView { + self.init(loadDotLottieFile, placeholder: EmptyView.init) + } + + /// Creates a `LottieView` that asynchronously loads and displays the given `DotLottieFile`. + /// The `loadDotLottieFile` closure is called exactly once in `onAppear`. + /// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`. + /// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`. + /// You can use the `DotLottieFile` static methods API which use Swift concurrency to load your `.lottie` files: + /// ``` + /// LottieView { + /// try await DotLottieFile.named(name) + /// } placeholder: { + /// LoadingView() + /// } + /// ``` + public init( + _ loadDotLottieFile: @escaping () async throws -> DotLottieFile?, + @ViewBuilder placeholder: @escaping (() -> Placeholder)) + { + self.init { + try await loadDotLottieFile().map(LottieAnimationSource.dotLottieFile) + } placeholder: { + placeholder() + } + } + + /// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimationSource`. + /// The `loadAnimation` closure is called exactly once in `onAppear`. + /// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`. + /// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`. + public init(_ loadAnimation: @escaping () async throws -> LottieAnimationSource?) where Placeholder == EmptyView { + self.init(loadAnimation, placeholder: EmptyView.init) + } + + /// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimationSource`. + /// The `loadAnimation` closure is called exactly once in `onAppear`. + /// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`. + /// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`. + public init( + _ loadAnimation: @escaping () async throws -> LottieAnimationSource?, + @ViewBuilder placeholder: @escaping () -> Placeholder) + { + localAnimation = nil + self.loadAnimation = loadAnimation + self.placeholder = placeholder + } + + // MARK: Public + + public var body: some View { + LottieAnimationView.swiftUIView { + LottieAnimationView( + animationSource: animationSource, + imageProvider: imageProviderConfiguration?.imageProvider, + textProvider: textProvider, + fontProvider: fontProvider, + configuration: configuration, + logger: logger) + } + .sizing(sizing) + .configure { context in + applyCurrentAnimationConfiguration(to: context.view, in: context.container) + } + .configurations(configurations) + .opacity(animationSource == nil ? 0 : 1) + .overlay { + placeholder?() + .opacity(animationSource == nil ? 1 : 0) + } + .onAppear { + loadAnimationIfNecessary() + } + .valueChanged(value: reloadAnimationTrigger) { _ in + reloadAnimationTriggerDidChange() + } + } + + /// Returns a copy of this `LottieView` updated to have the specific configuration property + /// applied to its represented `LottieAnimationView` whenever it is updated via the `updateUIView(...)` + /// or `updateNSView(...)` methods. + /// + /// - note: This configuration will be applied on every SwiftUI render pass. + /// Be wary of applying heavy side-effects as configuration values. + public func configure( + _ property: ReferenceWritableKeyPath, + to value: Property) + -> Self + { + configure { $0[keyPath: property] = value } + } + + /// Returns a copy of this `LottieView` updated to have the specific configuration property + /// applied to its represented `LottieAnimationView` whenever it is updated via the `updateUIView(...)` + /// or `updateNSView(...)` methods. + /// + /// - note: If the `value` is already the currently-applied configuration value, it won't be applied + public func configure( + _ property: ReferenceWritableKeyPath, + to value: Property) + -> Self + { + configure { + guard $0[keyPath: property] != value else { return } + $0[keyPath: property] = value + } + } + + /// Returns a copy of this `LottieView` updated to have the given closure applied to its + /// represented `LottieAnimationView` whenever it is updated via the `updateUIView(…)` + /// or `updateNSView(…)` method. + /// + /// - note: This configuration closure will be executed on every SwiftUI render pass. + /// Be wary of applying heavy side-effects inside it. + public func configure(_ configure: @escaping (LottieAnimationView) -> Void) -> Self { + var copy = self + copy.configurations.append { context in + configure(context.view) + } + return copy + } + + /// Returns a copy of this view that can be resized by scaling its animation + /// to always fit the size offered by its parent. + public func resizable() -> Self { + var copy = self + copy.sizing = .proposed + return copy + } + + /// Returns a copy of this view that adopts the intrinsic size of the animation, + /// up to the proposed size. + public func intrinsicSize() -> Self { + var copy = self + copy.sizing = .intrinsic + return copy + } + + @available(*, deprecated, renamed: "playing()", message: "Will be removed in a future major release.") + public func play() -> Self { + playbackMode(.playing(.fromProgress(nil, toProgress: 1, loopMode: .playOnce))) + } + + /// Returns a copy of this view that loops its animation from the start to end whenever visible + public func looping() -> Self { + playbackMode(.playing(.fromProgress(0, toProgress: 1, loopMode: .loop))) + } + + @available(*, deprecated, renamed: "playing(_:)", message: "Will be removed in a future major release.") + public func play(loopMode: LottieLoopMode = .playOnce) -> Self { + playbackMode(.playing(.fromProgress(nil, toProgress: 1, loopMode: loopMode))) + } + + @available(*, deprecated, renamed: "playbackMode(_:)", message: "Will be removed in a future major release.") + public func play(_ playbackMode: LottiePlaybackMode) -> Self { + self.playbackMode(playbackMode) + } + + /// Returns a copy of this view playing with the given playback mode + public func playing(_ mode: LottiePlaybackMode.PlaybackMode) -> Self { + playbackMode(.playing(mode)) + } + + /// Returns a copy of this view playing from the current frame to the end frame, + /// with the given `LottiePlaybackMode`. + public func playing(loopMode: LottieLoopMode) -> Self { + playbackMode(.playing(.fromProgress(nil, toProgress: 1, loopMode: loopMode))) + } + + /// Returns a copy of this view playing once from the current frame to the end frame + public func playing() -> Self { + playbackMode(.playing(.fromProgress(nil, toProgress: 1, loopMode: .playOnce))) + } + + /// Returns a copy of this view paused with the given state + public func paused(at state: LottiePlaybackMode.PausedState = .currentFrame) -> Self { + playbackMode(.paused(at: state)) + } + + /// Returns a copy of this view using the given `LottiePlaybackMode` + public func playbackMode(_ playbackMode: LottiePlaybackMode) -> Self { + var copy = self + copy.playbackMode = playbackMode + return copy + } + + /// Returns a copy of this view playing its animation at the given speed + public func animationSpeed(_ animationSpeed: Double) -> Self { + var copy = self + copy.animationSpeed = animationSpeed + return copy + } + + /// Returns a copy of this view with the given closure that is called whenever the + /// `LottieAnimationSource` provided via `init` is loaded and applied to the underlying `LottieAnimationView`. + public func animationDidLoad(_ animationDidLoad: @escaping (LottieAnimationSource) -> Void) -> Self { + var copy = self + copy.animationDidLoad = animationDidLoad + return copy + } + + /// Returns a copy of this view with the given `LottieCompletionBlock` that is called + /// when an animation finishes playing. + public func animationDidFinish(_ animationCompletionHandler: LottieCompletionBlock?) -> Self { + var copy = self + copy.animationCompletionHandler = { [previousCompletionHandler = self.animationCompletionHandler] completed in + previousCompletionHandler?(completed) + animationCompletionHandler?(completed) + } + return copy + } + + /// Returns a copy of this view updated to have the provided background behavior. + public func backgroundBehavior(_ value: LottieBackgroundBehavior) -> Self { + configure { view in + view.backgroundBehavior = value + } + } + + /// Returns a copy of this view with its accessibility label updated to the given value. + public func accessibilityLabel(_ accessibilityLabel: String?) -> Self { + configure { view in + #if os(macOS) + view.setAccessibilityElement(accessibilityLabel != nil) + view.setAccessibilityLabel(accessibilityLabel) + #else + view.isAccessibilityElement = accessibilityLabel != nil + view.accessibilityLabel = accessibilityLabel + #endif + } + } + + /// Returns a copy of this view with its `LottieConfiguration` updated to the given value. + public func configuration(_ configuration: LottieConfiguration) -> Self { + var copy = self + copy.configuration = configuration + + copy = copy.configure { view in + if view.configuration != configuration { + view.configuration = configuration + } + } + + return copy + } + + /// Returns a copy of this view with its `LottieLogger` updated to the given value. + /// - The underlying `LottieAnimationView`'s `LottieLogger` is immutable after configured, + /// so this value is only used when initializing the `LottieAnimationView` for the first time. + public func logger(_ logger: LottieLogger) -> Self { + var copy = self + copy.logger = logger + return copy + } + + /// Returns a copy of this view with its image provider updated to the given value. + /// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders. + public func imageProvider(_ imageProvider: ImageProvider) -> Self { + var copy = self + + copy.imageProviderConfiguration = ( + imageProvider: imageProvider, + imageProvidersAreEqual: { untypedLHS, untypedRHS in + guard + let lhs = untypedLHS as? ImageProvider, + let rhs = untypedRHS as? ImageProvider + else { return false } + + return lhs == rhs + }) + + return copy + } + + /// Returns a copy of this view with its text provider updated to the given value. + /// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders. + public func textProvider(_ textProvider: TextProvider) -> Self { + var copy = self + copy.textProvider = textProvider + + copy = copy.configure { view in + if (view.textProvider as? TextProvider) != textProvider { + view.textProvider = textProvider + } + } + + return copy + } + + /// Returns a copy of this view with its image provider updated to the given value. + /// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders. + public func fontProvider(_ fontProvider: FontProvider) -> Self { + var copy = self + copy.fontProvider = fontProvider + + copy = configure { view in + if (view.fontProvider as? FontProvider) != fontProvider { + view.fontProvider = fontProvider + } + } + + return copy + } + + /// Returns a copy of this view using the given value provider for the given keypath. + /// The value provider must be `Equatable` to avoid unnecessary state updates / re-renders. + public func valueProvider( + _ valueProvider: ValueProvider, + for keypath: AnimationKeypath) + -> Self + { + configure { view in + if (view.valueProviders[keypath] as? ValueProvider) != valueProvider { + view.setValueProvider(valueProvider, keypath: keypath) + } + } + } + + /// Returns a copy of this view updated to display the given `AnimationProgressTime`. + /// - If the `currentProgress` value is provided, the `currentProgress` of the + /// underlying `LottieAnimationView` is updated. This will pause any existing animations. + /// - If the `animationProgress` is `nil`, no changes will be made and any existing animations + /// will continue playing uninterrupted. + public func currentProgress(_ currentProgress: AnimationProgressTime?) -> Self { + guard let currentProgress else { return self } + var copy = self + copy.playbackMode = .paused(at: .progress(currentProgress)) + return copy + } + + /// Returns a copy of this view updated to display the given `AnimationFrameTime`. + /// - If the `currentFrame` value is provided, the `currentFrame` of the + /// underlying `LottieAnimationView` is updated. This will pause any existing animations. + /// - If the `currentFrame` is `nil`, no changes will be made and any existing animations + /// will continue playing uninterrupted. + public func currentFrame(_ currentFrame: AnimationFrameTime?) -> Self { + guard let currentFrame else { return self } + var copy = self + copy.playbackMode = .paused(at: .frame(currentFrame)) + return copy + } + + /// Returns a copy of this view updated to display the given time value. + /// - If the `currentTime` value is provided, the `currentTime` of the + /// underlying `LottieAnimationView` is updated. This will pause any existing animations. + /// - If the `currentTime` is `nil`, no changes will be made and any existing animations + /// will continue playing uninterrupted. + public func currentTime(_ currentTime: TimeInterval?) -> Self { + guard let currentTime else { return self } + var copy = self + copy.playbackMode = .paused(at: .time(currentTime)) + return copy + } + + /// Returns a new instance of this view, which will invoke the provided `loadAnimation` closure + /// whenever the `binding` value is updated. + /// + /// - Note: This function requires a valid `loadAnimation` closure provided during view initialization, + /// otherwise `reloadAnimationTrigger` will have no effect. + /// - Parameters: + /// - binding: The binding that triggers the reloading when its value changes. + /// - showPlaceholder: When `true`, the current animation will be removed before invoking `loadAnimation`, + /// displaying the `Placeholder` until the new animation loads. + /// When `false`, the previous animation remains visible while the new one loads. + public func reloadAnimationTrigger(_ value: some Equatable, showPlaceholder: Bool = true) -> Self { + var copy = self + copy.reloadAnimationTrigger = AnyEquatable(value) + copy.showPlaceholderWhileReloading = showPlaceholder + return copy + } + + /// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`. + /// The `LottieView` is wrapped in a `TimelineView` with the `.animation` schedule. + /// - This is a one-way binding. Its value is updated but never read. + /// - If provided, the binding will be updated each frame with the `realtimeAnimationProgress` + /// of the underlying `LottieAnimationView`. This is potentially expensive since it triggers + /// a state update every frame. + /// - If the binding is `nil`, the `TimelineView` will be paused and no updates will occur to the binding. + @available(iOS 15.0, tvOS 15.0, macOS 12.0, *) + public func getRealtimeAnimationProgress(_ realtimeAnimationProgress: Binding?) -> some View { + TimelineView(.animation(paused: realtimeAnimationProgress == nil)) { _ in + configure { view in + if let realtimeAnimationProgress { + DispatchQueue.main.async { + realtimeAnimationProgress.wrappedValue = view.realtimeAnimationProgress + } + } + } + } + } + + /// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`. + /// The `LottieView` is wrapped in a `TimelineView` with the `.animation` schedule. + /// - This is a one-way binding. Its value is updated but never read. + /// - If provided, the binding will be updated each frame with the `realtimeAnimationProgress` + /// of the underlying `LottieAnimationView`. This is potentially expensive since it triggers + /// a state update every frame. + /// - If the binding is `nil`, the `TimelineView` will be paused and no updates will occur to the binding. + @available(iOS 15.0, tvOS 15.0, macOS 12.0, *) + public func getRealtimeAnimationFrame(_ realtimeAnimationFrame: Binding?) -> some View { + TimelineView(.animation(paused: realtimeAnimationFrame == nil)) { _ in + configure { view in + if let realtimeAnimationFrame { + DispatchQueue.main.async { + realtimeAnimationFrame.wrappedValue = view.realtimeAnimationFrame + } + } + } + } + } + + /// Returns a copy of this view with the `DotLottieConfigurationComponents` + /// updated to the given value. + /// - Defaults to `[.imageProvider]` + /// - If a component is specified here, that value in the `DotLottieConfiguration` + /// of an active dotLottie animation will override any value provided via other methods. + public func dotLottieConfigurationComponents( + _ dotLottieConfigurationComponents: DotLottieConfigurationComponents) + -> Self + { + var copy = self + copy.dotLottieConfigurationComponents = dotLottieConfigurationComponents + return copy + } + + // MARK: Internal + + var configurations = [SwiftUIView.Configuration]() + + // MARK: Private + + private let localAnimation: LottieAnimationSource? + @State private var remoteAnimation: LottieAnimationSource? + private var playbackMode: LottiePlaybackMode? + private var animationSpeed: Double? + private var reloadAnimationTrigger: AnyEquatable? + private var loadAnimation: (() async throws -> LottieAnimationSource?)? + private var animationDidLoad: ((LottieAnimationSource) -> Void)? + private var animationCompletionHandler: LottieCompletionBlock? + private var showPlaceholderWhileReloading = false + private var textProvider: AnimationKeypathTextProvider = DefaultTextProvider() + private var fontProvider: AnimationFontProvider = DefaultFontProvider() + private var configuration: LottieConfiguration = .shared + private var dotLottieConfigurationComponents: DotLottieConfigurationComponents = .imageProvider + private var logger: LottieLogger = .shared + private var sizing = SwiftUIMeasurementContainerStrategy.automatic + private let placeholder: (() -> Placeholder)? + + private var imageProviderConfiguration: ( + imageProvider: AnimationImageProvider, + imageProvidersAreEqual: (AnimationImageProvider, AnimationImageProvider) -> Bool)? + + private var animationSource: LottieAnimationSource? { + localAnimation ?? remoteAnimation + } + + private func loadAnimationIfNecessary() { + guard let loadAnimation else { return } + + Task { + do { + remoteAnimation = try await loadAnimation() + } catch { + logger.warn("Failed to load asynchronous Lottie animation with error: \(error)") + } + } + } + + private func reloadAnimationTriggerDidChange() { + guard loadAnimation != nil else { return } + + if showPlaceholderWhileReloading { + remoteAnimation = nil + } + + loadAnimationIfNecessary() + } + + /// Applies playback configuration for the current animation to the `LottieAnimationView` + private func applyCurrentAnimationConfiguration( + to view: LottieAnimationView, + in container: SwiftUIMeasurementContainer) + { + guard let animationSource else { return } + var imageProviderConfiguration = imageProviderConfiguration + var playbackMode = playbackMode + var animationSpeed = animationSpeed + + // When playing a dotLottie animation, its `DotLottieConfiguration` + // can override some behavior of the animation. + if let dotLottieConfiguration = animationSource.dotLottieAnimation?.configuration { + // Only use the value from the `DotLottieConfiguration` is that component + // is specified in the list of `dotLottieConfigurationComponents`. + if dotLottieConfigurationComponents.contains(.loopMode) { + playbackMode = playbackMode?.loopMode(dotLottieConfiguration.loopMode) + } + + if dotLottieConfigurationComponents.contains(.animationSpeed) { + animationSpeed = dotLottieConfiguration.speed + } + + if + dotLottieConfigurationComponents.contains(.imageProvider), + let dotLottieImageProvider = dotLottieConfiguration.dotLottieImageProvider + { + imageProviderConfiguration = ( + imageProvider: dotLottieImageProvider, + imageProvidersAreEqual: { untypedLHS, untypedRHS in + guard + let lhs = untypedLHS as? DotLottieImageProvider, + let rhs = untypedRHS as? DotLottieImageProvider + else { return false } + + return lhs == rhs + }) + } + } + + // We check referential equality of the animation before updating as updating the + // animation has a side-effect of rebuilding the animation layer, and it would be + // prohibitive to do so on every state update. + if animationSource.animation !== view.animation { + view.loadAnimation(animationSource) + animationDidLoad?(animationSource) + + // Invalidate the intrinsic size of the SwiftUI measurement container, + // since any cached measurements will be out of date after updating the animation. + container.invalidateIntrinsicContentSize() + } + + if + let playbackMode, + playbackMode != view.currentPlaybackMode + { + view.setPlaybackMode(playbackMode, completion: animationCompletionHandler) + } + + if + let (imageProvider, imageProvidersAreEqual) = imageProviderConfiguration, + !imageProvidersAreEqual(imageProvider, view.imageProvider) + { + view.imageProvider = imageProvider + } + + if + let animationSpeed, + animationSpeed != view.animationSpeed + { + view.animationSpeed = animationSpeed + } + } +} + +extension View { + + /// The `.overlay` modifier that uses a `ViewBuilder` is available in iOS 15+, this helper function helps us to use the same API in older OSs + fileprivate func overlay( + @ViewBuilder content: () -> some View) + -> some View + { + overlay(content(), alignment: .center) + } +} + +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/AnimationCacheProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/AnimationCacheProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..ba6e3768d00b6b8940cceb780fd12f81cd8ea58c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/AnimationCacheProvider.swift @@ -0,0 +1,21 @@ +// +// AnimationCacheProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +/// `AnimationCacheProvider` is a protocol that describes an Animation Cache. +/// Animation Cache is used when loading `LottieAnimation` models. Using an Animation Cache +/// can increase performance when loading an animation multiple times. +/// +/// Lottie comes with a prebuilt LRU Animation Cache. +public protocol AnimationCacheProvider: AnyObject, Sendable { + + func animation(forKey: String) -> LottieAnimation? + + func setAnimation(_ animation: LottieAnimation, forKey: String) + + func clearCache() + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/DefaultAnimationCache.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/DefaultAnimationCache.swift new file mode 100644 index 0000000000000000000000000000000000000000..57f2a9b4c8bfa44d638a01f8dc94be3b8dd24be2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/DefaultAnimationCache.swift @@ -0,0 +1,67 @@ +// +// DefaultAnimationCache.swift +// Lottie +// +// Created by Marcelo Fabri on 10/18/22. +// + +import Foundation + +// MARK: - DefaultAnimationCache + +/// A thread-safe Animation Cache that will store animations up to `cacheSize`. +/// +/// Once `cacheSize` is reached, animations can be ejected. +/// The default size of the cache is 100. +/// +/// This cache implementation also responds to memory pressure. +public class DefaultAnimationCache: AnimationCacheProvider { + + // MARK: Lifecycle + + public init() { + cache.countLimit = Self.defaultCacheCountLimit + } + + // MARK: Public + + /// The global shared Cache. + public static let sharedCache = DefaultAnimationCache() + + /// The maximum number of animations that can be stored in the cache. + public var cacheSize: Int { + get { cache.countLimit } + set { cache.countLimit = newValue } + } + + /// Clears the Cache. + public func clearCache() { + cache.removeAllValues() + } + + public func animation(forKey key: String) -> LottieAnimation? { + cache.value(forKey: key) + } + + public func setAnimation(_ animation: LottieAnimation, forKey key: String) { + cache.setValue(animation, forKey: key) + } + + // MARK: Private + + private static let defaultCacheCountLimit = 100 + + /// The underlying storage of this cache. + /// - We use the `LRUCache` library instead of `NSCache`, because `NSCache` + /// clears all cached values when the app is backgrounded instead of + /// only when the app receives a memory warning notification. + private let cache = LRUCache() +} + +// MARK: Sendable + +// LottieAnimationCache has a Sendable requirement, but we can't +// redesign DefaultAnimationCache to be properly Sendable without +// making breaking changes. +// swiftlint:disable:next no_unchecked_sendable +extension DefaultAnimationCache: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/LRUAnimationCache.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/LRUAnimationCache.swift new file mode 100644 index 0000000000000000000000000000000000000000..ebf1f5ee56c44360ff68c7c28d65c189e96dc932 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/LRUAnimationCache.swift @@ -0,0 +1,11 @@ +// +// LRUAnimationCache.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +@available(*, deprecated, message: """ + Use DefaultAnimationCache instead, which is thread-safe and automatically responds to memory pressure. + """) +public typealias LRUAnimationCache = DefaultAnimationCache diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/LottieAnimationCache.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/LottieAnimationCache.swift new file mode 100644 index 0000000000000000000000000000000000000000..efb6a3ee39507907aadef903dfd867d391d31f6e --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/AnimationCache/LottieAnimationCache.swift @@ -0,0 +1,15 @@ +// +// LottieAnimationCache.swift +// Lottie +// +// Created by Marcelo Fabri on 10/17/22. +// + +/// A customization point to configure which `AnimationCacheProvider` will be used. +public enum LottieAnimationCache { + + /// The animation cache that will be used when loading `LottieAnimation` models. + /// Using an Animation Cache can increase performance when loading an animation multiple times. + /// Defaults to DefaultAnimationCache.sharedCache. + public static var shared: AnimationCacheProvider? = DefaultAnimationCache.sharedCache +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/DecodingStrategy.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/DecodingStrategy.swift new file mode 100644 index 0000000000000000000000000000000000000000..f1cfe3c4b007da40b8c159dbba837aabdbec66bb --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/DecodingStrategy.swift @@ -0,0 +1,15 @@ +// Created by Cal Stephens on 7/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +/// How animation files should be decoded +public enum DecodingStrategy: Hashable { + /// Use Codable. This is was the default strategy introduced on Lottie 3, but should be rarely + /// used as it's slower than `dictionaryBased`. Kept here for any possible compatibility issues + /// that may come up, but consider it soft-deprecated. + case legacyCodable + + /// Manually deserialize a dictionary into an Animation. + /// This should be at least 2-3x faster than using Codable and due to that + /// it's the default as of Lottie 4.x. + case dictionaryBased +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/LottieConfiguration.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/LottieConfiguration.swift new file mode 100644 index 0000000000000000000000000000000000000000..646ace717ac137ed5be24bf55adfe699cb601df4 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/LottieConfiguration.swift @@ -0,0 +1,47 @@ +// Created by Cal Stephens on 12/13/21. +// Copyright © 2021 Airbnb Inc. All rights reserved. + +import QuartzCore + +/// Global configuration options for Lottie animations +public struct LottieConfiguration: Hashable { + + // MARK: Lifecycle + + public init( + renderingEngine: RenderingEngineOption = .automatic, + decodingStrategy: DecodingStrategy = .dictionaryBased, + colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(), + reducedMotionOption: ReducedMotionOption = .systemReducedMotionToggle) + { + self.renderingEngine = renderingEngine + self.decodingStrategy = decodingStrategy + self.colorSpace = colorSpace + self.reducedMotionOption = reducedMotionOption + } + + // MARK: Public + + /// The global configuration of Lottie, + /// which applies to all `LottieAnimationView`s by default. + public static var shared = LottieConfiguration() + + /// The rendering engine implementation to use when displaying an animation + /// - Defaults to `RenderingEngineOption.automatic`, which uses the + /// Core Animation rendering engine for supported animations, and + /// falls back to using the Main Thread rendering engine for + /// animations that use features not supported by the Core Animation engine. + public var renderingEngine: RenderingEngineOption + + /// The decoding implementation to use when parsing an animation JSON file + public var decodingStrategy: DecodingStrategy + + /// Options for controlling animation behavior in response to user / system "reduced motion" configuration. + /// - Defaults to `ReducedMotionOption.systemReducedMotionToggle`, which returns `.reducedMotion` + /// when the system `UIAccessibility.isReduceMotionEnabled` option is `true`. + public var reducedMotionOption: ReducedMotionOption + + /// The color space to be used for rendering + /// - Defaults to `CGColorSpaceCreateDeviceRGB()` + public var colorSpace: CGColorSpace +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/ReducedMotionOption.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/ReducedMotionOption.swift new file mode 100644 index 0000000000000000000000000000000000000000..040d9d3548b1ade4d03876a6cc9b513fbf5c6998 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/ReducedMotionOption.swift @@ -0,0 +1,114 @@ +// Created by Cal Stephens on 7/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +// MARK: - ReducedMotionOption + +/// Options for controlling animation behavior in response to user / system "reduced motion" configuration +public enum ReducedMotionOption { + /// Always use the specific given `ReducedMotionMode` value. + case specific(ReducedMotionMode) + + /// Dynamically check the given `ReducedMotionOptionProvider` each time an animation begins. + /// - Includes a Hashable `dataID` to support `ReducedMotionOption`'s `Hashable` requirement, + /// which is required due to `LottieConfiguration`'s existing `Hashable` requirement. + case dynamic(ReducedMotionOptionProvider, dataID: AnyHashable) +} + +extension ReducedMotionOption { + /// The standard behavior where Lottie animations play normally with no overrides. + /// By default this mode is used when the system "reduced motion" option is disabled. + public static var standardMotion: ReducedMotionOption { .specific(.standardMotion) } + + /// Lottie animations with a "reduced motion" marker will play that marker instead of any other animations. + /// By default this mode is used when the system "reduced motion" option is enabled. + /// - Valid marker names include "reduced motion", "reducedMotion", "reduced_motion" (case insensitive). + public static var reducedMotion: ReducedMotionOption { .specific(.reducedMotion) } + + /// A `ReducedMotionOptionProvider` that returns `.reducedMotion` when + /// the system `UIAccessibility.isReduceMotionEnabled` option is `true`. + /// This is the default option of `LottieConfiguration`. + public static var systemReducedMotionToggle: ReducedMotionOption { + .dynamic(SystemReducedMotionOptionProvider(), dataID: ObjectIdentifier(SystemReducedMotionOptionProvider.self)) + } +} + +extension ReducedMotionOption { + /// The current `ReducedMotionMode` based on the currently selected option. + public var currentReducedMotionMode: ReducedMotionMode { + switch self { + case .specific(let specificMode): + specificMode + case .dynamic(let optionProvider, _): + optionProvider.currentReducedMotionMode + } + } +} + +// MARK: Hashable + +extension ReducedMotionOption: Hashable { + public static func ==(_ lhs: ReducedMotionOption, _ rhs: ReducedMotionOption) -> Bool { + switch (lhs, rhs) { + case (.specific(let lhsMode), .specific(let rhsMode)): + lhsMode == rhsMode + case (.dynamic(_, let lhsDataID), .dynamic(_, dataID: let rhsDataID)): + lhsDataID == rhsDataID + case (.dynamic, .specific), (.specific, .dynamic): + false + } + } + + public func hash(into hasher: inout Hasher) { + switch self { + case .specific(let mode): + hasher.combine(mode) + case .dynamic(_, let dataID): + hasher.combine(dataID) + } + } +} + +// MARK: - ReducedMotionMode + +public enum ReducedMotionMode: Hashable { + /// The default behavior where Lottie animations play normally with no overrides + /// By default this mode is used when the system "reduced motion" option is disabled. + case standardMotion + + /// Lottie animations with a "reduced motion" marker will play that marker instead of any other animations. + /// By default this mode is used when the system "reduced motion" option is enabled. + case reducedMotion +} + +// MARK: - ReducedMotionOptionProvider + +/// A type that returns a dynamic `ReducedMotionMode` which is checked when playing a Lottie animation. +public protocol ReducedMotionOptionProvider { + var currentReducedMotionMode: ReducedMotionMode { get } +} + +// MARK: - SystemReducedMotionOptionProvider + +/// A `ReducedMotionOptionProvider` that returns `.reducedMotion` when +/// the system `UIAccessibility.isReduceMotionEnabled` option is `true`. +public struct SystemReducedMotionOptionProvider: ReducedMotionOptionProvider { + public init() { } + + public var currentReducedMotionMode: ReducedMotionMode { + #if canImport(UIKit) + if UIAccessibility.isReduceMotionEnabled { + return .reducedMotion + } else { + return .standardMotion + } + #else + return .standardMotion + #endif + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/RenderingEngineOption.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/RenderingEngineOption.swift new file mode 100644 index 0000000000000000000000000000000000000000..4ef01bcdec731ddc28eadb1bf3180d837290f963 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Configuration/RenderingEngineOption.swift @@ -0,0 +1,113 @@ +// Created by Cal Stephens on 7/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +// MARK: - RenderingEngineOption + +public enum RenderingEngineOption: Hashable { + /// Uses the Core Animation engine for supported animations, and falls back to using + /// the Main Thread engine for animations that use features not supported by the + /// Core Animation engine. + case automatic + + /// Uses the specified rendering engine + case specific(RenderingEngine) + + // MARK: Public + + /// The Main Thread rendering engine, which supports all Lottie features + /// but runs on the main thread, which comes with some CPU overhead and + /// can cause the animation to play at a low framerate when the CPU is busy. + public static var mainThread: RenderingEngineOption { .specific(.mainThread) } + + /// The Core Animation rendering engine, that animates using Core Animation + /// and has better performance characteristics than the Main Thread engine, + /// but doesn't support all Lottie features. + /// - In general, prefer using `RenderingEngineOption.automatic` over + /// `RenderingEngineOption.coreAnimation`. The Core Animation rendering + /// engine doesn't support all features supported by the Main Thread + /// rendering engine. When using `RenderingEngineOption.automatic`, + /// Lottie will automatically fall back to the Main Thread engine + /// when necessary. + public static var coreAnimation: RenderingEngineOption { .specific(.coreAnimation) } +} + +// MARK: - RenderingEngine + +/// The rendering engine implementation to use when displaying an animation +public enum RenderingEngine: Hashable { + /// The Main Thread rendering engine, which supports all Lottie features + /// but runs on the main thread, which comes with some CPU overhead and + /// can cause the animation to play at a low framerate when the CPU is busy. + case mainThread + + /// The Core Animation rendering engine, that animates using Core Animation + /// and has better performance characteristics than the Main Thread engine, + /// but doesn't support all Lottie features. + case coreAnimation +} + +// MARK: - RenderingEngineOption + RawRepresentable, CustomStringConvertible + +extension RenderingEngineOption: RawRepresentable, CustomStringConvertible { + + // MARK: Lifecycle + + public init?(rawValue: String) { + if rawValue == "Automatic" { + self = .automatic + } else if let engine = RenderingEngine(rawValue: rawValue) { + self = .specific(engine) + } else { + return nil + } + } + + // MARK: Public + + public var rawValue: String { + switch self { + case .automatic: + "Automatic" + case .specific(let engine): + engine.rawValue + } + } + + public var description: String { + rawValue + } + +} + +// MARK: - RenderingEngine + RawRepresentable, CustomStringConvertible + +extension RenderingEngine: RawRepresentable, CustomStringConvertible { + + // MARK: Lifecycle + + public init?(rawValue: String) { + switch rawValue { + case "Main Thread": + self = .mainThread + case "Core Animation": + self = .coreAnimation + default: + return nil + } + } + + // MARK: Public + + public var rawValue: String { + switch self { + case .mainThread: + "Main Thread" + case .coreAnimation: + "Core Animation" + } + } + + public var description: String { + rawValue + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedButton.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedButton.swift new file mode 100644 index 0000000000000000000000000000000000000000..1e91c67acd224347b0717c11c39d560255c17f80 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedButton.swift @@ -0,0 +1,127 @@ +// +// AnimatedButton.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +// MARK: - AnimatedButton + +/// An interactive button that plays an animation when pressed. +open class AnimatedButton: AnimatedControl { + + // MARK: Lifecycle + + public override init( + animation: LottieAnimation?, + configuration: LottieConfiguration = .shared) + { + super.init(animation: animation, configuration: configuration) + + #if canImport(UIKit) + isAccessibilityElement = true + #elseif canImport(AppKit) + setAccessibilityElement(true) + #endif + } + + public override init() { + super.init() + + #if canImport(UIKit) + isAccessibilityElement = true + #elseif canImport(AppKit) + setAccessibilityElement(true) + #endif + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + #if canImport(UIKit) + isAccessibilityElement = true + #elseif canImport(AppKit) + setAccessibilityElement(true) + #endif + } + + // MARK: Open + + #if canImport(UIKit) + open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + let _ = super.beginTracking(touch, with: event) + let touchEvent = UIControl.Event.touchDown + if let playRange = rangesForEvents[touchEvent.id] { + animationView.play(fromProgress: playRange.from, toProgress: playRange.to, loopMode: .playOnce) + } + return true + } + + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + let touchEvent: UIControl.Event + if let touch, bounds.contains(touch.location(in: self)) { + touchEvent = UIControl.Event.touchUpInside + performAction?() + } else { + touchEvent = UIControl.Event.touchUpOutside + } + + if let playRange = rangesForEvents[touchEvent.id] { + animationView.play(fromProgress: playRange.from, toProgress: playRange.to, loopMode: .playOnce) + } + } + + #elseif canImport(AppKit) + open override func handle(_ event: LottieNSControlEvent) { + super.handle(event) + + if let playRange = rangesForEvents[event.id] { + animationView.play(fromProgress: playRange.from, toProgress: playRange.to, loopMode: .playOnce) + } + + if event == .touchUpInside { + performAction?() + } + } + #endif + + // MARK: Public + + /// A closure that is called when the button is pressed / clicked + public var performAction: (() -> Void)? + + #if canImport(UIKit) + public override var accessibilityTraits: UIAccessibilityTraits { + set { super.accessibilityTraits = newValue } + get { super.accessibilityTraits.union(.button) } + } + #endif + + /// Sets the play range for the given UIControlEvent. + public func setPlayRange(fromProgress: AnimationProgressTime, toProgress: AnimationProgressTime, event: LottieControlEvent) { + rangesForEvents[event.id] = (from: fromProgress, to: toProgress) + } + + /// Sets the play range for the given UIControlEvent. + public func setPlayRange(fromMarker fromName: String, toMarker toName: String, event: LottieControlEvent) { + if + let start = animationView.progressTime(forMarker: fromName), + let end = animationView.progressTime(forMarker: toName) + { + rangesForEvents[event.id] = (from: start, to: end) + } + } + + // MARK: Private + + private var rangesForEvents: [AnyHashable: (from: CGFloat, to: CGFloat)] = [LottieControlEvent.touchUpInside.id: ( + from: 0, + to: 1)] +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedControl.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedControl.swift new file mode 100644 index 0000000000000000000000000000000000000000..ae2cdd563fad6e3490b83da334523ef3a06918d3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedControl.swift @@ -0,0 +1,245 @@ +// +// AnimatedControl.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +// MARK: - AnimatedControl + +/// Lottie comes prepacked with a two Animated Controls, `AnimatedSwitch` and +/// `AnimatedButton`. Both of these controls are built on top of `AnimatedControl` +/// +/// `AnimatedControl` is a subclass of `UIControl` that provides an interactive +/// mechanism for controlling the visual state of an animation in response to +/// user actions. +/// +/// The `AnimatedControl` will show and hide layers depending on the current +/// `UIControl.State` of the control. +/// +/// Users of `AnimationControl` can set a Layer Name for each `UIControl.State`. +/// When the state is change the `AnimationControl` will change the visibility +/// of its layers. +/// +/// NOTE: Do not initialize directly. This is intended to be subclassed. +open class AnimatedControl: LottieControlType { + + // MARK: Lifecycle + + // MARK: Initializers + + public init( + animation: LottieAnimation?, + configuration: LottieConfiguration = .shared) + { + animationView = LottieAnimationView( + animation: animation, + configuration: configuration) + + super.init(frame: animation?.bounds ?? .zero) + commonInit() + } + + public init() { + animationView = LottieAnimationView() + super.init(frame: .zero) + commonInit() + } + + required public init?(coder aDecoder: NSCoder) { + animationView = LottieAnimationView() + super.init(coder: aDecoder) + commonInit() + } + + // MARK: Open + + // MARK: UIControl Overrides + + open override var isEnabled: Bool { + didSet { + updateForState() + } + } + + #if canImport(UIKit) + open override var isSelected: Bool { + didSet { + updateForState() + } + } + #endif + + open override var isHighlighted: Bool { + didSet { + updateForState() + } + } + + open override var intrinsicContentSize: CGSize { + animationView.intrinsicContentSize + } + + #if canImport(UIKit) + open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + updateForState() + return super.beginTracking(touch, with: event) + } + + open override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + updateForState() + return super.continueTracking(touch, with: event) + } + + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + updateForState() + return super.endTracking(touch, with: event) + } + + open override func cancelTracking(with event: UIEvent?) { + updateForState() + super.cancelTracking(with: event) + } + + #elseif canImport(AppKit) + open override func mouseDown(with mouseDownEvent: NSEvent) { + guard let window else { return } + + currentState = .highlighted + updateForState() + handle(LottieControlEvent(mouseDownEvent.type, inside: eventIsInside(mouseDownEvent))) + + // AppKit mouse-tracking loop from: + // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/HandlingMouseEvents/HandlingMouseEvents.html#//apple_ref/doc/uid/10000060i-CH6-SW4 + var keepOn = true + while + keepOn, + let event = window.nextEvent( + matching: .any, + until: .distantFuture, + inMode: .eventTracking, + dequeue: true) + { + if event.type == .leftMouseUp { + keepOn = false + } + + let isInside = eventIsInside(event) + handle(LottieControlEvent(event.type, inside: isInside)) + + let expectedState = (isInside && keepOn) ? LottieNSControlState.highlighted : .normal + if currentState != expectedState { + currentState = expectedState + updateForState() + } + } + } + + func handle(_: LottieNSControlEvent) { + // To be overridden in subclasses + } + + private func eventIsInside(_ event: NSEvent) -> Bool { + let mouseLocation = convert(event.locationInWindow, from: nil) + return isMousePoint(mouseLocation, in: bounds) + } + #endif + + open func animationDidSet() { } + + // MARK: Public + + /// The animation view in which the animation is rendered. + public let animationView: LottieAnimationView + + /// The animation backing the animated control. + public var animation: LottieAnimation? { + didSet { + animationView.animation = animation + animationView.bounds = animation?.bounds ?? .zero + #if canImport(UIKit) + setNeedsLayout() + #elseif canImport(AppKit) + needsLayout = true + #endif + updateForState() + animationDidSet() + } + } + + /// The speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat { + set { animationView.animationSpeed = newValue } + get { animationView.animationSpeed } + } + + /// Sets which Animation Layer should be visible for the given state. + public func setLayer(named: String, forState: LottieControlState) { + stateMap[forState.rawValue] = named + updateForState() + } + + /// Sets a ValueProvider for the specified keypath + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + animationView.setValueProvider(valueProvider, keypath: keypath) + } + + // MARK: Internal + + var stateMap: [UInt: String] = [:] + + #if canImport(UIKit) + var currentState: LottieControlState { + state + } + + #elseif canImport(AppKit) + var currentState = LottieControlState.normal + #endif + + func updateForState() { + guard let animationLayer = animationView.animationLayer else { return } + if + let layerName = stateMap[currentState.rawValue], + let stateLayer = animationLayer.layer(for: AnimationKeypath(keypath: layerName)) + { + for layer in animationLayer._animationLayers { + layer.isHidden = true + } + stateLayer.isHidden = false + } else { + for layer in animationLayer._animationLayers { + layer.isHidden = false + } + } + } + + // MARK: Private + + private func commonInit() { + #if canImport(UIKit) + animationView.clipsToBounds = false + clipsToBounds = true + #endif + + animationView.translatesAutoresizingMaskIntoConstraints = false + animationView.backgroundBehavior = .forceFinish + addSubview(animationView) + animationView.contentMode = .scaleAspectFit + + #if canImport(UIKit) + animationView.isUserInteractionEnabled = false + #endif + + animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true + animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedSwitch.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedSwitch.swift new file mode 100644 index 0000000000000000000000000000000000000000..e116e06e31c3bd8bb7c58bbc44d67596f7af6fb1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/AnimatedSwitch.swift @@ -0,0 +1,271 @@ +// +// AnimatedSwitch.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +// MARK: - AnimatedSwitch + +/// An interactive switch with an 'On' and 'Off' state. When the user taps on the +/// switch the state is toggled and the appropriate animation is played. +/// +/// Both the 'On' and 'Off' have an animation play range associated with their state. +/// +/// Also available as a SwiftUI view (`LottieSwitch`). +open class AnimatedSwitch: AnimatedControl { + + // MARK: Lifecycle + + public override init( + animation: LottieAnimation?, + configuration: LottieConfiguration = .shared) + { + /// Generate a haptic generator if available. + #if os(iOS) + hapticGenerator = HapticGenerator() + #else + hapticGenerator = NullHapticGenerator() + #endif + super.init(animation: animation, configuration: configuration) + + #if canImport(UIKit) + isAccessibilityElement = true + #elseif canImport(AppKit) + setAccessibilityElement(true) + #endif + + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + } + + public override init() { + /// Generate a haptic generator if available. + #if os(iOS) + hapticGenerator = HapticGenerator() + #else + hapticGenerator = NullHapticGenerator() + #endif + + super.init() + + #if canImport(UIKit) + isAccessibilityElement = true + #elseif canImport(AppKit) + setAccessibilityElement(true) + #endif + + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + } + + required public init?(coder aDecoder: NSCoder) { + /// Generate a haptic generator if available. + #if os(iOS) + hapticGenerator = HapticGenerator() + #else + hapticGenerator = NullHapticGenerator() + #endif + super.init(coder: aDecoder) + + #if canImport(UIKit) + isAccessibilityElement = true + #elseif canImport(AppKit) + setAccessibilityElement(true) + #endif + } + + // MARK: Open + + open override func animationDidSet() { + updateOnState(isOn: _isOn, animated: animateUpdateWhenChangingAnimation, shouldFireHaptics: false) + } + + #if canImport(UIKit) + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + updateOnState(isOn: !_isOn, animated: true, shouldFireHaptics: true) + sendActions(for: .valueChanged) + } + + #elseif canImport(AppKit) + open override func handle(_ event: LottieNSControlEvent) { + super.handle(event) + + if event == .touchUpInside { + updateOnState(isOn: !_isOn, animated: true, shouldFireHaptics: true) + } + } + #endif + + // MARK: Public + + /// Defines what happens when the user taps the switch while an + /// animation is still in flight + public enum CancelBehavior { + case reverse // default - plays the current animation in reverse + case none // does not update the animation when canceled + } + + /// The cancel behavior for the switch. See CancelBehavior for options + public var cancelBehavior: CancelBehavior = .reverse + + /// If `false` the switch will not play the animation when changing between animations. + public var animateUpdateWhenChangingAnimation = true + + #if canImport(UIKit) + public override var accessibilityTraits: UIAccessibilityTraits { + set { super.accessibilityTraits = newValue } + get { super.accessibilityTraits.union(.button) } + } + #endif + + /// A closure that is called when the `isOn` state is updated + public var stateUpdated: ((_ isOn: Bool) -> Void)? + + /// The current state of the switch. + public var isOn: Bool { + set { + /// This is forwarded to a private variable because the animation needs to be updated without animation when set externally and with animation when set internally. + guard _isOn != newValue else { return } + updateOnState(isOn: newValue, animated: false, shouldFireHaptics: false) + } + get { + _isOn + } + } + + /// Set the state of the switch and specify animation and haptics + public func setIsOn(_ isOn: Bool, animated: Bool, shouldFireHaptics: Bool = true) { + guard isOn != _isOn else { return } + updateOnState(isOn: isOn, animated: animated, shouldFireHaptics: shouldFireHaptics) + } + + /// Sets the play range for the given state. When the switch is toggled, the animation range is played. + public func setProgressForState( + fromProgress: AnimationProgressTime, + toProgress: AnimationProgressTime, + forOnState: Bool) + { + if forOnState { + onStartProgress = fromProgress + onEndProgress = toProgress + } else { + offStartProgress = fromProgress + offEndProgress = toProgress + } + + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + } + + // MARK: Internal + + private(set) var onStartProgress: CGFloat = 0 + private(set) var onEndProgress: CGFloat = 1 + private(set) var offStartProgress: CGFloat = 1 + private(set) var offEndProgress: CGFloat = 0 + + // MARK: Animation State + + func updateOnState(isOn: Bool, animated: Bool, shouldFireHaptics: Bool) { + _isOn = isOn + var startProgress = isOn ? onStartProgress : offStartProgress + var endProgress = isOn ? onEndProgress : offEndProgress + let finalProgress = endProgress + + if cancelBehavior == .reverse { + let realtimeProgress = animationView.realtimeAnimationProgress + + let previousStateStart = isOn ? offStartProgress : onStartProgress + let previousStateEnd = isOn ? offEndProgress : onEndProgress + if + realtimeProgress.isInRange( + min(previousStateStart, previousStateEnd), + max(previousStateStart, previousStateEnd)) + { + /// Animation is currently in the previous time range. Reverse the previous play. + startProgress = previousStateEnd + endProgress = previousStateStart + } + } + + updateAccessibilityLabel() + + guard animated == true else { + animationView.currentProgress = finalProgress + return + } + + if shouldFireHaptics { + hapticGenerator.generateImpact() + } + + animationView.play( + fromProgress: startProgress, + toProgress: endProgress, + loopMode: LottieLoopMode.playOnce, + completion: { [weak self] finished in + guard let self else { return } + + // For the Main Thread rendering engine, we freeze the animation at the expected final progress + // once the animation is complete. This isn't necessary on the Core Animation engine. + if finished, !(animationView.animationLayer is CoreAnimationLayer) { + animationView.currentProgress = finalProgress + } + }) + } + + // MARK: Fileprivate + + fileprivate var hapticGenerator: ImpactGenerator + + fileprivate var _isOn = false { + didSet { + stateUpdated?(_isOn) + } + } + + // MARK: Private + + private func updateAccessibilityLabel() { + let value = _isOn ? NSLocalizedString("On", comment: "On") : NSLocalizedString("Off", comment: "Off") + + #if canImport(UIKit) + accessibilityValue = value + #elseif canImport(AppKit) + setAccessibilityValue(value) + #endif + } + +} + +// MARK: - ImpactGenerator + +protocol ImpactGenerator { + func generateImpact() +} + +#if os(iOS) +class HapticGenerator: ImpactGenerator { + + // MARK: Internal + + func generateImpact() { + impact.impactOccurred() + } + + // MARK: Fileprivate + + fileprivate let impact = UIImpactFeedbackGenerator(style: .light) +} +#else +// MARK: - NullHapticGenerator + +class NullHapticGenerator: ImpactGenerator { + func generateImpact() { } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieButton.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieButton.swift new file mode 100644 index 0000000000000000000000000000000000000000..396a10127554a178a7e603d2a7cf5bd70c3aba93 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieButton.swift @@ -0,0 +1,122 @@ +// Created by Cal Stephens on 8/14/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +/// A wrapper which exposes Lottie's `AnimatedButton` to SwiftUI +public struct LottieButton: UIViewConfiguringSwiftUIView { + + // MARK: Lifecycle + + public init(animation: LottieAnimation?, action: @escaping () -> Void) { + self.animation = animation + self.action = action + } + + // MARK: Public + + public var body: some View { + AnimatedButton.swiftUIView { + let button = AnimatedButton(animation: animation, configuration: configuration) + button.performAction = action + return button + } + .configure { context in + // We check referential equality of the animation before updating as updating the + // animation has a side-effect of rebuilding the animation layer, and it would be + // prohibitive to do so on every state update. + if animation !== context.view.animationView.animation { + context.view.animationView.animation = animation + } + + #if os(macOS) + // Disable the intrinsic content size constraint on the inner animation view, + // or the Epoxy `SwiftUIMeasurementContainer` won't size this view correctly. + context.view.animationView.isVerticalContentSizeConstraintActive = false + context.view.animationView.isHorizontalContentSizeConstraintActive = false + #endif + } + .configurations(configurations) + } + + /// Returns a copy of this `LottieView` updated to have the given closure applied to its + /// represented `LottieAnimationView` whenever it is updated via the `updateUIView(…)` + /// or `updateNSView(…)` method. + public func configure(_ configure: @escaping (AnimatedButton) -> Void) -> Self { + var copy = self + copy.configurations.append { context in + configure(context.view) + } + return copy + } + + /// Returns a copy of this view with its `LottieConfiguration` updated to the given value. + public func configuration(_ configuration: LottieConfiguration) -> Self { + var copy = self + copy.configuration = configuration + + copy = copy.configure { view in + if view.animationView.configuration != configuration { + view.animationView.configuration = configuration + } + } + + return copy + } + + /// Returns a copy of this view configured to animate between the + /// given progress values when the given event is triggered + public func animate( + fromProgress: AnimationProgressTime, + toProgress: AnimationProgressTime, + on event: LottieControlEvent) + -> Self + { + configure { view in + // `setPlayRange` just modifies a dictionary, + // so we can just call it on every state update without diffing + view.setPlayRange(fromProgress: fromProgress, toProgress: toProgress, event: event) + } + } + + /// Returns a copy of this view configured to animate between the + /// given markers when the given event is triggered + public func animate( + fromMarker: String, + toMarker: String, + on event: LottieControlEvent) + -> Self + { + configure { view in + // `setPlayRange` just modifies a dictionary, + // so we can just call it on every state update without diffing + view.setPlayRange(fromMarker: fromMarker, toMarker: toMarker, event: event) + } + } + + /// Returns a copy of this view using the given value provider for the given keypath. + /// The value provider must be `Equatable` to avoid unnecessary state updates / re-renders. + public func valueProvider( + _ valueProvider: ValueProvider, + for keypath: AnimationKeypath) + -> Self + { + configure { view in + if (view.animationView.valueProviders[keypath] as? ValueProvider) != valueProvider { + view.animationView.setValueProvider(valueProvider, keypath: keypath) + } + } + } + + // MARK: Internal + + var configurations = [SwiftUIView.Configuration]() + + // MARK: Private + + private let animation: LottieAnimation? + private let action: () -> Void + private var configuration: LottieConfiguration = .shared +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieSwitch.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieSwitch.swift new file mode 100644 index 0000000000000000000000000000000000000000..fb657832967e8c2b43249aa2b850dae513531532 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieSwitch.swift @@ -0,0 +1,145 @@ +// Created by Cal Stephens on 8/11/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(SwiftUI) +import SwiftUI + +/// A wrapper which exposes Lottie's `AnimatedSwitch` to SwiftUI +public struct LottieSwitch: UIViewConfiguringSwiftUIView { + + // MARK: Lifecycle + + public init(animation: LottieAnimation?) { + self.animation = animation + } + + // MARK: Public + + public var body: some View { + AnimatedSwitch.swiftUIView { + let animatedSwitch = AnimatedSwitch(animation: animation, configuration: configuration) + animatedSwitch.isOn = isOn?.wrappedValue ?? false + return animatedSwitch + } + .configure { context in + // We check referential equality of the animation before updating as updating the + // animation has a side-effect of rebuilding the animation layer, and it would be + // prohibitive to do so on every state update. + if animation !== context.view.animationView.animation { + context.view.animationView.animation = animation + } + + #if os(macOS) + // Disable the intrinsic content size constraint on the inner animation view, + // or the Epoxy `SwiftUIMeasurementContainer` won't size this view correctly. + context.view.animationView.isVerticalContentSizeConstraintActive = false + context.view.animationView.isHorizontalContentSizeConstraintActive = false + #endif + + if let isOn = isOn?.wrappedValue, isOn != context.view.isOn { + context.view.setIsOn(isOn, animated: true) + } + } + .configurations(configurations) + } + + /// Returns a copy of this `LottieView` updated to have the given closure applied to its + /// represented `LottieAnimationView` whenever it is updated via the `updateUIView(…)` + /// or `updateNSView(…)` method. + public func configure(_ configure: @escaping (AnimatedSwitch) -> Void) -> Self { + var copy = self + copy.configurations.append { context in + configure(context.view) + } + return copy + } + + /// Returns a copy of this view with its `LottieConfiguration` updated to the given value. + public func configuration(_ configuration: LottieConfiguration) -> Self { + var copy = self + copy.configuration = configuration + + copy = copy.configure { view in + if view.animationView.configuration != configuration { + view.animationView.configuration = configuration + } + } + + return copy + } + + /// Returns a copy of this view with the given `Binding` reflecting the `isOn` state of the switch. + public func isOn(_ binding: Binding) -> Self { + var copy = self + copy.isOn = binding + return copy.configure { view in + view.stateUpdated = { isOn in + DispatchQueue.main.async { + binding.wrappedValue = isOn + } + } + } + } + + /// Returns a copy of this view with the "on" animation configured + /// to start and end at the given progress values. + /// Defaults to playing the entire animation forwards (0...1). + public func onAnimation( + fromProgress onStartProgress: AnimationProgressTime, + toProgress onEndProgress: AnimationProgressTime) + -> Self + { + configure { view in + if onStartProgress != view.onStartProgress || onEndProgress != view.onEndProgress { + view.setProgressForState( + fromProgress: onStartProgress, + toProgress: onEndProgress, + forOnState: true) + } + } + } + + /// Returns a copy of this view with the "on" animation configured + /// to start and end at the given progress values. + /// Defaults to playing the entire animation backwards (1...0). + public func offAnimation( + fromProgress offStartProgress: AnimationProgressTime, + toProgress offEndProgress: AnimationProgressTime) + -> Self + { + configure { view in + if offStartProgress != view.offStartProgress || offEndProgress != view.offEndProgress { + view.setProgressForState( + fromProgress: offStartProgress, + toProgress: offEndProgress, + forOnState: false) + } + } + } + + /// Returns a copy of this view using the given value provider for the given keypath. + /// The value provider must be `Equatable` to avoid unnecessary state updates / re-renders. + public func valueProvider( + _ valueProvider: ValueProvider, + for keypath: AnimationKeypath) + -> Self + { + configure { view in + if (view.animationView.valueProviders[keypath] as? ValueProvider) != valueProvider { + view.animationView.setValueProvider(valueProvider, keypath: keypath) + } + } + } + + // MARK: Internal + + var configurations = [SwiftUIView.Configuration]() + + // MARK: Private + + private let animation: LottieAnimation? + private var configuration: LottieConfiguration = .shared + private var isOn: Binding? + +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieViewType.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieViewType.swift new file mode 100644 index 0000000000000000000000000000000000000000..ec97c97df0c3fce7f9d0d8c3b8f43d715a5fd272 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Controls/LottieViewType.swift @@ -0,0 +1,79 @@ +// Created by Cal Stephens on 8/11/23. +// Copyright © 2023 Airbnb Inc. All rights reserved. + +#if canImport(UIKit) +import UIKit + +/// The control base type for this platform. +/// - `UIControl` on iOS / tvOS and `NSControl` on macOS. +public typealias LottieControlType = UIControl + +/// The `State` type of `LottieControlType` +/// - `UIControl.State` on iOS / tvOS and `NSControl.StateValue` on macOS. +public typealias LottieControlState = UIControl.State + +/// The event type handled by the `LottieControlType` component for this platform. +/// - `UIControl.Event` on iOS / tvOS and `LottieNSControlEvent` on macOS. +public typealias LottieControlEvent = UIControl.Event + +extension LottieControlEvent { + var id: AnyHashable { + rawValue + } +} +#else +import AppKit + +/// The control base type for this platform. +/// - `UIControl` on iOS / tvOS and `NSControl` on macOS. +public typealias LottieControlType = NSControl + +/// The `State` type of `LottieControlType` +/// - `UIControl.State` on iOS / tvOS and `NSControl.StateValue` on macOS. +public typealias LottieControlState = LottieNSControlState + +/// AppKit equivalent of `UIControl.State` for `AnimatedControl` +public enum LottieNSControlState: UInt, RawRepresentable { + /// The normal, or default, state of a control where the control is enabled but neither selected nor highlighted. + case normal + /// The highlighted state of a control. + case highlighted +} + +/// The event type handled by the `LottieControlType` component for this platform. +/// - `UIControl.Event` on iOS / tvOS and `LottieNSControlEvent` on macOS. +public typealias LottieControlEvent = LottieNSControlEvent + +public struct LottieNSControlEvent: Equatable, Sendable { + + // MARK: Lifecycle + + public init(_ event: NSEvent.EventType, inside: Bool) { + self.event = event + self.inside = inside + } + + // MARK: Public + + /// macOS equivalent to `UIControl.Event.touchDown` + public static let touchDown = LottieNSControlEvent(.leftMouseDown, inside: true) + + /// macOS equivalent to `UIControl.Event.touchUpInside` + public static let touchUpInside = LottieNSControlEvent(.leftMouseUp, inside: true) + + /// macOS equivalent to `UIControl.Event.touchUpInside` + public static let touchUpOutside = LottieNSControlEvent(.leftMouseUp, inside: false) + + /// The underlying `NSEvent.EventType` of this event, which is roughly equivalent to `UIControl.Event` + public var event: NSEvent.EventType + + /// Whether or not the mouse must be inside the control. + public var inside: Bool + + // MARK: Internal + + var id: AnyHashable { + [AnyHashable(event.rawValue), AnyHashable(inside)] + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/Cache/DotLottieCache.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/Cache/DotLottieCache.swift new file mode 100644 index 0000000000000000000000000000000000000000..1e7d00b11be9b50b41a17929e9cdae00ad1581be --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/Cache/DotLottieCache.swift @@ -0,0 +1,66 @@ +// +// LRUDotLottieCache.swift +// Lottie +// +// Created by Evandro Hoffmann on 20/10/22. +// + +import Foundation + +// MARK: - DotLottieCache + +/// A DotLottie Cache that will store lottie files up to `cacheSize`. +/// +/// Once `cacheSize` is reached, the least recently used lottie will be ejected. +/// The default size of the cache is 100. +public class DotLottieCache: DotLottieCacheProvider { + + // MARK: Lifecycle + + public init() { + cache.countLimit = Self.defaultCacheCountLimit + } + + // MARK: Public + + /// The global shared Cache. + public static let sharedCache = DotLottieCache() + + /// The size of the cache. + public var cacheSize = defaultCacheCountLimit { + didSet { + cache.countLimit = cacheSize + } + } + + /// Clears the Cache. + public func clearCache() { + cache.removeAllValues() + } + + public func file(forKey key: String) -> DotLottieFile? { + cache.value(forKey: key) + } + + public func setFile(_ lottie: DotLottieFile, forKey key: String) { + cache.setValue(lottie, forKey: key) + } + + // MARK: Private + + private static let defaultCacheCountLimit = 100 + + /// The underlying storage of this cache. + /// - We use the `LRUCache` library instead of `NSCache`, because `NSCache` + /// clears all cached values when the app is backgrounded instead of + /// only when the app receives a memory warning notification. + private var cache = LRUCache() + +} + +// MARK: Sendable + +// DotLottieCacheProvider has a Sendable requirement, but we can't +// redesign DotLottieCache to be properly Sendable without making breaking changes. +// swiftlint:disable:next no_unchecked_sendable +extension DotLottieCache: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/Cache/DotLottieCacheProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/Cache/DotLottieCacheProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..6a59cba287d44e2859bac5ac101cb2158d3decac --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/Cache/DotLottieCacheProvider.swift @@ -0,0 +1,21 @@ +// +// DotLottieCacheProvider.swift +// Lottie +// +// Created by Evandro Hoffmann on 20/10/22. +// + +/// `DotLottieCacheProvider` is a protocol that describes a DotLottie Cache. +/// DotLottie Cache is used when loading `DotLottie` models. Using a DotLottie Cache +/// can increase performance when loading an animation multiple times. +/// +/// Lottie comes with a prebuilt LRU DotLottie Cache. +public protocol DotLottieCacheProvider: Sendable { + + func file(forKey: String) -> DotLottieFile? + + func setFile(_ lottie: DotLottieFile, forKey: String) + + func clearCache() + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieConfiguration.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieConfiguration.swift new file mode 100644 index 0000000000000000000000000000000000000000..2de4b746c95ee9bd8caec0899fc20487c647bde7 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieConfiguration.swift @@ -0,0 +1,72 @@ +// +// DotLottieConfiguration.swift +// Lottie +// +// Created by Evandro Hoffmann on 19/10/22. +// + +// MARK: - DotLottieConfiguration + +/// The `DotLottieConfiguration` model holds the presets extracted from DotLottieAnimation +/// - The presets are used as input to setup `LottieAnimationView` before playing the animation. +public struct DotLottieConfiguration { + + // MARK: Public + + /// id of the animation + public var id: String + + /// Loop behavior of animation + public var loopMode: LottieLoopMode + + /// Playback speed of animation + public var speed: Double + + /// Animation Image Provider + public var imageProvider: AnimationImageProvider? { + dotLottieImageProvider + } + + // MARK: Internal + + /// The underlying `DotLottieImageProvider` used by this dotLottie animation + var dotLottieImageProvider: DotLottieImageProvider? +} + +// MARK: - DotLottieConfigurationComponents + +/// Components of the `DotLottieConfiguration` to apply to the `LottieAnimationView`. +/// - When using `LottieView`, if the component is selected to be applied it will +/// override any value provided via other `LottieView` APIs. +public struct DotLottieConfigurationComponents: OptionSet { + + // MARK: Lifecycle + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + // MARK: Public + + /// `DotLottieConfiguration.imageProvider` will be applied to the `LottieAnimationView` + /// - When using `LottieView`, the image provider from the dotLottie animation will override + /// the image provider applied manually using `LottieView.imageProvider(...)`. + public static let imageProvider = DotLottieConfigurationComponents(rawValue: 1 << 0) + + /// `DotLottieConfigurationMode.loopMode` will be applied to the `LottieAnimationView`. + /// - When using `LottieView`, the loop mode from the dotLottie animation will override + /// the loopMode applied by any playback method. + public static let loopMode = DotLottieConfigurationComponents(rawValue: 1 << 1) + + /// `DotLottieConfigurationMode.speed` will be applied to the `LottieAnimationView`. + /// - When using `LottieView`, the speed from the dotLottie animation will override + /// the speed applied manually using `LottieView.animationSpeed(...)`. + public static let animationSpeed = DotLottieConfigurationComponents(rawValue: 1 << 2) + + public static let all: DotLottieConfigurationComponents = [.imageProvider, .loopMode, .animationSpeed] + + public static let none: DotLottieConfigurationComponents = [] + + public let rawValue: Int + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieFile.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieFile.swift new file mode 100644 index 0000000000000000000000000000000000000000..4e329b2d74d98d18c6c7eb97c6ceb312ad5846d2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieFile.swift @@ -0,0 +1,154 @@ +// +// DotLottie.swift +// Lottie +// +// Created by Evandro Harrison Hoffmann on 27/06/2020. +// + +import Foundation + +// MARK: - DotLottieFile + +/// Detailed .lottie file structure +public final class DotLottieFile { + + // MARK: Lifecycle + + /// Loads `DotLottie` from `Data` object containing a compressed animation. + /// + /// - Parameters: + /// - data: Data of .lottie file + /// - filename: Name of .lottie file + /// - Returns: Deserialized `DotLottie`. Optional. + init(data: Data, filename: String) throws { + fileUrl = DotLottieUtils.tempDirectoryURL.appendingPathComponent(filename.asFilename()) + try decompress(data: data, to: fileUrl) + } + + // MARK: Public + + /// Definition for a single animation within a `DotLottieFile` + public struct Animation { + public let animation: LottieAnimation + public let configuration: DotLottieConfiguration + } + + /// List of `LottieAnimation` in the file + public private(set) var animations: [Animation] = [] + + // MARK: Internal + + /// Image provider for animations + private(set) var imageProvider: DotLottieImageProvider? + + /// Animations folder url + lazy var animationsUrl: URL = fileUrl.appendingPathComponent("\(DotLottieFile.animationsFolderName)") + + /// All files in animations folder + lazy var animationUrls: [URL] = FileManager.default.urls(for: animationsUrl) ?? [] + + /// Images folder url + lazy var imagesUrl: URL = fileUrl.appendingPathComponent("\(DotLottieFile.imagesFolderName)") + + /// All images in images folder + lazy var imageUrls: [URL] = FileManager.default.urls(for: imagesUrl) ?? [] + + /// The `LottieAnimation` and `DotLottieConfiguration` for the given animation ID in this file + func animation(for id: String? = nil) -> DotLottieFile.Animation? { + if let id { + animations.first(where: { $0.configuration.id == id }) + } else { + animations.first + } + } + + /// The `LottieAnimation` and `DotLottieConfiguration` for the given animation index in this file + func animation(at index: Int) -> DotLottieFile.Animation? { + guard index < animations.count else { return nil } + return animations[index] + } + + // MARK: Private + + private static let manifestFileName = "manifest.json" + private static let animationsFolderName = "animations" + private static let imagesFolderName = "images" + + private let fileUrl: URL + + /// Decompresses .lottie file from `URL` and saves to local temp folder + /// + /// - Parameters: + /// - url: url to .lottie file + /// - destinationURL: url to destination of decompression contents + private func decompress(from url: URL, to destinationURL: URL) throws { + try? FileManager.default.removeItem(at: destinationURL) + try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.unzipItem(at: url, to: destinationURL) + try loadContent() + try? FileManager.default.removeItem(at: destinationURL) + try? FileManager.default.removeItem(at: url) + } + + /// Decompresses .lottie file from `Data` and saves to local temp folder + /// + /// - Parameters: + /// - url: url to .lottie file + /// - destinationURL: url to destination of decompression contents + private func decompress(data: Data, to destinationURL: URL) throws { + let url = destinationURL.appendingPathExtension("lottie") + try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) + try data.write(to: url) + try decompress(from: url, to: destinationURL) + } + + /// Loads file content to memory + private func loadContent() throws { + imageProvider = DotLottieImageProvider(filepath: imagesUrl) + + animations = try loadManifest().animations.map { dotLottieAnimation in + let animation = try dotLottieAnimation.animation(url: animationsUrl) + let configuration = DotLottieConfiguration( + id: dotLottieAnimation.id, + loopMode: dotLottieAnimation.loopMode, + speed: dotLottieAnimation.animationSpeed, + dotLottieImageProvider: imageProvider) + + return DotLottieFile.Animation( + animation: animation, + configuration: configuration) + } + } + + private func loadManifest() throws -> DotLottieManifest { + let path = fileUrl.appendingPathComponent(DotLottieFile.manifestFileName) + return try DotLottieManifest.load(from: path) + } +} + +extension String { + + // MARK: Fileprivate + + fileprivate func asFilename() -> String { + lastPathComponent().removingPathExtension() + } + + // MARK: Private + + private func lastPathComponent() -> String { + (self as NSString).lastPathComponent + } + + private func removingPathExtension() -> String { + (self as NSString).deletingPathExtension + } +} + +// MARK: - DotLottieFile + Sendable + +// Mark `DotLottieFile` as `@unchecked Sendable` to allow it to be used when strict concurrency is enabled. +// In the future, it may be necessary to make changes to the internal implementation of `DotLottieFile` +// to make it truly thread-safe. +// swiftlint:disable:next no_unchecked_sendable +extension DotLottieFile: @unchecked Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieFileHelpers.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieFileHelpers.swift new file mode 100644 index 0000000000000000000000000000000000000000..018bd597087f869a568c778f8b5692f91eb7f552 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DotLottie/DotLottieFileHelpers.swift @@ -0,0 +1,373 @@ +// +// DotLottieFileHelpers.swift +// Lottie +// +// Created by Evandro Hoffmann on 20/10/22. +// + +import Foundation + +extension DotLottieFile { + + public enum SynchronouslyBlockingCurrentThread { + /// Loads an DotLottie from a specific filepath synchronously. Returns a `Result` + /// Please use the asynchronous methods whenever possible. This operation will block the Thread it is running in. + /// + /// - Parameter filepath: The absolute filepath of the lottie to load. EG "/User/Me/starAnimation.lottie" + /// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional. + public static func loadedFrom( + filepath: String, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache) + -> Result + { + LottieLogger.shared.assert( + !Thread.isMainThread, + "`DotLottieFile.SynchronouslyBlockingCurrentThread` methods shouldn't be called on the main thread.") + + /// Check cache for lottie + if + let dotLottieCache, + let lottie = dotLottieCache.file(forKey: filepath) + { + return .success(lottie) + } + + do { + /// Decode the lottie. + let url = URL(fileURLWithPath: filepath) + let data = try Data(contentsOf: url) + let lottie = try DotLottieFile(data: data, filename: url.deletingPathExtension().lastPathComponent) + dotLottieCache?.setFile(lottie, forKey: filepath) + return .success(lottie) + } catch { + /// Decoding Error. + return .failure(error) + } + } + + /// Loads a DotLottie model from a bundle by its name synchronously. Returns a `Result` + /// Please use the asynchronous methods whenever possible. This operation will block the Thread it is running in. + /// + /// - Parameter name: The name of the lottie file without the lottie extension. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main` + /// - Parameter subdirectory: A subdirectory in the bundle in which the lottie is located. Optional. + /// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional. + public static func named( + _ name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache) + -> Result + { + LottieLogger.shared.assert( + !Thread.isMainThread, + "`DotLottieFile.SynchronouslyBlockingCurrentThread` methods shouldn't be called on the main thread.") + + /// Create a cache key for the lottie. + let cacheKey = bundle.bundlePath + (subdirectory ?? "") + "/" + name + + /// Check cache for lottie + if + let dotLottieCache, + let lottie = dotLottieCache.file(forKey: cacheKey) + { + return .success(lottie) + } + + do { + /// Decode animation. + let data = try bundle.dotLottieData(name, subdirectory: subdirectory) + let lottie = try DotLottieFile(data: data, filename: name) + dotLottieCache?.setFile(lottie, forKey: cacheKey) + return .success(lottie) + } catch { + /// Decoding error. + LottieLogger.shared.warn("Error when decoding lottie \"\(name)\": \(error)") + return .failure(error) + } + } + + /// Loads an DotLottie from a data synchronously. Returns a `Result` + /// + /// Please use the asynchronous methods whenever possible. This operation will block the Thread it is running in. + /// + /// - Parameters: + /// - data: The data(`Foundation.Data`) object to load DotLottie from + /// - filename: The name of the lottie file without the lottie extension. eg. "StarAnimation" + public static func loadedFrom( + data: Data, + filename: String) + -> Result + { + LottieLogger.shared.assert( + !Thread.isMainThread, + "`DotLottieFile.SynchronouslyBlockingCurrentThread` methods shouldn't be called on the main thread.") + + do { + let dotLottieFile = try DotLottieFile(data: data, filename: filename) + return .success(dotLottieFile) + } catch { + return .failure(error) + } + } + } + + /// Loads a DotLottie model from a bundle by its name. Returns `nil` if a file is not found. + /// + /// - Parameter name: The name of the lottie file without the lottie extension. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main` + /// - Parameter subdirectory: A subdirectory in the bundle in which the lottie is located. Optional. + /// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional. + public static func named( + _ name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache) + async throws -> DotLottieFile + { + try await withCheckedThrowingContinuation { continuation in + DotLottieFile.named(name, bundle: bundle, subdirectory: subdirectory, dotLottieCache: dotLottieCache) { result in + continuation.resume(with: result) + } + } + } + + /// Loads a DotLottie model from a bundle by its name. Returns `nil` if a file is not found. + /// + /// - Parameter name: The name of the lottie file without the lottie extension. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main` + /// - Parameter subdirectory: A subdirectory in the bundle in which the lottie is located. Optional. + /// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional. + /// - Parameter dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional. + /// - Parameter handleResult: A closure to be called when the file has loaded. + public static func named( + _ name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + dispatchQueue: DispatchQueue = .dotLottie, + handleResult: @escaping (Result) -> Void) + { + dispatchQueue.async { + let result = SynchronouslyBlockingCurrentThread.named( + name, + bundle: bundle, + subdirectory: subdirectory, + dotLottieCache: dotLottieCache) + + DispatchQueue.main.async { + handleResult(result) + } + } + } + + /// Loads an DotLottie from a specific filepath. + /// - Parameter filepath: The absolute filepath of the lottie to load. EG "/User/Me/starAnimation.lottie" + /// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional. + public static func loadedFrom( + filepath: String, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache) + async throws -> DotLottieFile + { + try await withCheckedThrowingContinuation { continuation in + DotLottieFile.loadedFrom(filepath: filepath, dotLottieCache: dotLottieCache) { result in + continuation.resume(with: result) + } + } + } + + /// Loads an DotLottie from a specific filepath. + /// - Parameter filepath: The absolute filepath of the lottie to load. EG "/User/Me/starAnimation.lottie" + /// - Parameter dotLottieCache: A cache for holding loaded lotties. Defaults to `LRUDotLottieCache.sharedCache`. Optional. + /// - Parameter dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional. + /// - Parameter handleResult: A closure to be called when the file has loaded. + public static func loadedFrom( + filepath: String, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + dispatchQueue: DispatchQueue = .dotLottie, + handleResult: @escaping (Result) -> Void) + { + dispatchQueue.async { + let result = SynchronouslyBlockingCurrentThread.loadedFrom( + filepath: filepath, + dotLottieCache: dotLottieCache) + + DispatchQueue.main.async { + handleResult(result) + } + } + } + + /// Loads a DotLottie model from the asset catalog by its name. Returns `nil` if a lottie is not found. + /// - Parameter name: The name of the lottie file in the asset catalog. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main` + /// - Parameter dotLottieCache: A cache for holding loaded lottie files. Defaults to `LRUDotLottieCache.sharedCache` Optional. + public static func asset( + named name: String, + bundle: Bundle = Bundle.main, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache) + async throws -> DotLottieFile + { + try await withCheckedThrowingContinuation { continuation in + DotLottieFile.asset(named: name, bundle: bundle, dotLottieCache: dotLottieCache) { result in + continuation.resume(with: result) + } + } + } + + /// Loads a DotLottie model from the asset catalog by its name. Returns `nil` if a lottie is not found. + /// - Parameter name: The name of the lottie file in the asset catalog. EG "StarAnimation" + /// - Parameter bundle: The bundle in which the lottie is located. Defaults to `Bundle.main` + /// - Parameter dotLottieCache: A cache for holding loaded lottie files. Defaults to `LRUDotLottieCache.sharedCache` Optional. + /// - Parameter dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional. + /// - Parameter handleResult: A closure to be called when the file has loaded. + public static func asset( + named name: String, + bundle: Bundle = Bundle.main, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + dispatchQueue: DispatchQueue = .dotLottie, + handleResult: @escaping (Result) -> Void) + { + dispatchQueue.async { + /// Create a cache key for the lottie. + let cacheKey = bundle.bundlePath + "/" + name + + /// Check cache for lottie + if + let dotLottieCache, + let lottie = dotLottieCache.file(forKey: cacheKey) + { + /// If found, return the lottie. + DispatchQueue.main.async { + handleResult(.success(lottie)) + } + return + } + + do { + /// Load data from Asset + let data = try Data(assetName: name, in: bundle) + + /// Decode lottie. + let lottie = try DotLottieFile(data: data, filename: name) + dotLottieCache?.setFile(lottie, forKey: cacheKey) + DispatchQueue.main.async { + handleResult(.success(lottie)) + } + } catch { + /// Decoding error. + DispatchQueue.main.async { + handleResult(.failure(error)) + } + } + } + } + + /// Loads a DotLottie animation asynchronously from the URL. + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LRUAnimationCache.sharedCache`. Optional. + public static func loadedFrom( + url: URL, + session: URLSession = .shared, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache) + async throws -> DotLottieFile + { + try await withCheckedThrowingContinuation { continuation in + DotLottieFile.loadedFrom(url: url, session: session, dotLottieCache: dotLottieCache) { result in + continuation.resume(with: result) + } + } + } + + /// Loads a DotLottie animation asynchronously from the URL. + /// + /// - Parameter url: The url to load the animation from. + /// - Parameter animationCache: A cache for holding loaded animations. Defaults to `LRUAnimationCache.sharedCache`. Optional. + /// - Parameter handleResult: A closure to be called when the animation has loaded. + public static func loadedFrom( + url: URL, + session: URLSession = .shared, + dotLottieCache: DotLottieCacheProvider? = DotLottieCache.sharedCache, + handleResult: @escaping (Result) -> Void) + { + if let dotLottieCache, let lottie = dotLottieCache.file(forKey: url.absoluteString) { + handleResult(.success(lottie)) + } else { + let task = session.dataTask(with: url) { data, _, error in + do { + if let error { + throw error + } + guard let data else { + throw DotLottieError.noDataLoaded + } + let lottie = try DotLottieFile(data: data, filename: url.deletingPathExtension().lastPathComponent) + DispatchQueue.main.async { + dotLottieCache?.setFile(lottie, forKey: url.absoluteString) + handleResult(.success(lottie)) + } + } catch { + DispatchQueue.main.async { + handleResult(.failure(error)) + } + } + } + task.resume() + } + } + + /// Loads an DotLottie from a data asynchronously. + /// + /// - Parameters: + /// - data: The data(`Foundation.Data`) object to load DotLottie from + /// - filename: The name of the lottie file without the lottie extension. eg. "StarAnimation" + /// - dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional. + /// - handleResult: A closure to be called when the file has loaded. + public static func loadedFrom( + data: Data, + filename: String, + dispatchQueue: DispatchQueue = .dotLottie, + handleResult: @escaping (Result) -> Void) + { + dispatchQueue.async { + do { + let dotLottie = try DotLottieFile(data: data, filename: filename) + DispatchQueue.main.async { + handleResult(.success(dotLottie)) + } + } catch { + DispatchQueue.main.async { + handleResult(.failure(error)) + } + } + } + } + + /// Loads an DotLottie from a data asynchronously. + /// + /// - Parameters: + /// - data: The data(`Foundation.Data`) object to load DotLottie from + /// - filename: The name of the lottie file without the lottie extension. eg. "StarAnimation" + /// - dispatchQueue: A dispatch queue used to load animations. Defaults to `DispatchQueue.global()`. Optional. + public static func loadedFrom( + data: Data, + filename: String, + dispatchQueue: DispatchQueue = .dotLottie) + async throws -> DotLottieFile + { + try await withCheckedThrowingContinuation { continuation in + loadedFrom(data: data, filename: filename, dispatchQueue: dispatchQueue) { result in + continuation.resume(with: result) + } + } + } +} + +extension DispatchQueue { + /// A serial dispatch queue ensures that IO related to loading dot Lottie files don't overlap, + /// which can trigger file loading errors due to concurrent unzipping on a single archive. + public static let dotLottie = DispatchQueue( + label: "com.airbnb.lottie.dot-lottie", + qos: .userInitiated) +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/AnimationKeypath.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/AnimationKeypath.swift new file mode 100644 index 0000000000000000000000000000000000000000..425c1564845966b71783e3c3b708450a42644f63 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/AnimationKeypath.swift @@ -0,0 +1,57 @@ +// +// AnimationKeypath.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +/// `AnimationKeypath` is an object that describes a keypath search for nodes in the +/// animation JSON. `AnimationKeypath` matches views and properties inside of `LottieAnimationView` +/// to their backing `LottieAnimation` model by name. +/// +/// A keypath can be used to set properties on an existing animation, or can be validated +/// with an existing `LottieAnimation`. +/// +/// `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching +/// of objects. Acceptable wildcards are either "*" (star) or "**" (double star). +/// Single star will search a single depth for the next object. +/// Double star will search any depth. +/// +/// Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties +/// +/// EG: +/// @"Layer.Shape Group.Stroke 1.Color" +/// Represents a specific color node on a specific stroke. +/// +/// @"**.Stroke 1.Color" +/// Represents the color node for every Stroke named "Stroke 1" in the animation. +public struct AnimationKeypath: Hashable, ExpressibleByStringLiteral { + + // MARK: Lifecycle + + /// Creates a keypath from a dot-separated string. The string is separated by "." + public init(keypath: String) { + keys = keypath.components(separatedBy: ".") + } + + /// Creates a keypath from a dot-separated string + public init(stringLiteral: String) { + self.init(keypath: stringLiteral) + } + + /// Creates a keypath from a list of strings. + public init(keys: [String]) { + self.keys = keys + } + + // MARK: Public + + /// The dot-separated key values that represent this keypath. + public internal(set) var keys: [String] + + /// The `String` representation of this keypath + public var string: String { + keys.joined(separator: ".") + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/AnyValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/AnyValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..9970b48d1d8592012957ac3ceb7498b47e8e3f37 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/AnyValueProvider.swift @@ -0,0 +1,131 @@ +// +// AnyValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation + +// MARK: - AnyValueProvider + +/// `AnyValueProvider` is a protocol that return animation data for a property at a +/// given time. Every frame a `LottieAnimationView` queries all of its properties and asks +/// if their ValueProvider has an update. If it does the LottieAnimationView will read the +/// property and update that portion of the animation. +/// +/// Value Providers can be used to dynamically set animation properties at run time. +public protocol AnyValueProvider { + + /// The Type of the value provider + var valueType: Any.Type { get } + + /// The type-erased storage for this Value Provider + var typeErasedStorage: AnyValueProviderStorage { get } + + /// Asks the provider if it has an update for the given frame. + func hasUpdate(frame: AnimationFrameTime) -> Bool + +} + +extension AnyValueProvider { + /// Asks the provider to update the container with its value for the frame. + public func value(frame: AnimationFrameTime) -> Any { + typeErasedStorage.value(frame: frame) + } +} + +// MARK: - ValueProvider + +/// A base protocol for strongly-typed Value Providers +protocol ValueProvider: AnyValueProvider { + associatedtype Value: AnyInterpolatable + + /// The strongly-typed storage for this Value Provider + var storage: ValueProviderStorage { get } +} + +extension ValueProvider { + public var typeErasedStorage: AnyValueProviderStorage { + switch storage { + case .closure(let typedClosure): + .closure(typedClosure) + + case .singleValue(let typedValue): + .singleValue(typedValue) + + case .keyframes(let keyframes): + .keyframes( + keyframes.map { keyframe in + keyframe.withValue(keyframe.value as Any) + }, + interpolate: storage.value(frame:)) + } + } +} + +// MARK: - ValueProviderStorage + +/// The underlying storage of a `ValueProvider` +public enum ValueProviderStorage { + /// The value provider stores a single value that is used on all frames + case singleValue(T) + + /// The value provider stores a group of keyframes + /// - The main-thread rendering engine interpolates values in these keyframes + /// using `T`'s `Interpolatable` implementation. + /// - The Core Animation rendering engine constructs a `CAKeyframeAnimation` + /// using these keyframes. The Core Animation render server performs + /// the interpolation, without calling `T`'s `Interpolatable` implementation. + case keyframes([Keyframe]) + + /// The value provider stores a closure that is invoked on every frame + /// - This is only supported by the main-thread rendering engine + case closure((AnimationFrameTime) -> T) + + // MARK: Internal + + func value(frame: AnimationFrameTime) -> T { + switch self { + case .singleValue(let value): + value + + case .closure(let closure): + closure(frame) + + case .keyframes(let keyframes): + KeyframeInterpolator(keyframes: ContiguousArray(keyframes)).storage.value(frame: frame) + } + } +} + +// MARK: - AnyValueProviderStorage + +/// A type-erased representation of `ValueProviderStorage` +public enum AnyValueProviderStorage { + /// The value provider stores a single value that is used on all frames + case singleValue(Any) + + /// The value provider stores a group of keyframes + /// - Since we can't interpolate a type-erased `KeyframeGroup`, + /// the interpolation has to be performed in the `interpolate` closure. + case keyframes([Keyframe], interpolate: (AnimationFrameTime) -> Any) + + /// The value provider stores a closure that is invoked on every frame + case closure((AnimationFrameTime) -> Any) + + // MARK: Internal + + func value(frame: AnimationFrameTime) -> Any { + switch self { + case .singleValue(let value): + value + + case .closure(let closure): + closure(frame) + + case .keyframes(_, let valueForFrame): + valueForFrame(frame) + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..a8680986c97be03db73286dfb589aaa5549fc62a --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift @@ -0,0 +1,98 @@ +// +// ColorValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +// MARK: - ColorValueProvider + +/// A `ValueProvider` that returns a CGColor Value +public final class ColorValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping ColorValueBlock) { + self.block = block + color = LottieColor(r: 0, g: 0, b: 0, a: 1) + keyframes = nil + identity = UUID() + } + + /// Initializes with a single color. + public init(_ color: LottieColor) { + self.color = color + block = nil + keyframes = nil + hasUpdate = true + identity = color + } + + /// Initializes with multiple colors, with timing information + public init(_ keyframes: [Keyframe]) { + self.keyframes = keyframes + color = LottieColor(r: 0, g: 0, b: 0, a: 1) + block = nil + hasUpdate = true + identity = keyframes + } + + // MARK: Public + + /// Returns a LottieColor for a CGColor(Frame Time) + public typealias ColorValueBlock = (CGFloat) -> LottieColor + + /// The color value of the provider. + public var color: LottieColor { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + LottieColor.self + } + + public var storage: ValueProviderStorage { + if let block { + return .closure { frame in + self.hasUpdate = false + return block(frame) + } + } else if let keyframes { + return .keyframes(keyframes) + } else { + hasUpdate = false + return .singleValue(color) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: ColorValueBlock? + private var keyframes: [Keyframe]? + private var identity: AnyHashable +} + +// MARK: Equatable + +extension ColorValueProvider: Equatable { + public static func ==(_ lhs: ColorValueProvider, _ rhs: ColorValueProvider) -> Bool { + lhs.identity == rhs.identity + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..fb22c2b9efdf90ed2211c10d48ce7bfc8951cb15 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift @@ -0,0 +1,83 @@ +// +// DoubleValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +// MARK: - FloatValueProvider + +/// A `ValueProvider` that returns a CGFloat Value +public final class FloatValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping CGFloatValueBlock) { + self.block = block + float = 0 + identity = UUID() + } + + /// Initializes with a single float. + public init(_ float: CGFloat) { + self.float = float + block = nil + hasUpdate = true + identity = float + } + + // MARK: Public + + /// Returns a CGFloat for a CGFloat(Frame Time) + public typealias CGFloatValueBlock = (CGFloat) -> CGFloat + + public var float: CGFloat { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + LottieVector1D.self + } + + public var storage: ValueProviderStorage { + if let block { + return .closure { frame in + self.hasUpdate = false + return LottieVector1D(Double(block(frame))) + } + } else { + hasUpdate = false + return .singleValue(LottieVector1D(Double(float))) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: CGFloatValueBlock? + private var identity: AnyHashable +} + +// MARK: Equatable + +extension FloatValueProvider: Equatable { + public static func ==(_ lhs: FloatValueProvider, _ rhs: FloatValueProvider) -> Bool { + lhs.identity == rhs.identity + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..aa447826708603f07112fbf16b32f06faa5757ef --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift @@ -0,0 +1,140 @@ +// +// GradientValueProvider.swift +// lottie-swift +// +// Created by Enrique Bermúdez on 10/27/19. +// + +import CoreGraphics +import Foundation + +// MARK: - GradientValueProvider + +/// A `ValueProvider` that returns a Gradient Color Value. +public final class GradientValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider. + public init( + block: @escaping ColorsValueBlock, + locations: ColorLocationsBlock? = nil) + { + self.block = block + locationsBlock = locations + colors = [] + self.locations = [] + identity = UUID() + } + + /// Initializes with an array of colors. + public init( + _ colors: [LottieColor], + locations: [Double] = []) + { + self.colors = colors + self.locations = locations + identity = [AnyHashable(colors), AnyHashable(locations)] + updateValueArray() + hasUpdate = true + } + + // MARK: Public + + /// Returns a [LottieColor] for a CGFloat(Frame Time). + public typealias ColorsValueBlock = (CGFloat) -> [LottieColor] + /// Returns a [Double](Color locations) for a CGFloat(Frame Time). + public typealias ColorLocationsBlock = (CGFloat) -> [Double] + + /// The colors values of the provider. + public var colors: [LottieColor] { + didSet { + updateValueArray() + hasUpdate = true + } + } + + /// The color location values of the provider. + public var locations: [Double] { + didSet { + updateValueArray() + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + [Double].self + } + + public var storage: ValueProviderStorage<[Double]> { + if let block { + .closure { [self] frame in + hasUpdate = false + + let newColors = block(frame) + let newLocations = locationsBlock?(frame) ?? [] + value = value(from: newColors, locations: newLocations) + + return value + } + } else { + .singleValue(value) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil || locationsBlock != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: ColorsValueBlock? + private var locationsBlock: ColorLocationsBlock? + private var value: [Double] = [] + + private let identity: AnyHashable + + private func value(from colors: [LottieColor], locations: [Double]) -> [Double] { + var colorValues = [Double]() + var alphaValues = [Double]() + var shouldAddAlphaValues = false + + for i in 0.. i + ? locations[i] + : (Double(i) / Double(colors.count - 1)) + + colorValues.append(location) + colorValues.append(colors[i].r) + colorValues.append(colors[i].g) + colorValues.append(colors[i].b) + + alphaValues.append(location) + alphaValues.append(colors[i].a) + } + + return colorValues + (shouldAddAlphaValues ? alphaValues : []) + } + + private func updateValueArray() { + value = value(from: colors, locations: locations) + } + +} + +// MARK: Equatable + +extension GradientValueProvider: Equatable { + public static func ==(_ lhs: GradientValueProvider, _ rhs: GradientValueProvider) -> Bool { + lhs.identity == rhs.identity + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/PointValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/PointValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..8b9703bfe85913ea84cc7a7a202bee5263f4f9ee --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/PointValueProvider.swift @@ -0,0 +1,83 @@ +// +// PointValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +// MARK: - PointValueProvider + +/// A `ValueProvider` that returns a CGPoint Value +public final class PointValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping PointValueBlock) { + self.block = block + point = .zero + identity = UUID() + } + + /// Initializes with a single point. + public init(_ point: CGPoint) { + self.point = point + block = nil + hasUpdate = true + identity = [point.x, point.y] + } + + // MARK: Public + + /// Returns a CGPoint for a CGFloat(Frame Time) + public typealias PointValueBlock = (CGFloat) -> CGPoint + + public var point: CGPoint { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + LottieVector3D.self + } + + public var storage: ValueProviderStorage { + if let block { + return .closure { frame in + self.hasUpdate = false + return block(frame).vector3dValue + } + } else { + hasUpdate = false + return .singleValue(point.vector3dValue) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: PointValueBlock? + private let identity: AnyHashable +} + +// MARK: Equatable + +extension PointValueProvider: Equatable { + public static func ==(_ lhs: PointValueProvider, _ rhs: PointValueProvider) -> Bool { + lhs.identity == rhs.identity + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..aa5cac9498bc86e2e8ab0b6d600fec9dce416c0c --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift @@ -0,0 +1,83 @@ +// +// SizeValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import CoreGraphics +import Foundation + +// MARK: - SizeValueProvider + +/// A `ValueProvider` that returns a CGSize Value +public final class SizeValueProvider: ValueProvider { + + // MARK: Lifecycle + + /// Initializes with a block provider + public init(block: @escaping SizeValueBlock) { + self.block = block + size = .zero + identity = UUID() + } + + /// Initializes with a single size. + public init(_ size: CGSize) { + self.size = size + block = nil + hasUpdate = true + identity = [size.width, size.height] + } + + // MARK: Public + + /// Returns a CGSize for a CGFloat(Frame Time) + public typealias SizeValueBlock = (CGFloat) -> CGSize + + public var size: CGSize { + didSet { + hasUpdate = true + } + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + LottieVector3D.self + } + + public var storage: ValueProviderStorage { + if let block { + return .closure { frame in + self.hasUpdate = false + return block(frame).vector3dValue + } + } else { + hasUpdate = false + return .singleValue(size.vector3dValue) + } + } + + public func hasUpdate(frame _: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + // MARK: Private + + private var hasUpdate = true + + private var block: SizeValueBlock? + private let identity: AnyHashable +} + +// MARK: Equatable + +extension SizeValueProvider: Equatable { + public static func ==(_ lhs: SizeValueProvider, _ rhs: SizeValueProvider) -> Bool { + lhs.identity == rhs.identity + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/FontProvider/AnimationFontProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/FontProvider/AnimationFontProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..d9e1e8ea394ceb921b4656f4cb5ccfefe010c16f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/FontProvider/AnimationFontProvider.swift @@ -0,0 +1,41 @@ +// +// AnimationFontProvider.swift +// Lottie +// +// Created by Brandon Withrow on 8/5/20. +// Copyright © 2020 YurtvilleProds. All rights reserved. +// + +import CoreText + +// MARK: - AnimationFontProvider + +/// Font provider is a protocol that is used to supply fonts to `LottieAnimationView`. +/// +public protocol AnimationFontProvider { + func fontFor(family: String, size: CGFloat) -> CTFont? +} + +// MARK: - DefaultFontProvider + +/// Default Font provider. +public final class DefaultFontProvider: AnimationFontProvider { + + // MARK: Lifecycle + + public init() { } + + // MARK: Public + + public func fontFor(family: String, size: CGFloat) -> CTFont? { + CTFontCreateWithName(family as CFString, size, nil) + } +} + +// MARK: Equatable + +extension DefaultFontProvider: Equatable { + public static func ==(_: DefaultFontProvider, _: DefaultFontProvider) -> Bool { + true + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/ImageProvider/AnimationImageProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/ImageProvider/AnimationImageProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..7f7477003ebded23c22c6a07cf7b7bf9fa515e44 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/ImageProvider/AnimationImageProvider.swift @@ -0,0 +1,43 @@ +// +// LottieImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import QuartzCore + +// MARK: - AnimationImageProvider + +/// Image provider is a protocol that is used to supply images to `LottieAnimationView`. +/// +/// Some animations require a reference to an image. The image provider loads and +/// provides those images to the `LottieAnimationView`. Lottie includes a couple of +/// prebuilt Image Providers that supply images from a Bundle, or from a FilePath. +/// +/// Additionally custom Image Providers can be made to load images from a URL, +/// or to Cache images. +public protocol AnimationImageProvider { + + /// Whether or not the resulting image of this image provider can be cached by Lottie. Defaults to true. + /// If true, Lottie may internally cache the result of `imageForAsset` + var cacheEligible: Bool { get } + + /// The image to display for the given `ImageAsset` defined in the `LottieAnimation` JSON file. + func imageForAsset(asset: ImageAsset) -> CGImage? + + /// Specifies how the layer's contents are positioned or scaled within its bounds for a given asset. + /// Defaults to `.resize`, which stretches the image to fill the layer. + func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity +} + +extension AnimationImageProvider { + public var cacheEligible: Bool { + true + } + + /// The default value is `.resize`, similar to that of `CALayer`. + public func contentsGravity(for _: ImageAsset) -> CALayerContentsGravity { + .resize + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Keyframes/Interpolatable.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Keyframes/Interpolatable.swift new file mode 100644 index 0000000000000000000000000000000000000000..a0f09b60be2f830f703ca044a62e03ab87b8c479 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Keyframes/Interpolatable.swift @@ -0,0 +1,279 @@ +// Created by Cal Stephens on 1/24/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import CoreGraphics + +// MARK: - Interpolatable + +/// A type that can be interpolated between two values +public protocol Interpolatable: AnyInterpolatable { + /// Interpolates the `self` to the given number by `amount`. + /// - Parameter to: The number to interpolate to. + /// - Parameter amount: The amount to interpolate, + /// relative to 0.0 (self) and 1.0 (to). + /// `amount` can be greater than one and less than zero, + /// and interpolation should not be clamped. + /// + /// ``` + /// let number = 5 + /// let interpolated = number.interpolateTo(10, amount: 0.5) + /// print(interpolated) // 7.5 + /// ``` + /// + /// ``` + /// let number = 5 + /// let interpolated = number.interpolateTo(10, amount: 1.5) + /// print(interpolated) // 12.5 + /// ``` + func interpolate(to: Self, amount: CGFloat) -> Self +} + +// MARK: - SpatialInterpolatable + +/// A type that can be interpolated between two values, +/// additionally using optional `spatialOutTangent` and `spatialInTangent` values. +/// - If your implementation doesn't use the `spatialOutTangent` and `spatialInTangent` +/// parameters, prefer implementing the simpler `Interpolatable` protocol. +public protocol SpatialInterpolatable: AnyInterpolatable { + /// Interpolates the `self` to the given number by `amount`. + /// - Parameter to: The number to interpolate to. + /// - Parameter amount: The amount to interpolate, + /// relative to 0.0 (self) and 1.0 (to). + /// `amount` can be greater than one and less than zero, + /// and interpolation should not be clamped. + func interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Self +} + +// MARK: - AnyInterpolatable + +/// The base protocol that is implemented by both `Interpolatable` and `SpatialInterpolatable` +/// Types should not directly implement this protocol. +public protocol AnyInterpolatable { + /// Interpolates by calling either `Interpolatable.interpolate` + /// or `SpatialInterpolatable.interpolate`. + /// Should not be implemented or called by consumers. + func _interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Self +} + +extension Interpolatable { + public func _interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent _: CGPoint?, + spatialInTangent _: CGPoint?) + -> Self + { + interpolate(to: to, amount: amount) + } +} + +extension SpatialInterpolatable { + /// Helper that interpolates this `SpatialInterpolatable` + /// with `nil` spatial in/out tangents + public func interpolate(to: Self, amount: CGFloat) -> Self { + interpolate( + to: to, + amount: amount, + spatialOutTangent: nil, + spatialInTangent: nil) + } + + public func _interpolate( + to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> Self + { + interpolate( + to: to, + amount: amount, + spatialOutTangent: spatialOutTangent, + spatialInTangent: spatialInTangent) + } +} + +// MARK: - Double + Interpolatable + +extension Double: Interpolatable { } + +// MARK: - CGFloat + Interpolatable + +extension CGFloat: Interpolatable { } + +// MARK: - Float + Interpolatable + +extension Float: Interpolatable { } + +extension Interpolatable where Self: BinaryFloatingPoint { + public func interpolate(to: Self, amount: CGFloat) -> Self { + self + ((to - self) * Self(amount)) + } +} + +// MARK: - CGRect + Interpolatable + +extension CGRect: Interpolatable { + public func interpolate(to: CGRect, amount: CGFloat) -> CGRect { + CGRect( + x: origin.x.interpolate(to: to.origin.x, amount: amount), + y: origin.y.interpolate(to: to.origin.y, amount: amount), + width: width.interpolate(to: to.width, amount: amount), + height: height.interpolate(to: to.height, amount: amount)) + } +} + +// MARK: - CGSize + Interpolatable + +extension CGSize: Interpolatable { + public func interpolate(to: CGSize, amount: CGFloat) -> CGSize { + CGSize( + width: width.interpolate(to: to.width, amount: amount), + height: height.interpolate(to: to.height, amount: amount)) + } +} + +// MARK: - CGPoint + SpatialInterpolatable + +extension CGPoint: SpatialInterpolatable { + public func interpolate( + to: CGPoint, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> CGPoint + { + guard + let outTan = spatialOutTangent, + let inTan = spatialInTangent + else { + return CGPoint( + x: x.interpolate(to: to.x, amount: amount), + y: y.interpolate(to: to.y, amount: amount)) + } + + let cp1 = self + outTan + let cp2 = to + inTan + return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount) + } +} + +// MARK: - LottieColor + Interpolatable + +extension LottieColor: Interpolatable { + public func interpolate(to: LottieColor, amount: CGFloat) -> LottieColor { + LottieColor( + r: r.interpolate(to: to.r, amount: amount), + g: g.interpolate(to: to.g, amount: amount), + b: b.interpolate(to: to.b, amount: amount), + a: a.interpolate(to: to.a, amount: amount)) + } +} + +// MARK: - LottieVector1D + Interpolatable + +extension LottieVector1D: Interpolatable { + public func interpolate(to: LottieVector1D, amount: CGFloat) -> LottieVector1D { + value.interpolate(to: to.value, amount: amount).vectorValue + } +} + +// MARK: - LottieVector2D + SpatialInterpolatable + +extension LottieVector2D: SpatialInterpolatable { + public func interpolate( + to: LottieVector2D, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> LottieVector2D + { + pointValue.interpolate( + to: to.pointValue, + amount: amount, + spatialOutTangent: spatialOutTangent, + spatialInTangent: spatialInTangent) + .vector2dValue + } +} + +// MARK: - LottieVector3D + SpatialInterpolatable + +extension LottieVector3D: SpatialInterpolatable { + public func interpolate( + to: LottieVector3D, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) + -> LottieVector3D + { + if spatialInTangent != nil || spatialOutTangent != nil { + // TODO Support third dimension spatial interpolation + let point = pointValue.interpolate( + to: to.pointValue, + amount: amount, + spatialOutTangent: spatialOutTangent, + spatialInTangent: spatialInTangent) + + return LottieVector3D( + x: point.x, + y: point.y, + z: CGFloat(z.interpolate(to: to.z, amount: amount))) + } + + return LottieVector3D( + x: x.interpolate(to: to.x, amount: amount), + y: y.interpolate(to: to.y, amount: amount), + z: z.interpolate(to: to.z, amount: amount)) + } +} + +// MARK: - Array + Interpolatable, AnyInterpolatable + +extension Array: Interpolatable, AnyInterpolatable where Element: Interpolatable { + public func interpolate(to: [Element], amount: CGFloat) -> [Element] { + LottieLogger.shared.assert( + count == to.count, + "When interpolating Arrays, both array sound have the same element count.") + + return zip(self, to).map { lhs, rhs in + lhs.interpolate(to: rhs, amount: amount) + } + } +} + +// MARK: - Optional + Interpolatable, AnyInterpolatable + +extension Optional: Interpolatable, AnyInterpolatable where Wrapped: Interpolatable { + public func interpolate(to: Wrapped?, amount: CGFloat) -> Wrapped? { + guard let self, let to else { return nil } + return self.interpolate(to: to, amount: amount) + } +} + +// MARK: - Hold + +/// An `Interpolatable` container that animates using "hold" keyframes. +/// The keyframes do not animate, and instead always display the value from the most recent keyframe. +/// This is necessary when passing non-interpolatable values to a method that requires an `Interpolatable` conformance. +struct Hold: Interpolatable { + let value: T + + func interpolate(to: Hold, amount: CGFloat) -> Hold { + if amount < 1 { + self + } else { + to + } + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Keyframes/Keyframe.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Keyframes/Keyframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..626260cd5d235c08379a8eb67e60342deb02ac89 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Keyframes/Keyframe.swift @@ -0,0 +1,98 @@ +// Created by Cal Stephens on 1/24/22. +// Copyright © 2022 Airbnb Inc. All rights reserved. + +import CoreFoundation + +// MARK: - Keyframe + +/// A keyframe with a single value, and timing information +/// about when the value should be displayed and how it +/// should be interpolated. +public final class Keyframe { + + // MARK: Lifecycle + + /// Initialize a value-only keyframe with no time data. + public init( + _ value: T, + spatialInTangent: LottieVector3D? = nil, + spatialOutTangent: LottieVector3D? = nil) + { + self.value = value + time = 0 + isHold = true + inTangent = nil + outTangent = nil + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + /// Initialize a keyframe + public init( + value: T, + time: AnimationFrameTime, + isHold: Bool = false, + inTangent: LottieVector2D? = nil, + outTangent: LottieVector2D? = nil, + spatialInTangent: LottieVector3D? = nil, + spatialOutTangent: LottieVector3D? = nil) + { + self.value = value + self.time = time + self.isHold = isHold + self.outTangent = outTangent + self.inTangent = inTangent + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + // MARK: Public + + /// The value of the keyframe + public let value: T + /// The time in frames of the keyframe. + public let time: AnimationFrameTime + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + public let isHold: Bool + /// The in tangent for the time interpolation curve. + public let inTangent: LottieVector2D? + /// The out tangent for the time interpolation curve. + public let outTangent: LottieVector2D? + + /// The spatial in tangent of the vector. + public let spatialInTangent: LottieVector3D? + /// The spatial out tangent of the vector. + public let spatialOutTangent: LottieVector3D? +} + +// MARK: Equatable + +extension Keyframe: Equatable where T: Equatable { + public static func == (lhs: Keyframe, rhs: Keyframe) -> Bool { + lhs.value == rhs.value + && lhs.time == rhs.time + && lhs.isHold == rhs.isHold + && lhs.inTangent == rhs.inTangent + && lhs.outTangent == rhs.outTangent + && lhs.spatialInTangent == rhs.spatialOutTangent + && lhs.spatialOutTangent == rhs.spatialOutTangent + } +} + +// MARK: Hashable + +extension Keyframe: Hashable where T: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + hasher.combine(time) + hasher.combine(isHold) + hasher.combine(inTangent) + hasher.combine(outTangent) + hasher.combine(spatialInTangent) + hasher.combine(spatialOutTangent) + } +} + +// MARK: Sendable + +extension Keyframe: Sendable where T: Sendable { } diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Logging/LottieLogger.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Logging/LottieLogger.swift new file mode 100644 index 0000000000000000000000000000000000000000..cddde2dd76375e3e4dce8015dcfd68cbead77680 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Logging/LottieLogger.swift @@ -0,0 +1,137 @@ +// Created by eric_horacek on 12/9/20. +// Copyright © 2020 Airbnb Inc. All rights reserved. + +// MARK: - LottieLogger + +/// A shared logger that allows consumers to intercept Lottie assertions and warning messages to pipe +/// into their own logging systems. +public final class LottieLogger { + + // MARK: Lifecycle + + public init( + assert: @escaping Assert = { condition, message, file, line in + // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`, + // the call will unexpectedly not respect the -O flag and will crash in release + // https://github.com/apple/swift/issues/60249 + Swift.assert(condition(), message(), file: file, line: line) + }, + assertionFailure: @escaping AssertionFailure = { message, file, line in + // If we default to `Swift.assertionFailure` directly with + // `assertionFailure: AssertionFailure = Swift.assertionFailure`, + // the call will unexpectedly not respect the -O flag and will crash in release + // https://github.com/apple/swift/issues/60249 + Swift.assertionFailure(message(), file: file, line: line) + }, + warn: @escaping Warn = { message, _, _ in + #if DEBUG + // swiftlint:disable:next no_direct_standard_out_logs + print(message()) + #endif + }, + info: @escaping Info = { message in + #if DEBUG + // swiftlint:disable:next no_direct_standard_out_logs + print(message()) + #endif + }) + { + _assert = assert + _assertionFailure = assertionFailure + _warn = warn + _info = info + } + + // MARK: Public + + /// Logs that an assertion occurred. + public typealias Assert = ( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Logs that an assertion failure occurred. + public typealias AssertionFailure = ( + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Logs a warning message. + public typealias Warn = ( + _ message: @autoclosure () -> String, + _ fileID: StaticString, + _ line: UInt) + -> Void + + /// Prints a purely informational message. + public typealias Info = (_ message: @autoclosure () -> String) -> Void + + /// The shared instance used to log Lottie assertions and warnings. + /// + /// Set this to a new logger instance to intercept assertions and warnings logged by Lottie. + public static var shared = LottieLogger() + + /// Logs that an assertion occurred. + public func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _assert(condition(), message(), fileID, line) + } + + /// Logs that an assertion failure occurred. + public func assertionFailure( + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _assertionFailure(message(), fileID, line) + } + + /// Logs a warning message. + public func warn( + _ message: @autoclosure () -> String = String(), + fileID: StaticString = #fileID, + line: UInt = #line) + { + _warn(message(), fileID, line) + } + + /// Logs a purely informational message. + public func info(_ message: @autoclosure () -> String = String()) { + _info(message()) + } + + // MARK: Private + + private let _assert: Assert + private let _assertionFailure: AssertionFailure + private let _warn: Warn + private let _info: Info + +} + +// MARK: - LottieLogger + printToConsole + +extension LottieLogger { + /// A `LottieLogger` instance that always prints to the console (by calling `print`) + /// instead of calling `assert` / `assertionFailure`, which halt execution in debug builds. + public static var printToConsole: LottieLogger { + LottieLogger( + assert: { condition, message, _, _ in + if !condition() { + // swiftlint:disable:next no_direct_standard_out_logs + print(message()) + } + }, + assertionFailure: { message, _, _ in + // swiftlint:disable:next no_direct_standard_out_logs + print(message()) + }) + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/AnimationTime.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/AnimationTime.swift new file mode 100644 index 0000000000000000000000000000000000000000..2c33e2b452894a311d16776d60f8160359622597 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/AnimationTime.swift @@ -0,0 +1,15 @@ +// +// AnimationTime.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import CoreGraphics +import Foundation + +/// Defines animation time in Frames (Seconds * Framerate). +public typealias AnimationFrameTime = CGFloat + +/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) +public typealias AnimationProgressTime = CGFloat diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/LottieColor.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/LottieColor.swift new file mode 100644 index 0000000000000000000000000000000000000000..ffae202392d3e2830484bae2cdbc5640a9314c89 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/LottieColor.swift @@ -0,0 +1,43 @@ +// +// LottieColor.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +// MARK: - ColorFormatDenominator + +public enum ColorFormatDenominator: Hashable { + case One + case OneHundred + case TwoFiftyFive + + var value: Double { + switch self { + case .One: + 1.0 + case .OneHundred: + 100.0 + case .TwoFiftyFive: + 255.0 + } + } +} + +// MARK: - LottieColor + +public struct LottieColor: Hashable { + + public var r: Double + public var g: Double + public var b: Double + public var a: Double + + public init(r: Double, g: Double, b: Double, a: Double, denominator: ColorFormatDenominator = .One) { + self.r = r / denominator.value + self.g = g / denominator.value + self.b = b / denominator.value + self.a = a / denominator.value + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/Vectors.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/Vectors.swift new file mode 100644 index 0000000000000000000000000000000000000000..af5d956630b8d14424005d2ad3582a23979176f5 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/Primitives/Vectors.swift @@ -0,0 +1,36 @@ +// +// Vectors.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +// MARK: - LottieVector1D + +public struct LottieVector1D: Hashable, Sendable { + + public init(_ value: Double) { + self.value = value + } + + public let value: Double + +} + +// MARK: - LottieVector3D + +/// A three dimensional vector. +/// These vectors are encoded and decoded from [Double] +public struct LottieVector3D: Hashable, Sendable { + + public let x: Double + public let y: Double + public let z: Double + + public init(x: Double, y: Double, z: Double) { + self.x = x + self.y = y + self.z = z + } + +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/TextProvider/AnimationTextProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/TextProvider/AnimationTextProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..52ce0941ec0c51847ac17a6f3af7b1fc1b97c48b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/TextProvider/AnimationTextProvider.swift @@ -0,0 +1,124 @@ +// +// AnimationImageProvider.swift +// Lottie_iOS +// +// Created by Alexandr Goncharov on 07/06/2019. +// + +// MARK: - AnimationKeypathTextProvider + +/// Protocol for providing dynamic text to for a Lottie animation. +public protocol AnimationKeypathTextProvider: AnyObject { + /// The text to display for the given `AnimationKeypath`. + /// If `nil` is returned, continues using the existing default text value. + func text(for keypath: AnimationKeypath, sourceText: String) -> String? +} + +// MARK: - AnimationKeypathTextProvider + +/// Legacy protocol for providing dynamic text for a Lottie animation. +/// Instead prefer conforming to `AnimationKeypathTextProvider`. +@available(*, deprecated, message: """ + `AnimationKeypathTextProvider` has been deprecated and renamed to `LegacyAnimationTextProvider`. \ + Instead, conform to `AnimationKeypathTextProvider` instead or conform to `LegacyAnimationTextProvider` explicitly. + """) +public typealias AnimationTextProvider = LegacyAnimationTextProvider + +// MARK: - LegacyAnimationTextProvider + +/// Legacy protocol for providing dynamic text for a Lottie animation. +/// Instead prefer conforming to `AnimationKeypathTextProvider`. +public protocol LegacyAnimationTextProvider: AnimationKeypathTextProvider { + /// Legacy method to look up the text to display for the given keypath. + /// Instead, prefer implementing `AnimationKeypathTextProvider.` + /// The behavior of this method depends on the current rendering engine: + /// - The Core Animation rendering engine always calls this method + /// with the full keypath (e.g. `MY_LAYER.text_value`). + /// - The Main Thread rendering engine always calls this method + /// with the final component of the key path (e.g. just `text_value`). + func textFor(keypathName: String, sourceText: String) -> String +} + +extension LegacyAnimationTextProvider { + public func text(for _: AnimationKeypath, sourceText _: String) -> String? { + nil + } +} + +// MARK: - DictionaryTextProvider + +/// Text provider that simply map values from dictionary. +/// - The dictionary keys can either be the full layer keypath string (e.g. `MY_LAYER.text_value`) +/// or simply the final path component of the keypath (e.g. `text_value`). +public final class DictionaryTextProvider: AnimationKeypathTextProvider, LegacyAnimationTextProvider { + + // MARK: Lifecycle + + public init(_ values: [String: String]) { + self.values = values + } + + // MARK: Public + + public func text(for keypath: AnimationKeypath, sourceText: String) -> String? { + if let valueForFullKeypath = values[keypath.fullPath] { + valueForFullKeypath + } + + else if + let lastKeypathComponent = keypath.keys.last, + let valueForLastComponent = values[lastKeypathComponent] + { + valueForLastComponent + } + + else { + sourceText + } + } + + /// Never called directly by Lottie, but we continue to implement this conformance for backwards compatibility. + public func textFor(keypathName: String, sourceText: String) -> String { + values[keypathName] ?? sourceText + } + + // MARK: Internal + + let values: [String: String] +} + +// MARK: Equatable + +extension DictionaryTextProvider: Equatable { + public static func ==(_ lhs: DictionaryTextProvider, _ rhs: DictionaryTextProvider) -> Bool { + lhs.values == rhs.values + } +} + +// MARK: - DefaultTextProvider + +/// Default text provider. Uses text in the animation file +public final class DefaultTextProvider: AnimationKeypathTextProvider, LegacyAnimationTextProvider { + + // MARK: Lifecycle + + public init() { } + + // MARK: Public + + public func textFor(keypathName _: String, sourceText: String) -> String { + sourceText + } + + public func text(for _: AnimationKeypath, sourceText: String) -> String { + sourceText + } +} + +// MARK: Equatable + +extension DefaultTextProvider: Equatable { + public static func ==(_: DefaultTextProvider, _: DefaultTextProvider) -> Bool { + true + } +} diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/AnimationSubview.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/AnimationSubview.swift new file mode 100644 index 0000000000000000000000000000000000000000..93264ef259d7f3fefb2bf609e60d8c5ecb2dbb70 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/AnimationSubview.swift @@ -0,0 +1,19 @@ +// +// AnimationSubview.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +#if canImport(UIKit) +import UIKit + +/// A view that can be added to a keypath of an AnimationView +public final class AnimationSubview: UIView { + + var viewLayer: CALayer? { + layer + } + +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/BundleImageProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/BundleImageProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..3317e0b1964a0ad1395ecbb44a1bf386bba1e1d1 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/BundleImageProvider.swift @@ -0,0 +1,98 @@ +// +// LottieBundleImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +#if canImport(UIKit) +import UIKit + +/// An `AnimationImageProvider` that provides images by name from a specific bundle. +/// The BundleImageProvider is initialized with a bundle and an optional searchPath. +public class BundleImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a bundle and an optional subpath. + /// + /// Provides images for an animation from a bundle. Additionally the provider can + /// search a specific subpath for the images. + /// + /// - Parameter bundle: The bundle containing images for the provider. + /// - Parameter searchPath: The subpath is a path within the bundle to search for image assets. + /// - Parameter contentsGravity: The contents gravity to use when rendering the image. + /// + public init(bundle: Bundle, searchPath: String?, contentsGravity: CALayerContentsGravity = .resize) { + self.bundle = bundle + self.searchPath = searchPath + self.contentsGravity = contentsGravity + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + if let base64Image = asset.base64Image { + return base64Image + } + + let imagePath: String? + /// Try to find the image in the bundle. + if let searchPath { + /// Search in the provided search path for the image + var directoryPath = URL(fileURLWithPath: searchPath) + directoryPath.appendPathComponent(asset.directory) + + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: directoryPath.path) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: searchPath) { + /// Try finding the image in the search path. + imagePath = path + } else { + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } else { + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: asset.directory) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else { + /// First search for the image in bundle. + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } + + if imagePath == nil { + guard let image = UIImage(named: asset.name, in: bundle, compatibleWith: nil) else { + LottieLogger.shared.warn("Could not find image \"\(asset.name)\" in bundle") + return nil + } + return image.cgImage + } + + guard let foundPath = imagePath, let image = UIImage(contentsOfFile: foundPath) else { + /// No image found. + LottieLogger.shared.warn("Could not find image \"\(asset.name)\" in bundle") + return nil + } + return image.cgImage + } + + public func contentsGravity(for _: ImageAsset) -> CALayerContentsGravity { + contentsGravity + } + + // MARK: Internal + + let bundle: Bundle + let searchPath: String? + let contentsGravity: CALayerContentsGravity +} + +extension BundleImageProvider: Equatable { + public static func ==(_ lhs: BundleImageProvider, _ rhs: BundleImageProvider) -> Bool { + lhs.bundle == rhs.bundle + && lhs.searchPath == rhs.searchPath + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift new file mode 100644 index 0000000000000000000000000000000000000000..facb4e647b17cfcc337d0bd380ebb194b4593991 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift @@ -0,0 +1,33 @@ +// +// CompatibleAnimationKeypath.swift +// Lottie_iOS +// +// Created by Tyler Hedrick on 3/6/19. +// + +import Foundation +#if canImport(UIKit) + +/// An Objective-C compatible wrapper around Lottie's AnimationKeypath +@objc +public final class CompatibleAnimationKeypath: NSObject { + + // MARK: Lifecycle + + /// Creates a keypath from a dot separated string. The string is separated by "." + @objc + public init(keypath: String) { + animationKeypath = AnimationKeypath(keypath: keypath) + } + + /// Creates a keypath from a list of strings. + @objc + public init(keys: [String]) { + animationKeypath = AnimationKeypath(keys: keys) + } + + // MARK: Public + + public let animationKeypath: AnimationKeypath +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/Compatibility/CompatibleAnimationView.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/Compatibility/CompatibleAnimationView.swift new file mode 100644 index 0000000000000000000000000000000000000000..c78846ffe5338f40a80c418c1e8b275da703b8fe --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/Compatibility/CompatibleAnimationView.swift @@ -0,0 +1,543 @@ +// +// CompatibleAnimationView.swift +// Lottie_iOS +// +// Created by Tyler Hedrick on 3/6/19. +// + +import Foundation +#if canImport(UIKit) +import UIKit + +/// An Objective-C compatible wrapper around Lottie's Animation class. +/// Use in tandem with CompatibleAnimationView when using Lottie in Objective-C +@objc +public final class CompatibleAnimation: NSObject { + + // MARK: Lifecycle + + @objc + public init( + name: String, + subdirectory: String? = nil, + bundle: Bundle = Bundle.main) + { + self.name = name + self.subdirectory = subdirectory + self.bundle = bundle + super.init() + } + + // MARK: Internal + + var animation: LottieAnimation? { + LottieAnimation.named(name, bundle: bundle, subdirectory: subdirectory) + } + + @objc + static func named(_ name: String) -> CompatibleAnimation { + CompatibleAnimation(name: name) + } + + // MARK: Private + + private let name: String + private let subdirectory: String? + private let bundle: Bundle +} + +/// An Objective-C compatible wrapper around Lottie's RenderingEngineOption enum. Pass in an option +/// to the CompatibleAnimationView initializers to configure the rendering engine for the view. +@objc +public enum CompatibleRenderingEngineOption: Int { + /// Uses the rendering engine specified in LottieConfiguration.shared. + case shared + + /// Uses the library default rendering engine, coreAnimation. + case defaultEngine + + /// Optimizes rendering performance by using the Core Animation rendering engine for animations it + /// can render while falling back to the main thread renderer for all other animations. + case automatic + + /// Only renders animations using the main thread rendering engine. + case mainThread + + /// Only renders animations using the Core Animation rendering engine. Those animations that use + /// features not yet supported on this renderer will not be rendered. + case coreAnimation + + // MARK: Public + + /// Converts a CompatibleRenderingEngineOption to the corresponding LottieConfiguration for + /// internal rendering engine configuration. + public static func generateLottieConfiguration( + _ configuration: CompatibleRenderingEngineOption) + -> LottieConfiguration + { + switch configuration { + case .shared: + LottieConfiguration.shared + case .defaultEngine: + LottieConfiguration(renderingEngine: .coreAnimation) + case .automatic: + LottieConfiguration(renderingEngine: .automatic) + case .mainThread: + LottieConfiguration(renderingEngine: .mainThread) + case .coreAnimation: + LottieConfiguration(renderingEngine: .coreAnimation) + } + } +} + +/// An Objective-C compatible version of `LottieBackgroundBehavior`. +@objc +public enum CompatibleBackgroundBehavior: Int { + /// Stop the animation and reset it to the beginning of its current play time. The completion block is called. + case stop + + /// Pause the animation in its current state. The completion block is called. + case pause + + /// Pause the animation and restart it when the application moves to the foreground. + /// The completion block is stored and called when the animation completes. + /// - This is the default when using the Main Thread rendering engine. + case pauseAndRestore + + /// Stops the animation and sets it to the end of its current play time. The completion block is called. + case forceFinish + + /// The animation continues playing in the background. + /// - This is the default when using the Core Animation rendering engine. + /// Playing an animation using the Core Animation engine doesn't come with any CPU overhead, + /// so using `.continuePlaying` avoids the need to stop and then resume the animation + /// (which does come with some CPU overhead). + /// - This mode should not be used with the Main Thread rendering engine. + case continuePlaying +} + +/// An Objective-C compatible wrapper around Lottie's LottieAnimationView. +@objc +public final class CompatibleAnimationView: UIView { + + // MARK: Lifecycle + + /// Initializes a compatible AnimationView with a given compatible animation. Defaults to using + /// the rendering engine specified in LottieConfiguration.shared. + @objc + public convenience init(compatibleAnimation: CompatibleAnimation) { + self.init(compatibleAnimation: compatibleAnimation, compatibleRenderingEngineOption: .shared) + } + + /// Initializes a compatible AnimationView with a given compatible animation and rendering engine + /// configuration. + @objc + public init( + compatibleAnimation: CompatibleAnimation, + compatibleRenderingEngineOption: CompatibleRenderingEngineOption) + { + animationView = LottieAnimationView( + animation: compatibleAnimation.animation, + configuration: CompatibleRenderingEngineOption.generateLottieConfiguration(compatibleRenderingEngineOption)) + self.compatibleAnimation = compatibleAnimation + super.init(frame: .zero) + commonInit() + } + + /// Initializes a compatible AnimationView with the resources asynchronously loaded from a given + /// URL. Defaults to using the rendering engine specified in LottieConfiguration.shared. + @objc + public convenience init(url: URL) { + self.init(url: url, compatibleRenderingEngineOption: .shared) + } + + /// Initializes a compatible AnimationView with the resources asynchronously loaded from a given + /// URL using the given rendering engine configuration. + @objc + public init(url: URL, compatibleRenderingEngineOption: CompatibleRenderingEngineOption) { + animationView = LottieAnimationView( + url: url, + closure: { _ in }, + configuration: CompatibleRenderingEngineOption.generateLottieConfiguration(compatibleRenderingEngineOption)) + super.init(frame: .zero) + commonInit() + } + + /// Initializes a compatible AnimationView from a given Data object specifying the Lottie + /// animation. Defaults to using the rendering engine specified in LottieConfiguration.shared. + @objc + public convenience init(data: Data) { + self.init(data: data, compatibleRenderingEngineOption: .shared) + } + + /// Initializes a compatible AnimationView from a given Data object specifying the Lottie + /// animation using the given rendering engine configuration. + @objc + public init(data: Data, compatibleRenderingEngineOption: CompatibleRenderingEngineOption) { + if let animation = try? LottieAnimation.from(data: data) { + animationView = LottieAnimationView( + animation: animation, + configuration: CompatibleRenderingEngineOption.generateLottieConfiguration(compatibleRenderingEngineOption)) + } else { + animationView = LottieAnimationView( + configuration: CompatibleRenderingEngineOption.generateLottieConfiguration(compatibleRenderingEngineOption)) + } + super.init(frame: .zero) + commonInit() + } + + @objc + public override init(frame: CGRect) { + animationView = LottieAnimationView() + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + animationView = LottieAnimationView() + super.init(coder: coder) + commonInit() + } + + // MARK: Public + + @objc public var compatibleAnimation: CompatibleAnimation? { + didSet { + animationView.animation = compatibleAnimation?.animation + } + } + + @objc public var loopAnimationCount: CGFloat = 0 { + didSet { + animationView.loopMode = loopAnimationCount == -1 ? .loop : .repeat(Float(loopAnimationCount)) + } + } + + @objc public var compatibleDictionaryTextProvider: CompatibleDictionaryTextProvider? { + didSet { + animationView.textProvider = compatibleDictionaryTextProvider?.textProvider ?? DefaultTextProvider() + } + } + + @objc + public override var contentMode: UIView.ContentMode { + set { animationView.contentMode = newValue } + get { animationView.contentMode } + } + + @objc + public var shouldRasterizeWhenIdle: Bool { + set { animationView.shouldRasterizeWhenIdle = newValue } + get { animationView.shouldRasterizeWhenIdle } + } + + @objc + public var currentProgress: CGFloat { + set { animationView.currentProgress = newValue } + get { animationView.currentProgress } + } + + @objc + public var duration: CGFloat { + animationView.animation?.duration ?? 0.0 + } + + @objc + public var currentTime: TimeInterval { + set { animationView.currentTime = newValue } + get { animationView.currentTime } + } + + @objc + public var currentFrame: CGFloat { + set { animationView.currentFrame = newValue } + get { animationView.currentFrame } + } + + @objc + public var realtimeAnimationFrame: CGFloat { + animationView.realtimeAnimationFrame + } + + @objc + public var realtimeAnimationProgress: CGFloat { + animationView.realtimeAnimationProgress + } + + @objc + public var animationSpeed: CGFloat { + set { animationView.animationSpeed = newValue } + get { animationView.animationSpeed } + } + + @objc + public var respectAnimationFrameRate: Bool { + set { animationView.respectAnimationFrameRate = newValue } + get { animationView.respectAnimationFrameRate } + } + + @objc + public var isAnimationPlaying: Bool { + animationView.isAnimationPlaying + } + + @objc + public var backgroundMode: CompatibleBackgroundBehavior { + get { + switch animationView.backgroundBehavior { + case .stop: + .stop + case .pause: + .pause + case .pauseAndRestore: + .pauseAndRestore + case .forceFinish: + .forceFinish + case .continuePlaying: + .continuePlaying + } + } + set { + switch newValue { + case .stop: + animationView.backgroundBehavior = .stop + case .pause: + animationView.backgroundBehavior = .pause + case .pauseAndRestore: + animationView.backgroundBehavior = .pauseAndRestore + case .forceFinish: + animationView.backgroundBehavior = .forceFinish + case .continuePlaying: + animationView.backgroundBehavior = .continuePlaying + } + } + } + + @objc + public func play() { + play(completion: nil) + } + + @objc + public func play(completion: ((Bool) -> Void)?) { + animationView.play(completion: completion) + } + + /// Note: When calling this code from Objective-C, the method signature is + /// playFromProgress:toProgress:completion which drops the standard "With" naming convention. + @objc + public func play( + fromProgress: CGFloat, + toProgress: CGFloat, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromProgress: fromProgress, + toProgress: toProgress, + loopMode: nil, + completion: completion) + } + + /// Note: When calling this code from Objective-C, the method signature is + /// playFromFrame:toFrame:completion which drops the standard "With" naming convention. + @objc + public func play( + fromFrame: CGFloat, + toFrame: CGFloat, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromFrame: fromFrame, + toFrame: toFrame, + loopMode: nil, + completion: completion) + } + + /// Note: When calling this code from Objective-C, the method signature is + /// playFromMarker:toMarker:completion which drops the standard "With" naming convention. + @objc + public func play( + fromMarker: String, + toMarker: String, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromMarker: fromMarker, + toMarker: toMarker, + completion: completion) + } + + @objc + public func play( + marker: String, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + marker: marker, + completion: completion) + } + + @objc + public func stop() { + animationView.stop() + } + + @objc + public func pause() { + animationView.pause() + } + + @objc + public func reloadImages() { + animationView.reloadImages() + } + + @objc + public func forceDisplayUpdate() { + animationView.forceDisplayUpdate() + } + + @objc + public func getValue( + for keypath: CompatibleAnimationKeypath, + atFrame: CGFloat) + -> Any? + { + animationView.getValue( + for: keypath.animationKeypath, + atFrame: atFrame) + } + + @objc + public func logHierarchyKeypaths() { + animationView.logHierarchyKeypaths() + } + + @objc + public func setColorValue(_ color: UIColor, forKeypath keypath: CompatibleAnimationKeypath) { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + let colorspace = LottieConfiguration.shared.colorSpace + + let convertedColor = color.cgColor.converted(to: colorspace, intent: .defaultIntent, options: nil) + + if let components = convertedColor?.components, components.count == 4 { + red = components[0] + green = components[1] + blue = components[2] + alpha = components[3] + } else { + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + } + + let valueProvider = ColorValueProvider(LottieColor(r: Double(red), g: Double(green), b: Double(blue), a: Double(alpha))) + animationView.setValueProvider(valueProvider, keypath: keypath.animationKeypath) + } + + @objc + public func getColorValue(for keypath: CompatibleAnimationKeypath, atFrame: CGFloat) -> UIColor? { + let value = animationView.getValue(for: keypath.animationKeypath, atFrame: atFrame) + guard let colorValue = value as? LottieColor else { + return nil + } + + return UIColor( + red: CGFloat(colorValue.r), + green: CGFloat(colorValue.g), + blue: CGFloat(colorValue.b), + alpha: CGFloat(colorValue.a)) + } + + @objc + public func addSubview( + _ subview: AnimationSubview, + forLayerAt keypath: CompatibleAnimationKeypath) + { + animationView.addSubview( + subview, + forLayerAt: keypath.animationKeypath) + } + + @objc + public func convert( + rect: CGRect, + toLayerAt keypath: CompatibleAnimationKeypath?) + -> CGRect + { + animationView.convert( + rect, + toLayerAt: keypath?.animationKeypath) ?? .zero + } + + @objc + public func convert( + point: CGPoint, + toLayerAt keypath: CompatibleAnimationKeypath?) + -> CGPoint + { + animationView.convert( + point, + toLayerAt: keypath?.animationKeypath) ?? .zero + } + + @objc + public func progressTime(forMarker named: String) -> CGFloat { + animationView.progressTime(forMarker: named) ?? 0 + } + + @objc + public func frameTime(forMarker named: String) -> CGFloat { + animationView.frameTime(forMarker: named) ?? 0 + } + + @objc + public func durationFrameTime(forMarker named: String) -> CGFloat { + animationView.durationFrameTime(forMarker: named) ?? 0 + } + + // MARK: Private + + private let animationView: LottieAnimationView + + private func commonInit() { + setUpViews() + } + + private func setUpViews() { + animationView.translatesAutoresizingMaskIntoConstraints = false + addSubview(animationView) + animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true + animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } +} + +/// An Objective-C compatible wrapper around Lottie's DictionaryTextProvider. +/// Use in tandem with CompatibleAnimationView to supply text to LottieAnimationView +/// when using Lottie in Objective-C. +@objc +public final class CompatibleDictionaryTextProvider: NSObject { + + // MARK: Lifecycle + + @objc + public init(values: [String: String]) { + self.values = values + super.init() + } + + // MARK: Internal + + var textProvider: AnimationKeypathTextProvider? { + DictionaryTextProvider(values) + } + + // MARK: Private + + private let values: [String: String] +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/FilepathImageProvider.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/FilepathImageProvider.swift new file mode 100644 index 0000000000000000000000000000000000000000..32ec3c3b16cdb07eaa9fa8785847bb7e94c573c3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/FilepathImageProvider.swift @@ -0,0 +1,78 @@ +// +// FilepathImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +import Foundation +#if canImport(UIKit) +import UIKit + +/// Provides an image for a lottie animation from a provided Bundle. +public class FilepathImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a specific filepath. + /// + /// - Parameter filepath: The absolute filepath containing the images. + /// - Parameter contentsGravity: The contents gravity to use when rendering the images. + /// + public init(filepath: String, contentsGravity: CALayerContentsGravity = .resize) { + self.filepath = URL(fileURLWithPath: filepath) + self.contentsGravity = contentsGravity + } + + /// Initializes an image provider with a specific filepath. + /// + /// - Parameter filepath: The absolute filepath containing the images. + /// - Parameter contentsGravity: The contents gravity to use when rendering the images. + /// + public init(filepath: URL, contentsGravity: CALayerContentsGravity = .resize) { + self.filepath = filepath + self.contentsGravity = contentsGravity + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + if + asset.name.hasPrefix("data:"), + let url = URL(string: asset.name), + let data = try? Data(contentsOf: url), + let image = UIImage(data: data) + { + return image.cgImage + } + + let directPath = filepath.appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: directPath) { + return UIImage(contentsOfFile: directPath)?.cgImage + } + + let pathWithDirectory = filepath.appendingPathComponent(asset.directory).appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: pathWithDirectory) { + return UIImage(contentsOfFile: pathWithDirectory)?.cgImage + } + + LottieLogger.shared.warn("Could not find image \"\(asset.name)\" in bundle") + return nil + } + + public func contentsGravity(for _: ImageAsset) -> CALayerContentsGravity { + contentsGravity + } + + // MARK: Internal + + let filepath: URL + let contentsGravity: CALayerContentsGravity +} + +extension FilepathImageProvider: Equatable { + public static func ==(_ lhs: FilepathImageProvider, _ rhs: FilepathImageProvider) -> Bool { + lhs.filepath == rhs.filepath + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/LottieAnimationViewBase.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/LottieAnimationViewBase.swift new file mode 100644 index 0000000000000000000000000000000000000000..ae761a3953dbc463ea2dec0a287e2032fc3435d3 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/LottieAnimationViewBase.swift @@ -0,0 +1,83 @@ +// +// LottieAnimationViewBase.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +#if canImport(UIKit) +import UIKit + +/// The base view for `LottieAnimationView` on iOS, tvOS, watchOS, and macCatalyst. +/// +/// Enables the `LottieAnimationView` implementation to be shared across platforms. +open class LottieAnimationViewBase: UIView { + + // MARK: Public + + public override var contentMode: UIView.ContentMode { + didSet { + setNeedsLayout() + } + } + + public override func didMoveToWindow() { + super.didMoveToWindow() + animationMovedToWindow() + } + + public override func layoutSubviews() { + super.layoutSubviews() + layoutAnimation() + } + + // MARK: Internal + + var viewLayer: CALayer? { + layer + } + + var screenScale: CGFloat { + #if os(iOS) || os(tvOS) + max(UITraitCollection.current.displayScale, 1) + #else // if os(visionOS) + // We intentionally don't check `#if os(visionOS)`, because that emits + // a warning when building on Xcode 14 and earlier. + 1.0 + #endif + } + + func layoutAnimation() { + // Implemented by subclasses. + } + + func animationMovedToWindow() { + // Implemented by subclasses. + } + + func commonInit() { + contentMode = .scaleAspectFit + clipsToBounds = true + NotificationCenter.default.addObserver( + self, + selector: #selector(animationWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, + object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(animationWillMoveToBackground), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + } + + @objc + func animationWillMoveToBackground() { + // Implemented by subclasses. + } + + @objc + func animationWillEnterForeground() { + // Implemented by subclasses. + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/UIColorExtension.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/UIColorExtension.swift new file mode 100644 index 0000000000000000000000000000000000000000..b4b81d7f6ffa4ed6a13f273ca6e078785a4e766b --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/iOS/UIColorExtension.swift @@ -0,0 +1,20 @@ +// +// UIColorExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +#if canImport(UIKit) +import UIKit + +extension UIColor { + + public var lottieColorValue: LottieColor { + var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + return LottieColor(r: Double(r), g: Double(g), b: Double(b), a: Double(a)) + } + +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/AnimationSubview.macOS.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/AnimationSubview.macOS.swift new file mode 100644 index 0000000000000000000000000000000000000000..119388425ce2c58d20304f646cd5b75f93493848 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/AnimationSubview.macOS.swift @@ -0,0 +1,19 @@ +// +// AnimationSubview.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/5/19. +// + +#if os(macOS) +import AppKit + +/// A view that can be added to a keypath of an AnimationView +public final class AnimationSubview: NSView { + + var viewLayer: CALayer? { + layer + } + +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/BundleImageProvider.macOS.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/BundleImageProvider.macOS.swift new file mode 100644 index 0000000000000000000000000000000000000000..343664de00c21f4346bd8d731cd4381a6ce5db1f --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/BundleImageProvider.macOS.swift @@ -0,0 +1,90 @@ +// +// LottieBundleImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +#if os(macOS) +import AppKit + +/// Provides an image for a lottie animation from a provided Bundle. +public class BundleImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a bundle and an optional subpath. + /// + /// Provides images for an animation from a bundle. Additionally the provider can + /// search a specific subpath for the images. + /// + /// - Parameter bundle: The bundle containing images for the provider. + /// - Parameter searchPath: The subpath is a path within the bundle to search for image assets. + /// - Parameter contentsGravity: The contents gravity to use when rendering the image. + /// + public init(bundle: Bundle, searchPath: String?, contentsGravity: CALayerContentsGravity = .resize) { + self.bundle = bundle + self.searchPath = searchPath + self.contentsGravity = contentsGravity + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + if let base64Image = asset.base64Image { + return base64Image + } + + let imagePath: String? + /// Try to find the image in the bundle. + if let searchPath { + /// Search in the provided search path for the image + var directoryPath = URL(fileURLWithPath: searchPath) + directoryPath.appendPathComponent(asset.directory) + + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: directoryPath.path) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: searchPath) { + /// Try finding the image in the search path. + imagePath = path + } else { + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } else { + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: asset.directory) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else { + /// First search for the image in bundle. + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } + + guard let foundPath = imagePath, let image = NSImage(contentsOfFile: foundPath) else { + /// No image found. + LottieLogger.shared.warn("Could not find image \"\(asset.name)\" in bundle") + return nil + } + return image.lottie_CGImage + } + + public func contentsGravity(for _: ImageAsset) -> CALayerContentsGravity { + contentsGravity + } + + // MARK: Internal + + let bundle: Bundle + let searchPath: String? + let contentsGravity: CALayerContentsGravity +} + +extension BundleImageProvider: Equatable { + public static func ==(_ lhs: BundleImageProvider, _ rhs: BundleImageProvider) -> Bool { + lhs.bundle == rhs.bundle + && lhs.searchPath == rhs.searchPath + } +} + +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/FilepathImageProvider.macOS.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/FilepathImageProvider.macOS.swift new file mode 100644 index 0000000000000000000000000000000000000000..53aedbb2c2d74a3236d8837a9f093171c5900784 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/FilepathImageProvider.macOS.swift @@ -0,0 +1,86 @@ +// +// FilepathImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +#if os(macOS) +import AppKit + +/// An `AnimationImageProvider` that provides images by name from a specific filepath. +public class FilepathImageProvider: AnimationImageProvider { + + // MARK: Lifecycle + + /// Initializes an image provider with a specific filepath. + /// + /// - Parameter filepath: The absolute filepath containing the images. + /// - Parameter contentsGravity: The contents gravity to use when rendering the images. + /// + public init(filepath: String, contentsGravity: CALayerContentsGravity = .resize) { + self.filepath = URL(fileURLWithPath: filepath) + self.contentsGravity = contentsGravity + } + + /// Initializes an image provider with a specific filepath. + /// + /// - Parameter filepath: The absolute filepath containing the images. + /// - Parameter contentsGravity: The contents gravity to use when rendering the images. + /// + public init(filepath: URL, contentsGravity: CALayerContentsGravity = .resize) { + self.filepath = filepath + self.contentsGravity = contentsGravity + } + + // MARK: Public + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + if + asset.name.hasPrefix("data:"), + let url = URL(string: asset.name), + let data = try? Data(contentsOf: url), + let image = NSImage(data: data) + { + return image.lottie_CGImage + } + + let directPath = filepath.appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: directPath) { + return NSImage(contentsOfFile: directPath)?.lottie_CGImage + } + + let pathWithDirectory = filepath.appendingPathComponent(asset.directory).appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: pathWithDirectory) { + return NSImage(contentsOfFile: pathWithDirectory)?.lottie_CGImage + } + + LottieLogger.shared.warn("Could not find image \"\(asset.name)\" in bundle") + return nil + } + + public func contentsGravity(for _: ImageAsset) -> CALayerContentsGravity { + contentsGravity + } + + // MARK: Internal + + let filepath: URL + let contentsGravity: CALayerContentsGravity +} + +extension FilepathImageProvider: Equatable { + public static func ==(_ lhs: FilepathImageProvider, _ rhs: FilepathImageProvider) -> Bool { + lhs.filepath == rhs.filepath + } +} + +extension NSImage { + @nonobjc + var lottie_CGImage: CGImage? { + guard let imageData = tiffRepresentation else { return nil } + guard let sourceData = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil } + return CGImageSourceCreateImageAtIndex(sourceData, 0, nil) + } +} +#endif diff --git a/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/LottieAnimationViewBase.macOS.swift b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/LottieAnimationViewBase.macOS.swift new file mode 100644 index 0000000000000000000000000000000000000000..d51e5c67f5fd0a66a652be559076237675e3cef2 --- /dev/null +++ b/uni_modules/uni-animation-view/utssdk/app-ios/lottie-ios/Public/macOS/LottieAnimationViewBase.macOS.swift @@ -0,0 +1,102 @@ +// +// LottieAnimationViewBase.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +#if os(macOS) +import AppKit + +public enum LottieContentMode: Int { + case scaleToFill + case scaleAspectFit + case scaleAspectFill + case redraw + case center + case top + case bottom + case left + case right + case topLeft + case topRight + case bottomLeft + case bottomRight +} + +/// The base view for `LottieAnimationView` on macOs. +/// +/// Enables the `LottieAnimationView` implementation to be shared across platforms. +open class LottieAnimationViewBase: NSView { + + // MARK: Public + + public override var wantsUpdateLayer: Bool { + true + } + + public override var isFlipped: Bool { + true + } + + public var contentMode: LottieContentMode = .scaleAspectFit { + didSet { + setNeedsLayout() + } + } + + public override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + animationMovedToWindow() + } + + public override func layout() { + super.layout() + CATransaction.begin() + CATransaction.setDisableActions(true) + layoutAnimation() + CATransaction.commit() + } + + // MARK: Internal + + var screenScale: CGFloat { + NSApp.mainWindow?.backingScaleFactor ?? 1 + } + + var viewLayer: CALayer? { + layer + } + + func layoutAnimation() { + // Implemented by subclasses. + } + + func animationMovedToWindow() { + // Implemented by subclasses. + } + + func commonInit() { + wantsLayer = true + } + + func setNeedsLayout() { + needsLayout = true + } + + func layoutIfNeeded() { + // Implemented by subclasses. + } + + @objc + func animationWillMoveToBackground() { + // Implemented by subclasses. + } + + @objc + func animationWillEnterForeground() { + // Implemented by subclasses. + } + +} +#endif