lottie-ios 4.2.0 → 4.3.1
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.
- package/.github/workflows/main.yml +117 -11
- package/Gemfile +1 -0
- package/Gemfile.lock +5 -0
- package/Lottie.xcodeproj/project.pbxproj +1546 -194
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +374 -116
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
- package/Package.resolved +2 -2
- package/Package.swift +16 -9
- package/README.md +2 -2
- package/Rakefile +122 -25
- package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +3 -4
- package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +11 -7
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +21 -2
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/README.md +27 -0
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
- package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
- package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
- package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
- package/Sources/Private/Model/Assets/Asset.swift +8 -0
- package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
- package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
- package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
- package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
- package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
- package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
- package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
- package/Sources/Private/Model/Objects/Marker.swift +1 -1
- package/Sources/Private/Model/Objects/Transform.swift +1 -2
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
- package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
- package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
- package/Sources/Private/Model/Text/Font.swift +2 -2
- package/Sources/Private/Model/Text/Glyph.swift +1 -1
- package/Sources/Private/RootAnimationLayer.swift +2 -2
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
- package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
- package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
- package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
- package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
- package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
- package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
- package/Sources/Public/Animation/LottieAnimation.swift +21 -2
- package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
- package/Sources/Public/Animation/LottieAnimationView.swift +259 -736
- package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
- package/Sources/Public/Animation/LottieView.swift +462 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
- package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
- package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
- package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
- package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
- package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
- package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
- package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
- package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
- package/Sources/Public/Controls/LottieButton.swift +122 -0
- package/Sources/Public/Controls/LottieSwitch.swift +144 -0
- package/Sources/Public/Controls/LottieViewType.swift +79 -0
- package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
- package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
- package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
- package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
- package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
- package/Sources/Public/Keyframes/Keyframe.swift +6 -0
- package/Sources/Public/Primitives/Vectors.swift +2 -2
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
- package/Sources/Public/iOS/AnimationSubview.swift +1 -1
- package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +6 -4
- package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
- package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
- package/lottie-ios.podspec +2 -1
- package/package.json +1 -1
- package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Created by Laura Skelton on 11/25/16.
|
|
2
|
+
// Copyright © 2016 Airbnb. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
// MARK: - IndexChangeset
|
|
7
|
+
|
|
8
|
+
/// A set of inserts, deletes, updates, and moves that define the changes between two collections.
|
|
9
|
+
struct IndexChangeset {
|
|
10
|
+
|
|
11
|
+
// MARK: Lifecycle
|
|
12
|
+
|
|
13
|
+
init(
|
|
14
|
+
inserts: [Int] = [],
|
|
15
|
+
deletes: [Int] = [],
|
|
16
|
+
updates: [(old: Int, new: Int)] = [],
|
|
17
|
+
moves: [(old: Int, new: Int)] = [],
|
|
18
|
+
newIndices: [Int?] = [],
|
|
19
|
+
duplicates: [[Int]] = [])
|
|
20
|
+
{
|
|
21
|
+
self.inserts = inserts
|
|
22
|
+
self.deletes = deletes
|
|
23
|
+
self.updates = updates
|
|
24
|
+
self.moves = moves
|
|
25
|
+
self.newIndices = newIndices
|
|
26
|
+
self.duplicates = duplicates
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: Internal
|
|
30
|
+
|
|
31
|
+
/// The inserted indices needed to get from the old collection to the new collection.
|
|
32
|
+
var inserts: [Int]
|
|
33
|
+
|
|
34
|
+
/// The deleted indices needed to get from the old collection to the new collection.
|
|
35
|
+
var deletes: [Int]
|
|
36
|
+
|
|
37
|
+
/// The updated indices needed to get from the old collection to the new collection.
|
|
38
|
+
var updates: [(old: Int, new: Int)]
|
|
39
|
+
|
|
40
|
+
/// The moved indices needed to get from the old collection to the new collection.
|
|
41
|
+
var moves: [(old: Int, new: Int)]
|
|
42
|
+
|
|
43
|
+
/// A record for each old collection item to its index (if any) is in the new collection.
|
|
44
|
+
///
|
|
45
|
+
/// The indexes of this `Array` represent the indexes old collection, with elements of the
|
|
46
|
+
/// corresponding index of the same item in the new collection it exists, else `nil`.
|
|
47
|
+
var newIndices: [Int?]
|
|
48
|
+
|
|
49
|
+
/// A record of each element in the new collection that has an identical `diffIdentifier` with
|
|
50
|
+
/// another element in the same collection.
|
|
51
|
+
///
|
|
52
|
+
/// Each element in the outer `Array` corresponds to a duplicated identifier, with each inner
|
|
53
|
+
/// `[Int]` containing the indexes that share a duplicate identifier in the new collection.
|
|
54
|
+
///
|
|
55
|
+
/// While the diffing algorithm makes a best effort to handle duplicates, they can lead to
|
|
56
|
+
/// unexpected behavior since identity of elements cannot be established and should be avoided if
|
|
57
|
+
/// possible.
|
|
58
|
+
var duplicates: [[Int]]
|
|
59
|
+
|
|
60
|
+
/// Whether there are any inserts, deletes, moves, or updates in this changeset.
|
|
61
|
+
var isEmpty: Bool {
|
|
62
|
+
inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - IndexPathChangeset
|
|
67
|
+
|
|
68
|
+
/// A set of inserts, deletes, updates, and moves that define the changes between two collections
|
|
69
|
+
/// with indexes stored as `IndexPath`s.
|
|
70
|
+
struct IndexPathChangeset {
|
|
71
|
+
|
|
72
|
+
// MARK: Lifecycle
|
|
73
|
+
|
|
74
|
+
init(
|
|
75
|
+
inserts: [IndexPath] = [],
|
|
76
|
+
deletes: [IndexPath] = [],
|
|
77
|
+
updates: [(old: IndexPath, new: IndexPath)] = [],
|
|
78
|
+
moves: [(old: IndexPath, new: IndexPath)] = [],
|
|
79
|
+
duplicates: [[IndexPath]] = [])
|
|
80
|
+
{
|
|
81
|
+
self.inserts = inserts
|
|
82
|
+
self.deletes = deletes
|
|
83
|
+
self.updates = updates
|
|
84
|
+
self.moves = moves
|
|
85
|
+
self.duplicates = duplicates
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// MARK: Internal
|
|
89
|
+
|
|
90
|
+
/// The inserted `IndexPath`s needed to get from the old collection to the new collection.
|
|
91
|
+
var inserts: [IndexPath]
|
|
92
|
+
|
|
93
|
+
/// The deleted `IndexPath`s needed to get from the old collection to the new collection.
|
|
94
|
+
var deletes: [IndexPath]
|
|
95
|
+
|
|
96
|
+
/// The updated `IndexPath`s needed to get from the old collection to the new collection.
|
|
97
|
+
var updates: [(old: IndexPath, new: IndexPath)]
|
|
98
|
+
|
|
99
|
+
/// The moved `IndexPath`s needed to get from the old collection to the new collection.
|
|
100
|
+
var moves: [(old: IndexPath, new: IndexPath)]
|
|
101
|
+
|
|
102
|
+
/// A record for each element in the new collection that has an identical `diffIdentifier` with
|
|
103
|
+
/// another element in the same collection.
|
|
104
|
+
///
|
|
105
|
+
/// Each element in the outer `Array` corresponds to a duplicated identifier, with each inner
|
|
106
|
+
/// `[IndexPath]` corresponding to the indexes that share a duplicate identifier in the new
|
|
107
|
+
/// collection.
|
|
108
|
+
///
|
|
109
|
+
/// While the diffing algorithm makes a best effort to handle duplicates, they can lead to
|
|
110
|
+
/// unexpected behavior since identity of elements cannot be established and should be avoided if
|
|
111
|
+
/// possible.
|
|
112
|
+
var duplicates: [[IndexPath]]
|
|
113
|
+
|
|
114
|
+
/// Whether there are any inserts, deletes, moves, or updates in this changeset.
|
|
115
|
+
var isEmpty: Bool {
|
|
116
|
+
inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static func += (left: inout IndexPathChangeset, right: IndexPathChangeset) {
|
|
120
|
+
left.inserts += right.inserts
|
|
121
|
+
left.deletes += right.deletes
|
|
122
|
+
left.updates += right.updates
|
|
123
|
+
left.moves += right.moves
|
|
124
|
+
left.duplicates += right.duplicates
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// MARK: - IndexSetChangeset
|
|
129
|
+
|
|
130
|
+
/// A set of inserts, deletes, updates, and moves that define the changes between two collections
|
|
131
|
+
/// with indexes stored as `IndexSet`.
|
|
132
|
+
struct IndexSetChangeset {
|
|
133
|
+
|
|
134
|
+
// MARK: Lifecycle
|
|
135
|
+
|
|
136
|
+
init(
|
|
137
|
+
inserts: IndexSet = [],
|
|
138
|
+
deletes: IndexSet = [],
|
|
139
|
+
updates: [(old: Int, new: Int)] = [],
|
|
140
|
+
moves: [(old: Int, new: Int)] = [],
|
|
141
|
+
newIndices: [Int?] = [],
|
|
142
|
+
duplicates: [IndexSet] = [])
|
|
143
|
+
{
|
|
144
|
+
self.inserts = inserts
|
|
145
|
+
self.deletes = deletes
|
|
146
|
+
self.updates = updates
|
|
147
|
+
self.moves = moves
|
|
148
|
+
self.newIndices = newIndices
|
|
149
|
+
self.duplicates = duplicates
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// MARK: Internal
|
|
153
|
+
|
|
154
|
+
/// An `IndexSet` of inserts needed to get from the old collection to the new collection.
|
|
155
|
+
var inserts: IndexSet
|
|
156
|
+
|
|
157
|
+
/// An `IndexSet` of deletes needed to get from the old collection to the new collection.
|
|
158
|
+
var deletes: IndexSet
|
|
159
|
+
|
|
160
|
+
/// The updated indices needed to get from the old collection to the new collection.
|
|
161
|
+
var updates: [(old: Int, new: Int)]
|
|
162
|
+
|
|
163
|
+
/// The moved indices needed to get from the old collection to the new collection.
|
|
164
|
+
var moves: [(old: Int, new: Int)]
|
|
165
|
+
|
|
166
|
+
/// A record for each old collection item of what its index (if any) is in the new collection.
|
|
167
|
+
///
|
|
168
|
+
/// The indexes of this `Array` represent the indexes old collection, with elements of the
|
|
169
|
+
/// corresponding index of the same item in the new collection it exists, else `nil`.
|
|
170
|
+
var newIndices: [Int?]
|
|
171
|
+
|
|
172
|
+
/// A record for each element in the new collection that has an identical `diffIdentifier` with
|
|
173
|
+
/// another element in the same collection.
|
|
174
|
+
///
|
|
175
|
+
/// Each element in the `Array` corresponds to a duplicated identifier, with each `IndexSet`
|
|
176
|
+
/// containing the indexes that share a duplicate identifier in the new collection.
|
|
177
|
+
///
|
|
178
|
+
/// While the diffing algorithm makes a best effort to handle duplicates, they can lead to
|
|
179
|
+
/// unexpected behavior since identity of elements cannot be established and should be avoided if
|
|
180
|
+
/// possible.
|
|
181
|
+
var duplicates: [IndexSet]
|
|
182
|
+
|
|
183
|
+
/// Whether there are any inserts, deletes, moves, or updates in this changeset.
|
|
184
|
+
var isEmpty: Bool {
|
|
185
|
+
inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Created by Laura Skelton on 5/11/17.
|
|
2
|
+
// Copyright © 2017 Airbnb. All rights reserved.
|
|
3
|
+
|
|
4
|
+
/// A set of the minimum changes to get from one array of `DiffableSection`s to another, used for
|
|
5
|
+
/// diffing.
|
|
6
|
+
struct SectionedChangeset {
|
|
7
|
+
|
|
8
|
+
// MARK: Lifecycle
|
|
9
|
+
|
|
10
|
+
init(
|
|
11
|
+
sectionChangeset: IndexSetChangeset,
|
|
12
|
+
itemChangeset: IndexPathChangeset)
|
|
13
|
+
{
|
|
14
|
+
self.sectionChangeset = sectionChangeset
|
|
15
|
+
self.itemChangeset = itemChangeset
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// MARK: Internal
|
|
19
|
+
|
|
20
|
+
/// A set of the minimum changes to get from one set of sections to another.
|
|
21
|
+
var sectionChangeset: IndexSetChangeset
|
|
22
|
+
|
|
23
|
+
/// A set of the minimum changes to get from one set of items to another, aggregated across all
|
|
24
|
+
/// sections.
|
|
25
|
+
var itemChangeset: IndexPathChangeset
|
|
26
|
+
|
|
27
|
+
/// Whether there are any inserts, deletes, moves, or updates in this changeset.
|
|
28
|
+
var isEmpty: Bool {
|
|
29
|
+
sectionChangeset.isEmpty && itemChangeset.isEmpty
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Created by eric_horacek on 12/9/20.
|
|
2
|
+
// Copyright © 2020 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
/// A shared logger that allows consumers to intercept Epoxy assertions and warning messages to pipe
|
|
5
|
+
/// into their own logging systems.
|
|
6
|
+
final class EpoxyLogger {
|
|
7
|
+
|
|
8
|
+
// MARK: Lifecycle
|
|
9
|
+
|
|
10
|
+
init(
|
|
11
|
+
assert: @escaping Assert = { condition, message, file, line in
|
|
12
|
+
// If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`,
|
|
13
|
+
// the call will unexpectedly not respect the -O flag and will crash in release
|
|
14
|
+
// https://github.com/apple/swift/issues/60249
|
|
15
|
+
Swift.assert(condition(), message(), file: file, line: line)
|
|
16
|
+
},
|
|
17
|
+
assertionFailure: @escaping AssertionFailure = { message, file, line in
|
|
18
|
+
// If we default to `Swift.assertionFailure` directly with
|
|
19
|
+
// `assertionFailure: AssertionFailure = Swift.assertionFailure`,
|
|
20
|
+
// the call will unexpectedly not respect the -O flag and will crash in release
|
|
21
|
+
// https://github.com/apple/swift/issues/60249
|
|
22
|
+
Swift.assertionFailure(message(), file: file, line: line)
|
|
23
|
+
},
|
|
24
|
+
warn: @escaping Warn = { message, _, _ in
|
|
25
|
+
#if DEBUG
|
|
26
|
+
// swiftlint:disable:next no_direct_standard_out_logs
|
|
27
|
+
print(message())
|
|
28
|
+
#endif
|
|
29
|
+
})
|
|
30
|
+
{
|
|
31
|
+
_assert = assert
|
|
32
|
+
_assertionFailure = assertionFailure
|
|
33
|
+
_warn = warn
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// MARK: Internal
|
|
37
|
+
|
|
38
|
+
/// Logs that an assertion occurred.
|
|
39
|
+
typealias Assert = (
|
|
40
|
+
_ condition: @autoclosure () -> Bool,
|
|
41
|
+
_ message: @autoclosure () -> String,
|
|
42
|
+
_ fileID: StaticString,
|
|
43
|
+
_ line: UInt)
|
|
44
|
+
-> Void
|
|
45
|
+
|
|
46
|
+
/// Logs that an assertion failure occurred.
|
|
47
|
+
typealias AssertionFailure = (
|
|
48
|
+
_ message: @autoclosure () -> String,
|
|
49
|
+
_ fileID: StaticString,
|
|
50
|
+
_ line: UInt)
|
|
51
|
+
-> Void
|
|
52
|
+
|
|
53
|
+
/// Logs a warning message.
|
|
54
|
+
typealias Warn = (
|
|
55
|
+
_ message: @autoclosure () -> String,
|
|
56
|
+
_ fileID: StaticString,
|
|
57
|
+
_ line: UInt)
|
|
58
|
+
-> Void
|
|
59
|
+
|
|
60
|
+
/// The shared instance used to log Epoxy assertions and warnings.
|
|
61
|
+
///
|
|
62
|
+
/// Set this to a new logger instance to intercept assertions and warnings logged by Epoxy.
|
|
63
|
+
static var shared = EpoxyLogger()
|
|
64
|
+
|
|
65
|
+
/// Logs that an assertion occurred.
|
|
66
|
+
func assert(
|
|
67
|
+
_ condition: @autoclosure () -> Bool,
|
|
68
|
+
_ message: @autoclosure () -> String = String(),
|
|
69
|
+
fileID: StaticString = #fileID,
|
|
70
|
+
line: UInt = #line)
|
|
71
|
+
{
|
|
72
|
+
_assert(condition(), message(), fileID, line)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Logs that an assertion failure occurred.
|
|
76
|
+
func assertionFailure(
|
|
77
|
+
_ message: @autoclosure () -> String = String(),
|
|
78
|
+
fileID: StaticString = #fileID,
|
|
79
|
+
line: UInt = #line)
|
|
80
|
+
{
|
|
81
|
+
_assertionFailure(message(), fileID, line)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Logs a warning message.
|
|
85
|
+
func warn(
|
|
86
|
+
_ message: @autoclosure () -> String = String(),
|
|
87
|
+
fileID: StaticString = #fileID,
|
|
88
|
+
line: UInt = #line)
|
|
89
|
+
{
|
|
90
|
+
_warn(message(), fileID, line)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// MARK: Private
|
|
94
|
+
|
|
95
|
+
private let _assert: Assert
|
|
96
|
+
private let _assertionFailure: AssertionFailure
|
|
97
|
+
private let _warn: Warn
|
|
98
|
+
|
|
99
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Created by eric_horacek on 12/15/20.
|
|
2
|
+
// Copyright © 2020 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
/// An Epoxy model with an associated context type that's passed into callback closures.
|
|
5
|
+
protocol CallbackContextEpoxyModeled: EpoxyModeled {
|
|
6
|
+
/// A context type that's passed into callback closures.
|
|
7
|
+
associatedtype CallbackContext
|
|
8
|
+
}
|
|
@@ -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
|
+
}
|