lottie-ios 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/.github/workflows/main.yml +117 -11
  2. package/Gemfile +1 -0
  3. package/Gemfile.lock +5 -0
  4. package/Lottie.xcodeproj/project.pbxproj +1542 -194
  5. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
  7. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
  8. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
  9. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
  10. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
  11. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
  12. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  13. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
  14. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
  15. package/Package.resolved +2 -2
  16. package/Package.swift +16 -9
  17. package/README.md +2 -2
  18. package/Rakefile +122 -25
  19. package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
  20. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
  21. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
  22. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
  23. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
  24. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
  25. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
  26. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
  27. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
  28. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
  29. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
  30. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
  31. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
  32. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
  33. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
  34. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
  35. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
  36. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
  37. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
  38. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
  39. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
  40. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
  41. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
  42. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
  43. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
  44. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
  45. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
  46. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
  47. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
  48. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
  49. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
  50. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
  51. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
  52. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
  53. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
  54. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
  55. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
  56. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
  57. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
  58. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
  59. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
  60. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
  61. package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
  62. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
  63. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
  64. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
  65. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
  66. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
  67. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
  68. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
  69. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
  70. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
  71. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
  72. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
  73. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
  74. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
  75. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
  76. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
  77. package/Sources/Private/EmbeddedLibraries/README.md +27 -0
  78. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
  79. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
  80. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
  81. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
  82. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
  83. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
  84. package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
  85. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
  86. package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
  87. package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
  88. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
  89. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
  90. package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
  91. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
  92. package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
  93. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
  94. package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
  95. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
  96. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
  97. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
  98. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  99. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  101. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
  102. package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
  103. package/Sources/Private/Model/Assets/Asset.swift +8 -0
  104. package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
  105. package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
  106. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
  107. package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
  108. package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
  109. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
  110. package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
  111. package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
  112. package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
  113. package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
  114. package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
  115. package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
  116. package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
  117. package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
  118. package/Sources/Private/Model/Objects/Marker.swift +1 -1
  119. package/Sources/Private/Model/Objects/Transform.swift +1 -2
  120. package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
  121. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
  122. package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
  123. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
  124. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
  125. package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
  126. package/Sources/Private/Model/Text/Font.swift +2 -2
  127. package/Sources/Private/Model/Text/Glyph.swift +1 -1
  128. package/Sources/Private/RootAnimationLayer.swift +2 -2
  129. package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
  130. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
  131. package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
  132. package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
  133. package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
  134. package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
  135. package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
  136. package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
  137. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
  138. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
  139. package/Sources/Public/Animation/LottieAnimation.swift +21 -2
  140. package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
  141. package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
  142. package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
  143. package/Sources/Public/Animation/LottieView.swift +462 -0
  144. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
  145. package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
  146. package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
  147. package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
  148. package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
  149. package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
  150. package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
  151. package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
  152. package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
  153. package/Sources/Public/Controls/LottieButton.swift +122 -0
  154. package/Sources/Public/Controls/LottieSwitch.swift +144 -0
  155. package/Sources/Public/Controls/LottieViewType.swift +79 -0
  156. package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
  157. package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
  158. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
  159. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
  160. package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
  161. package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
  162. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
  163. package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
  164. package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
  165. package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
  166. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
  167. package/Sources/Public/Keyframes/Keyframe.swift +6 -0
  168. package/Sources/Public/Primitives/Vectors.swift +2 -2
  169. package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
  170. package/Sources/Public/iOS/AnimationSubview.swift +1 -1
  171. package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
  172. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
  173. package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
  174. package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
  175. package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
  176. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
  177. package/lottie-ios.podspec +2 -1
  178. package/package.json +1 -1
  179. package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
  180. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
  181. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
  182. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
  183. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
  184. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
  185. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
  186. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
  187. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
  188. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
  189. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
  190. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
  191. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
  192. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
@@ -0,0 +1,48 @@
1
+ // Created by eric_horacek on 3/15/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ /// A generic result builder that enables a DSL for building arrays of Epoxy models.
5
+ @resultBuilder
6
+ enum EpoxyModelArrayBuilder<Model> {
7
+ typealias Expression = Model
8
+ typealias Component = [Model]
9
+
10
+ static func buildExpression(_ expression: Expression) -> Component {
11
+ [expression]
12
+ }
13
+
14
+ static func buildExpression(_ expression: Component) -> Component {
15
+ expression
16
+ }
17
+
18
+ static func buildExpression(_ expression: Expression?) -> Component {
19
+ if let expression = expression {
20
+ return [expression]
21
+ }
22
+ return []
23
+ }
24
+
25
+ static func buildBlock(_ children: Component...) -> Component {
26
+ children.flatMap { $0 }
27
+ }
28
+
29
+ static func buildBlock(_ component: Component) -> Component {
30
+ component
31
+ }
32
+
33
+ static func buildOptional(_ children: Component?) -> Component {
34
+ children ?? []
35
+ }
36
+
37
+ static func buildEither(first child: Component) -> Component {
38
+ child
39
+ }
40
+
41
+ static func buildEither(second child: Component) -> Component {
42
+ child
43
+ }
44
+
45
+ static func buildArray(_ components: [Component]) -> Component {
46
+ components.flatMap { $0 }
47
+ }
48
+ }
@@ -0,0 +1,158 @@
1
+ // Created by eric_horacek on 11/18/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - EpoxyModelProperty
5
+
6
+ /// A property that can be stored in any concrete `EpoxyModeled` type.
7
+ ///
8
+ /// Custom model properties can be declared in any module. It's recommended that properties are
9
+ /// declared as `var`s in extensions to `EpoxyModeled` with a `*Property` suffix.
10
+ ///
11
+ /// For example, to declare a `EpoxyModelProperty` that fulfills the `TitleProviding` protocol:
12
+ ///
13
+ /// ````
14
+ /// internal protocol TitleProviding {
15
+ /// var title: String? { get }
16
+ /// }
17
+ ///
18
+ /// extension EpoxyModeled where Self: TitleProviding {
19
+ /// internal var title: String? {
20
+ /// get { self[titleProperty] }
21
+ /// set { self[titleProperty] = newValue }
22
+ /// }
23
+ ///
24
+ /// internal func title(_ value: String?) -> Self {
25
+ /// copy(updating: titleProperty, to: value)
26
+ /// }
27
+ ///
28
+ /// private var titleProperty: EpoxyModelProperty<String?> {
29
+ /// .init(keyPath: \TitleProviding.title, defaultValue: nil, updateStrategy: .replace)
30
+ /// }
31
+ /// }
32
+ /// ````
33
+ struct EpoxyModelProperty<Value> {
34
+
35
+ // MARK: Lifecycle
36
+
37
+ /// Creates a property identified by a `KeyPath` to its provided `value` and with its default
38
+ /// value if not customized in content by consumers.
39
+ ///
40
+ /// The `updateStrategy` is used to update the value when updating from an old value to a new
41
+ /// value.
42
+ init<Model>(
43
+ keyPath: KeyPath<Model, Value>,
44
+ defaultValue: @escaping @autoclosure () -> Value,
45
+ updateStrategy: UpdateStrategy)
46
+ {
47
+ self.keyPath = keyPath
48
+ self.defaultValue = defaultValue
49
+ self.updateStrategy = updateStrategy
50
+ }
51
+
52
+ // MARK: Internal
53
+
54
+ /// The `KeyPath` that uniquely identifies this property.
55
+ let keyPath: AnyKeyPath
56
+
57
+ /// A closure that produces the default property value when called.
58
+ let defaultValue: () -> Value
59
+
60
+ /// A closure used to update an `EpoxyModelProperty` from an old value to a new value.
61
+ let updateStrategy: UpdateStrategy
62
+
63
+ }
64
+
65
+ // MARK: EpoxyModelProperty.UpdateStrategy
66
+
67
+ extension EpoxyModelProperty {
68
+ /// A closure used to update an `EpoxyModelProperty` from an old value to a new value.
69
+ struct UpdateStrategy {
70
+
71
+ // MARK: Lifecycle
72
+
73
+ init(update: @escaping (Value, Value) -> Value) {
74
+ self.update = update
75
+ }
76
+
77
+ // MARK: Public
78
+
79
+ /// A closure used to update an `EpoxyModelProperty` from an old value to a new value.
80
+ var update: (_ old: Value, _ new: Value) -> Value
81
+ }
82
+ }
83
+
84
+ // MARK: Defaults
85
+
86
+ extension EpoxyModelProperty.UpdateStrategy {
87
+ /// Replaces the old value with the new value when an update occurs.
88
+ static var replace: Self {
89
+ .init { _, new in new }
90
+ }
91
+
92
+ /// Chains the new closure value onto the old closure value, returning a new closure that first
93
+ /// calls the old closure and then subsequently calls the new closure.
94
+ static func chain() -> EpoxyModelProperty<(() -> Void)?>.UpdateStrategy {
95
+ .init { old, new in
96
+ guard let new = new else { return old }
97
+ guard let old = old else { return new }
98
+ return {
99
+ old()
100
+ new()
101
+ }
102
+ }
103
+ }
104
+
105
+ /// Chains the new closure value onto the old closure value, returning a new closure that first
106
+ /// calls the old closure and then subsequently calls the new closure.
107
+ static func chain<A>() -> EpoxyModelProperty<((A) -> Void)?>.UpdateStrategy {
108
+ .init { old, new in
109
+ guard let new = new else { return old }
110
+ guard let old = old else { return new }
111
+ return { a in
112
+ old(a)
113
+ new(a)
114
+ }
115
+ }
116
+ }
117
+
118
+ /// Chains the new closure value onto the old closure value, returning a new closure that first
119
+ /// calls the old closure and then subsequently calls the new closure.
120
+ static func chain<A, B>() -> EpoxyModelProperty<((A, B) -> Void)?>.UpdateStrategy {
121
+ .init { old, new in
122
+ guard let new = new else { return old }
123
+ guard let old = old else { return new }
124
+ return { a, b in
125
+ old(a, b)
126
+ new(a, b)
127
+ }
128
+ }
129
+ }
130
+
131
+ /// Chains the new closure value onto the old closure value, returning a new closure that first
132
+ /// calls the old closure and then subsequently calls the new closure.
133
+ static func chain<A, B, C>() -> EpoxyModelProperty<((A, B, C) -> Void)?>.UpdateStrategy {
134
+ .init { old, new in
135
+ guard let new = new else { return old }
136
+ guard let old = old else { return new }
137
+ return { a, b, c in
138
+ old(a, b, c)
139
+ new(a, b, c)
140
+ }
141
+ }
142
+ }
143
+
144
+ /// Chains the new closure value onto the old closure value, returning a new closure that first
145
+ /// calls the old closure and then subsequently calls the new closure.
146
+ static func chain<A, B, C, D>() -> EpoxyModelProperty<((A, B, C, D) -> Void)?>.UpdateStrategy {
147
+ .init { old, new in
148
+ guard let new = new else { return old }
149
+ guard let old = old else { return new }
150
+ return { a, b, c, d in
151
+ old(a, b, c, d)
152
+ new(a, b, c, d)
153
+ }
154
+ }
155
+ }
156
+
157
+ // Add more arities as needed
158
+ }
@@ -0,0 +1,88 @@
1
+ // Created by eric_horacek on 11/18/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - EpoxyModelStorage
5
+
6
+ /// The underlying storage for an `EpoxyModeled` model that is capable of storing any
7
+ /// `EpoxyModelProperty`.
8
+ ///
9
+ /// Supports being extended with additional storage capabilities in other modules and conditionally
10
+ /// based on the provider capabilities that the content containing this storage conforms to.
11
+ struct EpoxyModelStorage {
12
+
13
+ // MARK: Lifecycle
14
+
15
+ init() { }
16
+
17
+ // MARK: Internal
18
+
19
+ /// Stores or retrieves the value of the specified property.
20
+ subscript<Property>(property: EpoxyModelProperty<Property>) -> Property {
21
+ get {
22
+ guard let propertyStorage = storage[property.keyPath] else {
23
+ return property.defaultValue()
24
+ }
25
+
26
+ // This cast will never fail as the storage is only settable via this subscript and the
27
+ // `KeyPath` key is unique for any provider and value type pair.
28
+ // swiftlint:disable:next force_cast
29
+ return propertyStorage.value as! Property
30
+ }
31
+ set {
32
+ // We first update the value without using the `updateStrategy` since the likely scenario
33
+ // is that there won't be a collision that requires the `updateStrategy`, and we'll be able to
34
+ // return without incurring the cost of another write.
35
+ let propertyStorage = PropertyStorage(value: newValue, property: property)
36
+
37
+ guard var replaced = storage.updateValue(propertyStorage, forKey: property.keyPath) else {
38
+ return
39
+ }
40
+
41
+ // This cast will never fail as the storage is only settable via this subscript and the
42
+ // `KeyPath` key is unique for any provider and value type pair.
43
+ // swiftlint:disable:next force_cast
44
+ replaced.value = property.updateStrategy.update(replaced.value as! Property, newValue)
45
+
46
+ storage[property.keyPath] = replaced
47
+ }
48
+ }
49
+
50
+ /// Merges the given storage into this storage.
51
+ ///
52
+ /// In the case of a collision, the `UpdateStrategy` of the property is used to determine the
53
+ /// resulting value in this storage.
54
+ mutating func merge(_ other: Self) {
55
+ for (key, otherValue) in other.storage {
56
+ // We first update the value without using the `updateStrategy` since the likely scenario
57
+ // is that there won't be a collision that requires the `updateStrategy`, and we'll be able to
58
+ // return without incurring the cost of another write.
59
+ guard var replaced = storage.updateValue(otherValue, forKey: key) else {
60
+ continue
61
+ }
62
+
63
+ replaced.value = replaced.property.update(old: replaced.value, new: otherValue.value)
64
+
65
+ storage[key] = replaced
66
+ }
67
+ }
68
+
69
+ // MARK: Private
70
+
71
+ /// The underlying storage for the properties, with a key of the `EpoxyModelProperty.keyPath` and
72
+ /// a value of the property's `PropertyStorage`.
73
+ ///
74
+ /// Does not include default values.
75
+ private var storage = [AnyKeyPath: PropertyStorage]()
76
+
77
+ }
78
+
79
+ // MARK: - PropertyStorage
80
+
81
+ /// A value stored within an `EpoxyModelStorage`.
82
+ private struct PropertyStorage {
83
+ /// The type-erased value of the `EpoxyModelProperty`.
84
+ var value: Any
85
+
86
+ /// The property's corresponding `EpoxyModelProperty`, erased to an `AnyEpoxyModelProperty`.
87
+ var property: AnyEpoxyModelProperty
88
+ }
@@ -0,0 +1,54 @@
1
+ // Created by eric_horacek on 11/18/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - EpoxyModeled
5
+
6
+ /// A protocol that all concrete Epoxy declarative UI model types conform to.
7
+ ///
8
+ /// This protocol should be conditionally extended to fulfill provider protocols and with chainable
9
+ /// setters for those providers that concrete model types can receive by declaring conformance to
10
+ /// provider protocols.
11
+ protocol EpoxyModeled {
12
+ /// The underlying storage of this model that stores the current property values.
13
+ var storage: EpoxyModelStorage { get set }
14
+ }
15
+
16
+ // MARK: Extensions
17
+
18
+ extension EpoxyModeled {
19
+ /// Stores or retrieves a value of the specified property in `storage`.
20
+ ///
21
+ /// If the value was set previously for the given `property`, the conflict is resolved using the
22
+ /// `EpoxyModelProperty.UpdateStrategy` of the `property`.
23
+ subscript<Property>(property: EpoxyModelProperty<Property>) -> Property {
24
+ get { storage[property] }
25
+ set { storage[property] = newValue }
26
+ }
27
+
28
+ /// Returns a copy of this model with the given property updated to the provided value.
29
+ ///
30
+ /// Typically called from within the context of a chainable setter to allow fluent setting of a
31
+ /// property, e.g.:
32
+ ///
33
+ /// ````
34
+ /// internal func title(_ value: String?) -> Self {
35
+ /// copy(updating: titleProperty, to: value)
36
+ /// }
37
+ /// ````
38
+ ///
39
+ /// If a `value` was set previously for the given `property`, the conflict is resolved using the
40
+ /// `EpoxyModelProperty.UpdateStrategy` of the `property`.
41
+ func copy<Value>(updating property: EpoxyModelProperty<Value>, to value: Value) -> Self {
42
+ var copy = self
43
+ copy.storage[property] = value
44
+ return copy
45
+ }
46
+
47
+ /// Returns a copy of this model produced by merging the given `other` model's storage into this
48
+ /// model's storage.
49
+ func merging(_ other: EpoxyModeled) -> Self {
50
+ var copy = self
51
+ copy.storage.merge(other.storage)
52
+ return copy
53
+ }
54
+ }
@@ -0,0 +1,29 @@
1
+ // Created by eric_horacek on 12/1/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - AnyEpoxyModelProperty
5
+
6
+ /// An erased `EpoxyModelProperty`, with the ability to call the `UpdateStrategy` even when the type
7
+ /// has been erased.
8
+ protocol AnyEpoxyModelProperty {
9
+ /// Returns the updated property from updating from given old to new property.
10
+ func update(old: Any, new: Any) -> Any
11
+ }
12
+
13
+ // MARK: - EpoxyModelProperty + AnyEpoxyModelProperty
14
+
15
+ extension EpoxyModelProperty: AnyEpoxyModelProperty {
16
+ func update(old: Any, new: Any) -> Any {
17
+ guard let typedOld = old as? Value else {
18
+ EpoxyLogger.shared.assertionFailure(
19
+ "Expected old to be of type \(Value.self), instead found \(old). This is programmer error.")
20
+ return defaultValue()
21
+ }
22
+ guard let typedNew = new as? Value else {
23
+ EpoxyLogger.shared.assertionFailure(
24
+ "Expected new to be of type \(Value.self), instead found \(old). This is programmer error.")
25
+ return defaultValue()
26
+ }
27
+ return updateStrategy.update(typedOld, typedNew)
28
+ }
29
+ }
@@ -0,0 +1,39 @@
1
+ // Created by Cal Stephens on 10/15/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - ClassReference
5
+
6
+ /// A `Hashable` value wrapper around an `AnyClass` value
7
+ /// - Unlike `ObjectIdentifier(class)`, `ClassReference(class)`
8
+ /// preserves the `AnyClass` value and is more human-readable.
9
+ struct ClassReference {
10
+ init(_ class: AnyClass) {
11
+ self.class = `class`
12
+ }
13
+
14
+ let `class`: AnyClass
15
+ }
16
+
17
+ // MARK: Equatable
18
+
19
+ extension ClassReference: Equatable {
20
+ static func ==(_ lhs: Self, _ rhs: Self) -> Bool {
21
+ ObjectIdentifier(lhs.class) == ObjectIdentifier(rhs.class)
22
+ }
23
+ }
24
+
25
+ // MARK: Hashable
26
+
27
+ extension ClassReference: Hashable {
28
+ func hash(into hasher: inout Hasher) {
29
+ hasher.combine(ObjectIdentifier(`class`))
30
+ }
31
+ }
32
+
33
+ // MARK: CustomStringConvertible
34
+
35
+ extension ClassReference: CustomStringConvertible {
36
+ var description: String {
37
+ String(describing: `class`)
38
+ }
39
+ }
@@ -0,0 +1,10 @@
1
+ // Created by eric_horacek on 12/16/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ /// The capability of providing a flag indicating whether an operation should be animated.
5
+ ///
6
+ /// Typically conformed to by the `CallbackContext` of a `CallbackContextEpoxyModeled`.
7
+ protocol AnimatedProviding {
8
+ /// Whether this operation should be animated.
9
+ var animated: Bool { get }
10
+ }
@@ -0,0 +1,57 @@
1
+ // Created by eric_horacek on 12/1/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - DataIDProviding
5
+
6
+ /// The capability of providing a stable data identifier with an erased type.
7
+ ///
8
+ /// While it has similar semantics, this type cannot inherit from `Identifiable` as this would give
9
+ /// it an associated type, which would cause the `keyPath` used in its `EpoxyModelProperty` to not
10
+ /// be stable across types if written as `\Self.dataID` since the `KeyPath` `Root` would be
11
+ /// different for each type.
12
+ ///
13
+ /// - SeeAlso: `Identifiable`.
14
+ protocol DataIDProviding {
15
+ /// A stable identifier that uniquely identifies this instance, with its typed erased.
16
+ ///
17
+ /// Defaults to `DefaultDataID.noneProvided` if no data ID is provided.
18
+ var dataID: AnyHashable { get }
19
+ }
20
+
21
+ // MARK: - EpoxyModeled
22
+
23
+ extension EpoxyModeled where Self: DataIDProviding {
24
+
25
+ // MARK: Internal
26
+
27
+ /// A stable identifier that uniquely identifies this model, with its typed erased.
28
+ var dataID: AnyHashable {
29
+ get { self[dataIDProperty] }
30
+ set { self[dataIDProperty] = newValue }
31
+ }
32
+
33
+ /// Returns a copy of this model with the ID replaced with the provided ID.
34
+ func dataID(_ value: AnyHashable) -> Self {
35
+ copy(updating: dataIDProperty, to: value)
36
+ }
37
+
38
+ // MARK: Private
39
+
40
+ private var dataIDProperty: EpoxyModelProperty<AnyHashable> {
41
+ EpoxyModelProperty(
42
+ keyPath: \DataIDProviding.dataID,
43
+ defaultValue: DefaultDataID.noneProvided,
44
+ updateStrategy: .replace)
45
+ }
46
+ }
47
+
48
+ // MARK: - DefaultDataID
49
+
50
+ /// The default data ID when none is provided.
51
+ enum DefaultDataID: Hashable, CustomDebugStringConvertible {
52
+ case noneProvided
53
+
54
+ var debugDescription: String {
55
+ "DefaultDataID.noneProvided"
56
+ }
57
+ }
@@ -0,0 +1,41 @@
1
+ // Created by eric_horacek on 1/6/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - DidDisplayProviding
5
+
6
+ /// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `didDisplay`
7
+ /// closure property.
8
+ ///
9
+ /// - SeeAlso: `WillDisplayProviding`
10
+ /// - SeeAlso: `DidEndDisplayingProviding`
11
+ protocol DidDisplayProviding { }
12
+
13
+ // MARK: - CallbackContextEpoxyModeled
14
+
15
+ extension CallbackContextEpoxyModeled where Self: DidDisplayProviding {
16
+
17
+ // MARK: Internal
18
+
19
+ /// A closure that's called after a view has been added to the view hierarchy following any
20
+ /// appearance animations.
21
+ typealias DidDisplay = (_ context: CallbackContext) -> Void
22
+
23
+ /// A closure that's called after the view has been added to the view hierarchy following any
24
+ /// appearance animations.
25
+ var didDisplay: DidDisplay? {
26
+ get { self[didDisplayProperty] }
27
+ set { self[didDisplayProperty] = newValue }
28
+ }
29
+
30
+ /// Returns a copy of this model with the given did display closure called after the current did
31
+ /// display closure of this model, if there is one.
32
+ func didDisplay(_ value: DidDisplay?) -> Self {
33
+ copy(updating: didDisplayProperty, to: value)
34
+ }
35
+
36
+ // MARK: Private
37
+
38
+ private var didDisplayProperty: EpoxyModelProperty<DidDisplay?> {
39
+ .init(keyPath: \Self.didDisplay, defaultValue: nil, updateStrategy: .chain())
40
+ }
41
+ }
@@ -0,0 +1,41 @@
1
+ // Created by eric_horacek on 12/15/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - DidEndDisplayingProviding
5
+
6
+ /// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a
7
+ /// `didEndDisplaying` closure property.
8
+ protocol DidEndDisplayingProviding { }
9
+
10
+ // MARK: - CallbackContextEpoxyModeled
11
+
12
+ extension CallbackContextEpoxyModeled where Self: DidEndDisplayingProviding {
13
+
14
+ // MARK: Internal
15
+
16
+ /// A closure that's called when a view is no longer displayed following any disappearance
17
+ /// animations and when it has been removed from the view hierarchy.
18
+ typealias DidEndDisplaying = (_ context: CallbackContext) -> Void
19
+
20
+ /// A closure that's called when the view is no longer displayed following any disappearance
21
+ /// animations and when it has been removed from the view hierarchy.
22
+ var didEndDisplaying: DidEndDisplaying? {
23
+ get { self[didEndDisplayingProperty] }
24
+ set { self[didEndDisplayingProperty] = newValue }
25
+ }
26
+
27
+ /// Returns a copy of this model with the given did end displaying closure called after the
28
+ /// current did end displaying closure of this model, if there is one.
29
+ func didEndDisplaying(_ value: DidEndDisplaying?) -> Self {
30
+ copy(updating: didEndDisplayingProperty, to: value)
31
+ }
32
+
33
+ // MARK: Private
34
+
35
+ private var didEndDisplayingProperty: EpoxyModelProperty<DidEndDisplaying?> {
36
+ .init(
37
+ keyPath: \Self.didEndDisplaying,
38
+ defaultValue: nil,
39
+ updateStrategy: .chain())
40
+ }
41
+ }
@@ -0,0 +1,36 @@
1
+ // Created by eric_horacek on 12/2/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - DidSelectProviding
5
+
6
+ /// A sentinel protocol for enabling an `CallbackContextEpoxyModeled` to provide a `didSelect`
7
+ /// closure property.
8
+ protocol DidSelectProviding { }
9
+
10
+ // MARK: - CallbackContextEpoxyModeled
11
+
12
+ extension CallbackContextEpoxyModeled where Self: DidSelectProviding {
13
+
14
+ // MARK: Internal
15
+
16
+ /// A closure that's called to handle this model's view being selected.
17
+ typealias DidSelect = (CallbackContext) -> Void
18
+
19
+ /// A closure that's called to handle this model's view being selected.
20
+ var didSelect: DidSelect? {
21
+ get { self[didSelectProperty] }
22
+ set { self[didSelectProperty] = newValue }
23
+ }
24
+
25
+ /// Returns a copy of this model with the given did select closure called after the current did
26
+ /// select closure of this model, if there is one.
27
+ func didSelect(_ value: DidSelect?) -> Self {
28
+ copy(updating: didSelectProperty, to: value)
29
+ }
30
+
31
+ // MARK: Private
32
+
33
+ private var didSelectProperty: EpoxyModelProperty<DidSelect?> {
34
+ .init(keyPath: \Self.didSelect, defaultValue: nil, updateStrategy: .chain())
35
+ }
36
+ }
@@ -0,0 +1,49 @@
1
+ // Created by eric_horacek on 12/2/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - ErasedContentProviding
5
+
6
+ /// The capability of providing an type-erased `Equatable` content instance.
7
+ protocol ErasedContentProviding {
8
+ /// The type-erased content instance of this model, else `nil` if there is no content.
9
+ ///
10
+ /// If there was an `AnyEquatable` type, we could store this property using it. Instead we need
11
+ /// need to store `isErasedContentEqual` to determine equality.
12
+ var erasedContent: Any? { get }
13
+
14
+ /// A closure that can be called to determine whether the given `model`'s `erasedContent` is equal
15
+ /// to this model's `erasedContent`, else `nil` if there is no content or the content is always
16
+ /// equal.
17
+ var isErasedContentEqual: ((Self) -> Bool)? { get }
18
+ }
19
+
20
+ // MARK: - EpoxyModeled
21
+
22
+ extension EpoxyModeled where Self: ErasedContentProviding {
23
+
24
+ // MARK: Internal
25
+
26
+ /// The type-erased content instance of this model, else `nil` if there is no content.
27
+ var erasedContent: Any? {
28
+ get { self[contentProperty] }
29
+ set { self[contentProperty] = newValue }
30
+ }
31
+
32
+ /// A closure that can be called to determine whether the given `model`'s `erasedContent` is equal
33
+ /// to this model's `erasedContent`, else `nil` if there is no content or the content is always
34
+ /// equal.
35
+ var isErasedContentEqual: ((Self) -> Bool)? {
36
+ get { self[isContentEqualProperty] }
37
+ set { self[isContentEqualProperty] = newValue }
38
+ }
39
+
40
+ // MARK: Private
41
+
42
+ private var contentProperty: EpoxyModelProperty<Any?> {
43
+ .init(keyPath: \Self.erasedContent, defaultValue: nil, updateStrategy: .replace)
44
+ }
45
+
46
+ private var isContentEqualProperty: EpoxyModelProperty<((Self) -> Bool)?> {
47
+ .init(keyPath: \Self.isErasedContentEqual, defaultValue: nil, updateStrategy: .replace)
48
+ }
49
+ }