react-native-smooth-clip-view 0.2.9 → 0.4.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 (193) hide show
  1. package/README.md +132 -349
  2. package/android/build.gradle +8 -4
  3. package/android/proguard-rules.pro +3 -3
  4. package/android/src/main/cpp/SmoothClipAndroid.h +13 -6
  5. package/android/src/main/cpp/SmoothClipBindings.cpp +605 -123
  6. package/android/src/main/cpp/SmoothClipRegistry.cpp +975 -209
  7. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +192 -118
  8. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +22 -4
  9. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +81 -56
  10. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +449 -240
  11. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +150 -38
  12. package/cpp/SmoothClipAnimationCurve.h +317 -397
  13. package/cpp/SmoothClipRegistry.h +84 -20
  14. package/cpp/SmoothClipSharedGeometry.h +131 -22
  15. package/cpp/SmoothClipVelocityTracker.h +16 -12
  16. package/ios/SmoothClipGeometry.h +188 -21
  17. package/ios/SmoothClipRegistry.mm +869 -368
  18. package/ios/SmoothClipRegistrySnapshot.h +13 -0
  19. package/ios/SmoothClipTurboModule.cpp +513 -156
  20. package/ios/SmoothClipTurboModule.h +123 -56
  21. package/ios/SmoothClipView.mm +1074 -449
  22. package/ios/SmoothClipViewRegistry.h +3 -7
  23. package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  24. package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  25. package/lib/module/NativeSmoothClipModule.js.map +1 -1
  26. package/lib/module/SmoothClipView.ios.js +94 -11
  27. package/lib/module/SmoothClipView.ios.js.map +1 -1
  28. package/lib/module/SmoothClipView.js +1 -46
  29. package/lib/module/SmoothClipView.js.map +1 -1
  30. package/lib/module/SmoothClipViewNativeComponent.ts +32 -12
  31. package/lib/module/capabilities.js +4 -0
  32. package/lib/module/capabilities.js.map +1 -0
  33. package/lib/module/capabilities.native.js +18 -0
  34. package/lib/module/capabilities.native.js.map +1 -0
  35. package/lib/module/capabilityTypes.js +2 -0
  36. package/lib/module/capabilityTypes.js.map +1 -0
  37. package/lib/module/controllerInternals.js +28 -0
  38. package/lib/module/controllerInternals.js.map +1 -0
  39. package/lib/module/controllerLifecycle.js +9 -0
  40. package/lib/module/controllerLifecycle.js.map +1 -0
  41. package/lib/module/controllerTypes.js +4 -0
  42. package/lib/module/controllerTypes.js.map +1 -0
  43. package/lib/module/controllers.android.js +4 -0
  44. package/lib/module/controllers.android.js.map +1 -0
  45. package/lib/module/controllers.ios.js +4 -0
  46. package/lib/module/controllers.ios.js.map +1 -0
  47. package/lib/module/controllers.js +4 -0
  48. package/lib/module/controllers.js.map +1 -0
  49. package/lib/module/controllers.native.js +89 -0
  50. package/lib/module/controllers.native.js.map +1 -0
  51. package/lib/module/geometry.js +180 -30
  52. package/lib/module/geometry.js.map +1 -1
  53. package/lib/module/groupTypes.js +4 -0
  54. package/lib/module/groupTypes.js.map +1 -0
  55. package/lib/module/groups.android.js +4 -0
  56. package/lib/module/groups.android.js.map +1 -0
  57. package/lib/module/groups.ios.js +4 -0
  58. package/lib/module/groups.ios.js.map +1 -0
  59. package/lib/module/groups.js +4 -0
  60. package/lib/module/groups.js.map +1 -0
  61. package/lib/module/groups.native.js +399 -0
  62. package/lib/module/groups.native.js.map +1 -0
  63. package/lib/module/ids.js +8 -0
  64. package/lib/module/ids.js.map +1 -0
  65. package/lib/module/index.js +4 -3
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/module/nativeCompletion.js +37 -16
  68. package/lib/module/nativeCompletion.js.map +1 -1
  69. package/lib/module/presentationCodec.js +81 -0
  70. package/lib/module/presentationCodec.js.map +1 -0
  71. package/lib/typescript/src/NativeSmoothClipModule.d.ts +20 -4
  72. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  73. package/lib/typescript/src/SmoothClipView.d.ts +2 -10
  74. package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
  75. package/lib/typescript/src/SmoothClipView.ios.d.ts +139 -4
  76. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  77. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +16 -3
  78. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  79. package/lib/typescript/src/capabilities.d.ts +3 -0
  80. package/lib/typescript/src/capabilities.d.ts.map +1 -0
  81. package/lib/typescript/src/capabilities.native.d.ts +3 -0
  82. package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
  83. package/lib/typescript/src/capabilityTypes.d.ts +4 -0
  84. package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
  85. package/lib/typescript/src/controllerInternals.d.ts +14 -0
  86. package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
  87. package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
  88. package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
  89. package/lib/typescript/src/controllerTypes.d.ts +62 -0
  90. package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
  91. package/lib/typescript/src/controllers.android.d.ts +2 -0
  92. package/lib/typescript/src/controllers.android.d.ts.map +1 -0
  93. package/lib/typescript/src/controllers.d.ts +2 -0
  94. package/lib/typescript/src/controllers.d.ts.map +1 -0
  95. package/lib/typescript/src/controllers.ios.d.ts +2 -0
  96. package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
  97. package/lib/typescript/src/controllers.native.d.ts +3 -0
  98. package/lib/typescript/src/controllers.native.d.ts.map +1 -0
  99. package/lib/typescript/src/geometry.d.ts +49 -5
  100. package/lib/typescript/src/geometry.d.ts.map +1 -1
  101. package/lib/typescript/src/groupTypes.d.ts +31 -0
  102. package/lib/typescript/src/groupTypes.d.ts.map +1 -0
  103. package/lib/typescript/src/groups.android.d.ts +2 -0
  104. package/lib/typescript/src/groups.android.d.ts.map +1 -0
  105. package/lib/typescript/src/groups.d.ts +2 -0
  106. package/lib/typescript/src/groups.d.ts.map +1 -0
  107. package/lib/typescript/src/groups.ios.d.ts +2 -0
  108. package/lib/typescript/src/groups.ios.d.ts.map +1 -0
  109. package/lib/typescript/src/groups.native.d.ts +3 -0
  110. package/lib/typescript/src/groups.native.d.ts.map +1 -0
  111. package/lib/typescript/src/ids.d.ts +2 -0
  112. package/lib/typescript/src/ids.d.ts.map +1 -0
  113. package/lib/typescript/src/index.d.ts +8 -5
  114. package/lib/typescript/src/index.d.ts.map +1 -1
  115. package/lib/typescript/src/nativeCompletion.d.ts +16 -6
  116. package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
  117. package/lib/typescript/src/presentationCodec.d.ts +10 -0
  118. package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
  119. package/package.json +17 -13
  120. package/src/NativeSmoothClipModule.ts +80 -56
  121. package/src/SmoothClipView.ios.tsx +142 -16
  122. package/src/SmoothClipView.tsx +6 -76
  123. package/src/SmoothClipViewNativeComponent.ts +32 -12
  124. package/src/capabilities.native.ts +19 -0
  125. package/src/capabilities.ts +2 -0
  126. package/src/capabilityTypes.ts +3 -0
  127. package/src/controllerInternals.ts +52 -0
  128. package/src/controllerLifecycle.ts +6 -0
  129. package/src/controllerTypes.ts +87 -0
  130. package/src/controllers.android.ts +1 -0
  131. package/src/controllers.ios.ts +1 -0
  132. package/src/controllers.native.ts +104 -0
  133. package/src/controllers.ts +1 -0
  134. package/src/geometry.ts +293 -45
  135. package/src/groupTypes.ts +53 -0
  136. package/src/groups.android.ts +1 -0
  137. package/src/groups.ios.ts +1 -0
  138. package/src/groups.native.ts +604 -0
  139. package/src/groups.ts +1 -0
  140. package/src/ids.ts +6 -0
  141. package/src/index.ts +32 -12
  142. package/src/nativeCompletion.ts +67 -32
  143. package/src/presentationCodec.ts +134 -0
  144. package/lib/module/SmoothClipView.web.js +0 -54
  145. package/lib/module/SmoothClipView.web.js.map +0 -1
  146. package/lib/module/driverState.js +0 -110
  147. package/lib/module/driverState.js.map +0 -1
  148. package/lib/module/driverTypes.js +0 -4
  149. package/lib/module/driverTypes.js.map +0 -1
  150. package/lib/module/drivers.android.js +0 -6
  151. package/lib/module/drivers.android.js.map +0 -1
  152. package/lib/module/drivers.ios.js +0 -6
  153. package/lib/module/drivers.ios.js.map +0 -1
  154. package/lib/module/drivers.js +0 -284
  155. package/lib/module/drivers.js.map +0 -1
  156. package/lib/module/drivers.native.js +0 -556
  157. package/lib/module/drivers.native.js.map +0 -1
  158. package/lib/module/index.web.js +0 -8
  159. package/lib/module/index.web.js.map +0 -1
  160. package/lib/module/reactRequests.js +0 -56
  161. package/lib/module/reactRequests.js.map +0 -1
  162. package/lib/module/webClipPath.js +0 -25
  163. package/lib/module/webClipPath.js.map +0 -1
  164. package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
  165. package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
  166. package/lib/typescript/src/driverState.d.ts +0 -31
  167. package/lib/typescript/src/driverState.d.ts.map +0 -1
  168. package/lib/typescript/src/driverTypes.d.ts +0 -107
  169. package/lib/typescript/src/driverTypes.d.ts.map +0 -1
  170. package/lib/typescript/src/drivers.android.d.ts +0 -3
  171. package/lib/typescript/src/drivers.android.d.ts.map +0 -1
  172. package/lib/typescript/src/drivers.d.ts +0 -5
  173. package/lib/typescript/src/drivers.d.ts.map +0 -1
  174. package/lib/typescript/src/drivers.ios.d.ts +0 -3
  175. package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
  176. package/lib/typescript/src/drivers.native.d.ts +0 -5
  177. package/lib/typescript/src/drivers.native.d.ts.map +0 -1
  178. package/lib/typescript/src/index.web.d.ts +0 -9
  179. package/lib/typescript/src/index.web.d.ts.map +0 -1
  180. package/lib/typescript/src/reactRequests.d.ts +0 -9
  181. package/lib/typescript/src/reactRequests.d.ts.map +0 -1
  182. package/lib/typescript/src/webClipPath.d.ts +0 -7
  183. package/lib/typescript/src/webClipPath.d.ts.map +0 -1
  184. package/src/SmoothClipView.web.tsx +0 -66
  185. package/src/driverState.ts +0 -158
  186. package/src/driverTypes.ts +0 -140
  187. package/src/drivers.android.ts +0 -8
  188. package/src/drivers.ios.ts +0 -8
  189. package/src/drivers.native.ts +0 -899
  190. package/src/drivers.ts +0 -427
  191. package/src/index.web.ts +0 -23
  192. package/src/reactRequests.ts +0 -76
  193. package/src/webClipPath.ts +0 -30
@@ -5,6 +5,7 @@
5
5
  #include <fbjni/fbjni.h>
6
6
  #include <jsi/jsi.h>
7
7
 
8
+ #include <algorithm>
8
9
  #include <cmath>
9
10
  #include <limits>
10
11
  #include <memory>
@@ -26,26 +27,80 @@ namespace smoothclip {
26
27
  namespace {
27
28
 
28
29
  constexpr double kMaxSafeJavaScriptInteger = 9007199254740991.0;
30
+ constexpr size_t kPresentationStride = 21;
31
+ constexpr size_t kSnapshotStride = kPresentationStride + 1;
32
+ constexpr size_t kMotionEntryStride = kPresentationStride * 2 + 2;
29
33
 
30
34
  bool validDriverId(double value) {
31
35
  return std::isfinite(value) && value > 0 &&
32
36
  value <= kMaxSafeJavaScriptInteger && std::floor(value) == value;
33
37
  }
34
38
 
35
- bool finitePresentation(const Presentation &presentation) {
39
+ bool validCurve(double value) {
40
+ return std::isfinite(value) && std::floor(value) == value &&
41
+ (value == static_cast<double>(ClipCurve::Circular) ||
42
+ value == static_cast<double>(ClipCurve::Continuous));
43
+ }
44
+
45
+ bool validRadiusOverride(double value) {
46
+ return std::isfinite(value) || std::isnan(value);
47
+ }
48
+
49
+ bool finiteBasePresentation(const Presentation &presentation) {
36
50
  return std::isfinite(presentation.clip.x) &&
37
51
  std::isfinite(presentation.clip.y) &&
38
52
  std::isfinite(presentation.clip.width) &&
39
53
  std::isfinite(presentation.clip.height) &&
40
54
  std::isfinite(presentation.clip.radius) &&
55
+ validRadiusOverride(presentation.clip.topLeftRadius) &&
56
+ validRadiusOverride(presentation.clip.topRightRadius) &&
57
+ validRadiusOverride(presentation.clip.bottomRightRadius) &&
58
+ validRadiusOverride(presentation.clip.bottomLeftRadius) &&
59
+ (presentation.clip.curve == ClipCurve::Circular ||
60
+ presentation.clip.curve == ClipCurve::Continuous) &&
41
61
  std::isfinite(presentation.contentTranslateX) &&
42
- std::isfinite(presentation.contentTranslateY);
62
+ std::isfinite(presentation.contentTranslateY) &&
63
+ std::isfinite(presentation.contentScale) &&
64
+ presentation.contentScale > 0;
65
+ }
66
+
67
+ bool finitePresentation(const Presentation &presentation) {
68
+ return finiteBasePresentation(presentation) &&
69
+ std::isfinite(presentation.clip.topLeftRadius) &&
70
+ std::isfinite(presentation.clip.topRightRadius) &&
71
+ std::isfinite(presentation.clip.bottomRightRadius) &&
72
+ std::isfinite(presentation.clip.bottomLeftRadius) &&
73
+ std::isfinite(presentation.shadow.red) &&
74
+ std::isfinite(presentation.shadow.green) &&
75
+ std::isfinite(presentation.shadow.blue) &&
76
+ std::isfinite(presentation.shadow.alpha) &&
77
+ std::isfinite(presentation.shadow.offsetX) &&
78
+ std::isfinite(presentation.shadow.offsetY) &&
79
+ std::isfinite(presentation.shadow.blurRadius) &&
80
+ std::isfinite(presentation.shadow.spreadDistance) &&
81
+ presentation.shadow.red >= 0 && presentation.shadow.red <= 1 &&
82
+ presentation.shadow.green >= 0 && presentation.shadow.green <= 1 &&
83
+ presentation.shadow.blue >= 0 && presentation.shadow.blue <= 1 &&
84
+ presentation.shadow.alpha >= 0 && presentation.shadow.alpha <= 1 &&
85
+ presentation.shadow.blurRadius >= 0;
43
86
  }
44
87
 
45
88
  bool boolArg(const Value *args, size_t count, size_t index) {
46
89
  return index < count && args[index].isBool() && args[index].getBool();
47
90
  }
48
91
 
92
+ bool numberAt(
93
+ Runtime &runtime,
94
+ const Array &values,
95
+ size_t index,
96
+ double &result) {
97
+ if (index >= values.size(runtime)) return false;
98
+ const Value value = values.getValueAtIndex(runtime, index);
99
+ if (!value.isNumber()) return false;
100
+ result = value.asNumber();
101
+ return std::isfinite(result);
102
+ }
103
+
49
104
  // Optional trailing start stamp (Reanimated-rule milliseconds captured in the
50
105
  // issuing worklet), converted to the registry's CLOCK_MONOTONIC seconds. A
51
106
  // missing or non-numeric argument — and the JS side's deliberate NaN fallback
@@ -58,26 +113,89 @@ double startStampArg(const Value *args, size_t count, size_t index) {
58
113
  : std::numeric_limits<double>::quiet_NaN();
59
114
  }
60
115
 
61
- Presentation presentationFromArgs(const Value *args, size_t offset) {
62
- return Presentation{
63
- {args[offset].asNumber(),
64
- args[offset + 1].asNumber(),
65
- args[offset + 2].asNumber(),
66
- args[offset + 3].asNumber(),
67
- args[offset + 4].asNumber()},
68
- args[offset + 5].asNumber(),
69
- args[offset + 6].asNumber()};
116
+ bool presentationAt(
117
+ Runtime &runtime,
118
+ const Array &values,
119
+ size_t offset,
120
+ Presentation &result) {
121
+ const size_t length = values.size(runtime);
122
+ if (offset > length || length - offset < kPresentationStride) {
123
+ return false;
124
+ }
125
+ double packet[kPresentationStride];
126
+ for (size_t index = 0; index < kPresentationStride; index += 1) {
127
+ const Value value = values.getValueAtIndex(runtime, offset + index);
128
+ if (!value.isNumber()) return false;
129
+ packet[index] = value.asNumber();
130
+ }
131
+ const double curveCode = packet[8];
132
+ if (!validCurve(curveCode) ||
133
+ (packet[12] != 0.0 && packet[12] != 1.0)) {
134
+ return false;
135
+ }
136
+ Geometry geometry{
137
+ packet[0], packet[1], packet[2], packet[3],
138
+ 0.0};
139
+ geometry.topLeftRadius = packet[4];
140
+ geometry.topRightRadius = packet[5];
141
+ geometry.bottomRightRadius = packet[6];
142
+ geometry.bottomLeftRadius = packet[7];
143
+ geometry.curve = curveCode == static_cast<double>(ClipCurve::Continuous)
144
+ ? ClipCurve::Continuous
145
+ : ClipCurve::Circular;
146
+ if (geometry.topLeftRadius == geometry.topRightRadius &&
147
+ geometry.topLeftRadius == geometry.bottomRightRadius &&
148
+ geometry.topLeftRadius == geometry.bottomLeftRadius) {
149
+ geometry.radius = geometry.topLeftRadius;
150
+ }
151
+ result = Presentation{
152
+ geometry,
153
+ packet[9], packet[10], packet[11],
154
+ Shadow{
155
+ packet[12] == 1.0,
156
+ packet[13], packet[14], packet[15], packet[16],
157
+ packet[17], packet[18], packet[19], packet[20]}};
158
+ return finitePresentation(result);
159
+ }
160
+
161
+ void appendPresentation(
162
+ Runtime &runtime,
163
+ Array &result,
164
+ size_t &offset,
165
+ const Presentation &presentation) {
166
+ result.setValueAtIndex(runtime, offset++, presentation.clip.x);
167
+ result.setValueAtIndex(runtime, offset++, presentation.clip.y);
168
+ result.setValueAtIndex(runtime, offset++, presentation.clip.width);
169
+ result.setValueAtIndex(runtime, offset++, presentation.clip.height);
170
+ result.setValueAtIndex(runtime, offset++, SmoothClipResolvedRadius(
171
+ presentation.clip.topLeftRadius, presentation.clip.radius));
172
+ result.setValueAtIndex(runtime, offset++, SmoothClipResolvedRadius(
173
+ presentation.clip.topRightRadius, presentation.clip.radius));
174
+ result.setValueAtIndex(runtime, offset++, SmoothClipResolvedRadius(
175
+ presentation.clip.bottomRightRadius, presentation.clip.radius));
176
+ result.setValueAtIndex(runtime, offset++, SmoothClipResolvedRadius(
177
+ presentation.clip.bottomLeftRadius, presentation.clip.radius));
178
+ result.setValueAtIndex(
179
+ runtime, offset++, static_cast<double>(presentation.clip.curve));
180
+ result.setValueAtIndex(runtime, offset++, presentation.contentTranslateX);
181
+ result.setValueAtIndex(runtime, offset++, presentation.contentTranslateY);
182
+ result.setValueAtIndex(runtime, offset++, presentation.contentScale);
183
+ result.setValueAtIndex(
184
+ runtime, offset++, presentation.shadow.enabled ? 1.0 : 0.0);
185
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.red);
186
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.green);
187
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.blue);
188
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.alpha);
189
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.offsetX);
190
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.offsetY);
191
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.blurRadius);
192
+ result.setValueAtIndex(runtime, offset++, presentation.shadow.spreadDistance);
70
193
  }
71
194
 
72
195
  Array presentationArray(Runtime &runtime, const Presentation &presentation) {
73
- Array result(runtime, 7);
74
- result.setValueAtIndex(runtime, 0, presentation.clip.x);
75
- result.setValueAtIndex(runtime, 1, presentation.clip.y);
76
- result.setValueAtIndex(runtime, 2, presentation.clip.width);
77
- result.setValueAtIndex(runtime, 3, presentation.clip.height);
78
- result.setValueAtIndex(runtime, 4, presentation.clip.radius);
79
- result.setValueAtIndex(runtime, 5, presentation.contentTranslateX);
80
- result.setValueAtIndex(runtime, 6, presentation.contentTranslateY);
196
+ Array result(runtime, kPresentationStride);
197
+ size_t offset = 0;
198
+ appendPresentation(runtime, result, offset, presentation);
81
199
  return result;
82
200
  }
83
201
 
@@ -85,23 +203,78 @@ Array cancelResultArray(
85
203
  Runtime &runtime,
86
204
  bool handled,
87
205
  const Presentation &presentation) {
88
- Array result(runtime, 8);
89
- // JS checks `values[0] !== 1`, so the handled flag must be a number.
206
+ Array result(runtime, kSnapshotStride);
90
207
  result.setValueAtIndex(runtime, 0, handled ? 1.0 : 0.0);
91
- result.setValueAtIndex(runtime, 1, presentation.clip.x);
92
- result.setValueAtIndex(runtime, 2, presentation.clip.y);
93
- result.setValueAtIndex(runtime, 3, presentation.clip.width);
94
- result.setValueAtIndex(runtime, 4, presentation.clip.height);
95
- result.setValueAtIndex(runtime, 5, presentation.clip.radius);
96
- result.setValueAtIndex(runtime, 6, presentation.contentTranslateX);
97
- result.setValueAtIndex(runtime, 7, presentation.contentTranslateY);
208
+ size_t offset = 1;
209
+ appendPresentation(runtime, result, offset, presentation);
210
+ return result;
211
+ }
212
+
213
+ Array snapshotsArray(
214
+ Runtime &runtime,
215
+ const std::vector<DriverSnapshot> &snapshots) {
216
+ Array result(runtime, snapshots.size() * kSnapshotStride);
217
+ size_t offset = 0;
218
+ for (const DriverSnapshot &snapshot : snapshots) {
219
+ result.setValueAtIndex(runtime, offset++, snapshot.ready ? 1.0 : 0.0);
220
+ const Presentation &presentation = snapshot.presentation;
221
+ appendPresentation(runtime, result, offset, presentation);
222
+ }
98
223
  return result;
99
224
  }
100
225
 
226
+ std::vector<uint64_t> driverIdsFromArray(Runtime &runtime, const Array &values) {
227
+ std::vector<uint64_t> driverIds;
228
+ const size_t length = values.size(runtime);
229
+ driverIds.reserve(length);
230
+ for (size_t index = 0; index < length; index += 1) {
231
+ double value;
232
+ if (!numberAt(runtime, values, index, value) || !validDriverId(value) ||
233
+ std::find(driverIds.begin(), driverIds.end(),
234
+ static_cast<uint64_t>(value)) != driverIds.end()) {
235
+ return {};
236
+ }
237
+ driverIds.push_back(static_cast<uint64_t>(value));
238
+ }
239
+ return driverIds;
240
+ }
241
+
242
+ bool parseGroupMotionEntries(
243
+ Runtime &runtime,
244
+ const Array &values,
245
+ std::vector<GroupMotionEntry> &entries) {
246
+ const size_t length = values.size(runtime);
247
+ if (length < kMotionEntryStride || length % kMotionEntryStride != 0) {
248
+ return false;
249
+ }
250
+ entries.reserve(length / kMotionEntryStride);
251
+ for (size_t index = 0; index < length; index += kMotionEntryStride) {
252
+ double driverId;
253
+ double hasFromValue;
254
+ Presentation from;
255
+ Presentation target;
256
+ if (!numberAt(runtime, values, index, driverId) ||
257
+ !numberAt(runtime, values, index + 1, hasFromValue) ||
258
+ !presentationAt(runtime, values, index + 2, from) ||
259
+ !presentationAt(
260
+ runtime, values, index + 2 + kPresentationStride, target) ||
261
+ !validDriverId(driverId) ||
262
+ (hasFromValue != 0.0 && hasFromValue != 1.0) ||
263
+ !finitePresentation(from) || !finitePresentation(target)) {
264
+ entries.clear();
265
+ return false;
266
+ }
267
+ entries.push_back(
268
+ {static_cast<uint64_t>(driverId), hasFromValue == 1.0, from, target});
269
+ }
270
+ return true;
271
+ }
272
+
101
273
  struct BindingsState {
102
274
  Runtime *runtime = nullptr;
103
275
  std::shared_ptr<CallInvoker> callInvoker;
104
276
  std::unordered_map<int32_t, std::shared_ptr<Function>> listeners;
277
+ std::unordered_map<int32_t, std::shared_ptr<Function>> groupListeners;
105
278
  int32_t nextListenerId = 0;
106
279
  };
107
280
 
@@ -137,15 +310,21 @@ void installBindings(
137
310
  // against — the new one; the TurboModule invalidate hook releases them
138
311
  // while their runtime is still alive, and this is the safety net.
139
312
  state->listeners.clear();
313
+ state->groupListeners.clear();
140
314
  state->runtime = &runtime;
141
315
  state->callInvoker = callInvoker;
142
316
 
143
317
  setCompletionCallback(
144
318
  state.get(),
145
- [state](uint64_t driverId, int32_t animationId, bool finished) {
319
+ [state](
320
+ uint64_t driverId,
321
+ int32_t animationId,
322
+ int32_t completionTag,
323
+ bool finished) {
146
324
  auto invoker = state->callInvoker;
147
325
  if (!invoker) return;
148
- invoker->invokeAsync([state, driverId, animationId, finished]() {
326
+ invoker->invokeAsync(
327
+ [state, driverId, animationId, completionTag, finished]() {
149
328
  if (state->runtime == nullptr) return;
150
329
  Runtime &rt = *state->runtime;
151
330
  std::vector<std::shared_ptr<Function>> snapshot;
@@ -157,30 +336,260 @@ void installBindings(
157
336
  Object result(rt);
158
337
  result.setProperty(rt, "driverId", static_cast<double>(driverId));
159
338
  result.setProperty(rt, "animationId", animationId);
339
+ result.setProperty(rt, "completionTag", completionTag);
160
340
  result.setProperty(rt, "finished", finished);
161
341
  listener->call(rt, std::move(result));
162
342
  }
163
343
  });
164
344
  });
165
345
 
346
+ setGroupCompletionCallback(
347
+ state.get(),
348
+ [state](
349
+ uint64_t controllerId,
350
+ int32_t groupId,
351
+ int32_t completionTag,
352
+ bool finished,
353
+ std::vector<DriverSnapshot> snapshots) {
354
+ auto invoker = state->callInvoker;
355
+ if (!invoker) return;
356
+ invoker->invokeAsync(
357
+ [state, controllerId, groupId, completionTag, finished,
358
+ snapshots = std::move(snapshots)]() {
359
+ if (state->runtime == nullptr) return;
360
+ Runtime &rt = *state->runtime;
361
+ std::vector<std::shared_ptr<Function>> snapshot;
362
+ snapshot.reserve(state->groupListeners.size());
363
+ for (const auto &entry : state->groupListeners) {
364
+ snapshot.push_back(entry.second);
365
+ }
366
+ for (const auto &listener : snapshot) {
367
+ Object result(rt);
368
+ result.setProperty(
369
+ rt, "controllerId", static_cast<double>(controllerId));
370
+ result.setProperty(rt, "groupId", groupId);
371
+ result.setProperty(rt, "completionTag", completionTag);
372
+ result.setProperty(rt, "finished", finished);
373
+ result.setProperty(
374
+ rt, "snapshots", snapshotsArray(rt, snapshots));
375
+ listener->call(rt, std::move(result));
376
+ }
377
+ });
378
+ });
379
+
166
380
  Object bindings(runtime);
167
381
 
168
382
  setHostFunction(
169
- runtime, bindings, "setClipPresentation", 11,
383
+ runtime, bindings, "supportsAutonomousComplexPathAnimation", 0,
384
+ [](Runtime &, const Value &, const Value *, size_t) -> Value {
385
+ // Static complex clipping is available. Autonomous complex-path
386
+ // animation is deliberately gated off until physical-device sampling
387
+ // proves the release tolerance and frame-time requirements.
388
+ return Value(false);
389
+ });
390
+
391
+ setHostFunction(
392
+ runtime, bindings, "beginGroupInteraction", 1,
393
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
394
+ if (count < 1 || !args[0].isObject() ||
395
+ !args[0].getObject(rt).isArray(rt)) {
396
+ return Array(rt, 0);
397
+ }
398
+ Array values = args[0].getObject(rt).getArray(rt);
399
+ const std::vector<uint64_t> driverIds = driverIdsFromArray(rt, values);
400
+ if (driverIds.empty()) return Array(rt, 0);
401
+ return snapshotsArray(rt, beginGroupInteraction(driverIds));
402
+ });
403
+
404
+ setHostFunction(
405
+ runtime, bindings, "snapshotGroup", 1,
406
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
407
+ if (count < 1 || !args[0].isObject() ||
408
+ !args[0].getObject(rt).isArray(rt)) {
409
+ return Array(rt, 0);
410
+ }
411
+ Array values = args[0].getObject(rt).getArray(rt);
412
+ const std::vector<uint64_t> driverIds = driverIdsFromArray(rt, values);
413
+ if (driverIds.empty()) return Array(rt, 0);
414
+ return snapshotsArray(rt, snapshotGroup(driverIds));
415
+ });
416
+
417
+ setHostFunction(
418
+ runtime, bindings, "setClipPresentationBatch", 1,
419
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
420
+ if (count < 1 || !args[0].isObject() ||
421
+ !args[0].getObject(rt).isArray(rt)) {
422
+ return Value(false);
423
+ }
424
+ Array values = args[0].getObject(rt).getArray(rt);
425
+ const size_t length = values.size(rt);
426
+ if (length < kSnapshotStride || length % kSnapshotStride != 0) {
427
+ return Value(false);
428
+ }
429
+ std::vector<BatchEntry> entries;
430
+ entries.reserve(length / kSnapshotStride);
431
+ for (size_t index = 0; index < length; index += kSnapshotStride) {
432
+ double driverId;
433
+ Presentation presentation;
434
+ if (!numberAt(rt, values, index, driverId) ||
435
+ !presentationAt(rt, values, index + 1, presentation) ||
436
+ !validDriverId(driverId) || !finitePresentation(presentation)) {
437
+ return Value(false);
438
+ }
439
+ entries.push_back(
440
+ {static_cast<uint64_t>(driverId), presentation});
441
+ }
442
+ return Value(setPresentationBatch(entries));
443
+ });
444
+
445
+ setHostFunction(
446
+ runtime, bindings, "animateTimingGroup", 10,
447
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
448
+ if (count < 10 || !args[1].isObject() ||
449
+ !args[1].getObject(rt).isArray(rt)) {
450
+ return Value(0);
451
+ }
452
+ const double controllerId = args[0].asNumber();
453
+ Array values = args[1].getObject(rt).getArray(rt);
454
+ std::vector<GroupMotionEntry> entries;
455
+ const TimingAnimation animation{
456
+ args[2].asNumber(), args[3].asNumber(), args[4].asNumber(),
457
+ args[5].asNumber(), args[6].asNumber(),
458
+ static_cast<int32_t>(args[7].asNumber())};
459
+ if (!validDriverId(controllerId) ||
460
+ !parseGroupMotionEntries(rt, values, entries) ||
461
+ !std::isfinite(animation.durationMs) ||
462
+ !std::isfinite(animation.controlPoint1X) ||
463
+ !std::isfinite(animation.controlPoint1Y) ||
464
+ !std::isfinite(animation.controlPoint2X) ||
465
+ !std::isfinite(animation.controlPoint2Y) ||
466
+ !std::isfinite(args[8].asNumber())) {
467
+ return Value(0);
468
+ }
469
+ return Value(animateTimingGroup(
470
+ static_cast<uint64_t>(controllerId),
471
+ std::move(entries),
472
+ animation,
473
+ static_cast<int32_t>(args[8].asNumber()),
474
+ startStampArg(args, count, 9)));
475
+ });
476
+
477
+ setHostFunction(
478
+ runtime, bindings, "animateSpringGroup", 10,
170
479
  [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
171
- if (count < 9) return Value::undefined();
480
+ if (count < 10 || !args[1].isObject() ||
481
+ !args[1].getObject(rt).isArray(rt)) {
482
+ return Value(0);
483
+ }
484
+ const double controllerId = args[0].asNumber();
485
+ Array values = args[1].getObject(rt).getArray(rt);
486
+ std::vector<GroupMotionEntry> entries;
487
+ const SpringAnimation animation{
488
+ args[2].asNumber(), args[3].asNumber(), args[4].asNumber(),
489
+ args[5].asNumber(), false,
490
+ static_cast<int32_t>(args[7].asNumber()),
491
+ args[6].asNumber()};
492
+ if (!validDriverId(controllerId) ||
493
+ !parseGroupMotionEntries(rt, values, entries) ||
494
+ !std::isfinite(animation.mass) ||
495
+ !std::isfinite(animation.stiffness) ||
496
+ !std::isfinite(animation.damping) ||
497
+ !std::isfinite(animation.initialVelocity) || animation.mass <= 0 ||
498
+ !std::isfinite(animation.energyThreshold) ||
499
+ !std::isfinite(args[8].asNumber()) || animation.stiffness <= 0 ||
500
+ animation.damping < 0 || animation.energyThreshold <= 0) {
501
+ return Value(0);
502
+ }
503
+ return Value(animateSpringGroup(
504
+ static_cast<uint64_t>(controllerId),
505
+ std::move(entries),
506
+ animation,
507
+ static_cast<int32_t>(args[8].asNumber()),
508
+ startStampArg(args, count, 9)));
509
+ });
510
+
511
+ setHostFunction(
512
+ runtime, bindings, "cancelAnimationGroup", 2,
513
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
514
+ if (count < 2 || !args[0].isNumber() || !args[1].isNumber()) {
515
+ return Array(rt, 0);
516
+ }
517
+ const double groupId = args[0].asNumber();
518
+ const double behavior = args[1].asNumber();
519
+ if (!std::isfinite(groupId) || groupId <= 0 ||
520
+ std::floor(groupId) != groupId ||
521
+ (behavior != static_cast<double>(GroupCancelBehavior::Freeze) &&
522
+ behavior != static_cast<double>(GroupCancelBehavior::Finish))) {
523
+ return Array(rt, 0);
524
+ }
525
+ return snapshotsArray(
526
+ rt,
527
+ cancelAnimationGroup(
528
+ static_cast<int32_t>(groupId),
529
+ static_cast<GroupCancelBehavior>(
530
+ static_cast<int32_t>(behavior))));
531
+ });
532
+
533
+ setHostFunction(
534
+ runtime, bindings, "setClipPresentation", 4,
535
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
536
+ if (count < 4 || !args[0].isNumber() || !args[1].isObject() ||
537
+ !args[1].getObject(rt).isArray(rt)) {
538
+ return Value::undefined();
539
+ }
540
+ const double driverId = args[0].asNumber();
541
+ const Array values = args[1].getObject(rt).getArray(rt);
542
+ Presentation presentation;
543
+ if (values.size(rt) == kPresentationStride &&
544
+ validDriverId(driverId) &&
545
+ presentationAt(rt, values, 0, presentation)) {
546
+ setPresentation(
547
+ static_cast<uint64_t>(driverId), presentation,
548
+ boolArg(args, count, 2), boolArg(args, count, 3), true);
549
+ }
550
+ return Value::undefined();
551
+ });
552
+
553
+ setHostFunction(
554
+ runtime, bindings, "setClipFrameScalars", 22,
555
+ [](Runtime &, const Value &, const Value *args, size_t count) -> Value {
556
+ if (count < 22) return Value::undefined();
172
557
  const double driverId = args[0].asNumber();
173
- const Presentation presentation = presentationFromArgs(args, 1);
174
- const bool takeOwnership = boolArg(args, count, 8);
175
- const bool overridePendingAnimation = boolArg(args, count, 9);
176
- // Optional trailing flag; absent means record (the pre-flag
177
- // behavior), so only an explicit false skips the velocity sample.
178
- const bool recordVelocity =
179
- !(count > 10 && args[10].isBool() && !args[10].getBool());
180
- if (validDriverId(driverId) && finitePresentation(presentation)) {
558
+ Geometry geometry{
559
+ args[1].asNumber(), args[2].asNumber(), args[3].asNumber(),
560
+ args[4].asNumber(), 0};
561
+ geometry.topLeftRadius = args[5].asNumber();
562
+ geometry.topRightRadius = args[6].asNumber();
563
+ geometry.bottomRightRadius = args[7].asNumber();
564
+ geometry.bottomLeftRadius = args[8].asNumber();
565
+ geometry.curve = static_cast<ClipCurve>(
566
+ static_cast<int32_t>(args[9].asNumber()));
567
+ if (geometry.topLeftRadius == geometry.topRightRadius &&
568
+ geometry.topLeftRadius == geometry.bottomRightRadius &&
569
+ geometry.topLeftRadius == geometry.bottomLeftRadius) {
570
+ geometry.radius = geometry.topLeftRadius;
571
+ }
572
+ const Presentation presentation{
573
+ geometry,
574
+ args[10].asNumber(),
575
+ args[11].asNumber(),
576
+ args[12].asNumber(),
577
+ {
578
+ boolArg(args, count, 13),
579
+ args[14].asNumber(),
580
+ args[15].asNumber(),
581
+ args[16].asNumber(),
582
+ args[17].asNumber(),
583
+ args[18].asNumber(),
584
+ args[19].asNumber(),
585
+ args[20].asNumber(),
586
+ args[21].asNumber(),
587
+ }};
588
+ if (validDriverId(driverId) && validCurve(args[9].asNumber()) &&
589
+ finitePresentation(presentation)) {
181
590
  setPresentation(
182
- static_cast<uint64_t>(driverId), presentation, takeOwnership,
183
- overridePendingAnimation, recordVelocity);
591
+ static_cast<uint64_t>(driverId), presentation,
592
+ true, false, true);
184
593
  }
185
594
  return Value::undefined();
186
595
  });
@@ -196,104 +605,91 @@ void installBindings(
196
605
  });
197
606
 
198
607
  setHostFunction(
199
- runtime, bindings, "animateTiming", 23,
608
+ runtime, bindings, "snapshotCurrent", 1,
200
609
  [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
201
- if (count < 22) return Value(0);
202
- const double driverId = args[0].asNumber();
203
- const Presentation start = presentationFromArgs(args, 2);
204
- const Presentation target = presentationFromArgs(args, 9);
205
- const TimingAnimation animation{
206
- args[16].asNumber(), args[17].asNumber(), args[18].asNumber(),
207
- args[19].asNumber(), args[20].asNumber(),
208
- static_cast<int32_t>(args[21].asNumber())};
209
- if (!validDriverId(driverId) || !finitePresentation(start) ||
210
- !finitePresentation(target) ||
211
- !std::isfinite(animation.durationMs)) {
212
- return Value(0);
610
+ if (count < 1 || !validDriverId(args[0].asNumber())) {
611
+ return presentationArray(rt, {{0, 0, 0, 0, 0}, 0, 0});
213
612
  }
214
- return Value(animateTiming(
215
- static_cast<uint64_t>(driverId),
216
- {boolArg(args, count, 1), start, startStampArg(args, count, 22)},
217
- target,
218
- animation));
613
+ return presentationArray(
614
+ rt,
615
+ snapshotCurrentAndroid(
616
+ static_cast<uint64_t>(args[0].asNumber())));
219
617
  });
220
618
 
221
619
  setHostFunction(
222
- runtime, bindings, "animateSpring", 23,
620
+ runtime, bindings, "animateTiming", 11,
223
621
  [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
224
- if (count < 22) return Value(0);
622
+ if (count < 10 || !args[0].isNumber() || !args[1].isObject() ||
623
+ !args[1].getObject(rt).isArray(rt) || !args[2].isObject() ||
624
+ !args[2].getObject(rt).isArray(rt) || !args[9].isNumber() ||
625
+ !std::isfinite(args[9].asNumber())) {
626
+ return Value(0);
627
+ }
225
628
  const double driverId = args[0].asNumber();
226
- const Presentation start = presentationFromArgs(args, 2);
227
- const Presentation target = presentationFromArgs(args, 9);
228
- const SpringAnimation animation{
229
- args[16].asNumber(), args[17].asNumber(), args[18].asNumber(),
230
- args[19].asNumber(), boolArg(args, count, 20),
231
- static_cast<int32_t>(args[21].asNumber())};
232
- if (!validDriverId(driverId) || !finitePresentation(start) ||
233
- !finitePresentation(target) || !std::isfinite(animation.mass) ||
234
- !std::isfinite(animation.stiffness) ||
235
- !std::isfinite(animation.damping) ||
236
- !std::isfinite(animation.initialVelocity) ||
237
- animation.mass <= 0 || animation.stiffness <= 0 ||
238
- animation.damping < 0) {
629
+ const Array startValues = args[1].getObject(rt).getArray(rt);
630
+ const Array targetValues = args[2].getObject(rt).getArray(rt);
631
+ const bool hasInteractiveStart = startValues.size(rt) != 0;
632
+ Presentation start{};
633
+ Presentation target;
634
+ const TimingAnimation animation{
635
+ args[3].asNumber(), args[4].asNumber(), args[5].asNumber(),
636
+ args[6].asNumber(), args[7].asNumber(),
637
+ static_cast<int32_t>(args[8].asNumber())};
638
+ if (!validDriverId(driverId) ||
639
+ (hasInteractiveStart &&
640
+ (startValues.size(rt) != kPresentationStride ||
641
+ !presentationAt(rt, startValues, 0, start))) ||
642
+ targetValues.size(rt) != kPresentationStride ||
643
+ !presentationAt(rt, targetValues, 0, target) ||
644
+ !std::isfinite(animation.durationMs)) {
239
645
  return Value(0);
240
646
  }
241
- return Value(animateSpring(
647
+ return Value(animateTiming(
242
648
  static_cast<uint64_t>(driverId),
243
- {boolArg(args, count, 1), start, startStampArg(args, count, 22)},
649
+ {hasInteractiveStart, start, startStampArg(args, count, 10)},
244
650
  target,
245
- animation));
651
+ animation,
652
+ static_cast<int32_t>(args[9].asNumber())));
246
653
  });
247
654
 
248
655
  setHostFunction(
249
- runtime, bindings, "animateKeyframes", 20,
656
+ runtime, bindings, "animateSpring", 11,
250
657
  [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
251
- if (count < 19 || !args[17].isObject() ||
252
- !args[17].getObject(rt).isArray(rt)) {
658
+ if (count < 10 || !args[0].isNumber() || !args[1].isObject() ||
659
+ !args[1].getObject(rt).isArray(rt) || !args[2].isObject() ||
660
+ !args[2].getObject(rt).isArray(rt) || !args[9].isNumber() ||
661
+ !std::isfinite(args[9].asNumber())) {
253
662
  return Value(0);
254
663
  }
255
664
  const double driverId = args[0].asNumber();
256
- const Presentation start = presentationFromArgs(args, 2);
257
- const Presentation target = presentationFromArgs(args, 9);
258
- const double durationMs = args[16].asNumber();
259
- const int32_t reduceMotion = static_cast<int32_t>(args[18].asNumber());
260
- Array frames = args[17].getObject(rt).getArray(rt);
261
- const size_t length = frames.size(rt);
262
- if (!validDriverId(driverId) || !finitePresentation(start) ||
263
- !finitePresentation(target) || !std::isfinite(durationMs) ||
264
- length < 16 || length % 8 != 0) {
265
- return Value(0);
266
- }
267
- std::vector<Keyframe> keyframes;
268
- keyframes.reserve(length / 8);
269
- double previousOffset = -1;
270
- for (size_t index = 0; index < length; index += 8) {
271
- const double offset = frames.getValueAtIndex(rt, index).asNumber();
272
- const Presentation frame{
273
- {frames.getValueAtIndex(rt, index + 1).asNumber(),
274
- frames.getValueAtIndex(rt, index + 2).asNumber(),
275
- frames.getValueAtIndex(rt, index + 3).asNumber(),
276
- frames.getValueAtIndex(rt, index + 4).asNumber(),
277
- frames.getValueAtIndex(rt, index + 5).asNumber()},
278
- frames.getValueAtIndex(rt, index + 6).asNumber(),
279
- frames.getValueAtIndex(rt, index + 7).asNumber()};
280
- if (!std::isfinite(offset) || offset < 0 || offset > 1 ||
281
- offset <= previousOffset || !finitePresentation(frame)) {
282
- return Value(0);
283
- }
284
- previousOffset = offset;
285
- keyframes.push_back({offset, frame});
286
- }
287
- if (keyframes.front().offset != 0 || keyframes.back().offset != 1) {
665
+ const Array startValues = args[1].getObject(rt).getArray(rt);
666
+ const Array targetValues = args[2].getObject(rt).getArray(rt);
667
+ const bool hasInteractiveStart = startValues.size(rt) != 0;
668
+ Presentation start{};
669
+ Presentation target;
670
+ const SpringAnimation animation{
671
+ args[3].asNumber(), args[4].asNumber(), args[5].asNumber(),
672
+ args[6].asNumber(), boolArg(args, count, 7),
673
+ static_cast<int32_t>(args[8].asNumber())};
674
+ if (!validDriverId(driverId) ||
675
+ (hasInteractiveStart &&
676
+ (startValues.size(rt) != kPresentationStride ||
677
+ !presentationAt(rt, startValues, 0, start))) ||
678
+ targetValues.size(rt) != kPresentationStride ||
679
+ !presentationAt(rt, targetValues, 0, target) ||
680
+ !std::isfinite(animation.mass) ||
681
+ !std::isfinite(animation.stiffness) ||
682
+ !std::isfinite(animation.damping) ||
683
+ !std::isfinite(animation.initialVelocity) || animation.mass <= 0 ||
684
+ animation.stiffness <= 0 || animation.damping < 0) {
288
685
  return Value(0);
289
686
  }
290
- return Value(animateKeyframes(
687
+ return Value(animateSpring(
291
688
  static_cast<uint64_t>(driverId),
292
- {boolArg(args, count, 1), start, startStampArg(args, count, 19)},
689
+ {hasInteractiveStart, start, startStampArg(args, count, 10)},
293
690
  target,
294
- durationMs,
295
- std::move(keyframes),
296
- reduceMotion));
691
+ animation,
692
+ static_cast<int32_t>(args[9].asNumber())));
297
693
  });
298
694
 
299
695
  setHostFunction(
@@ -308,13 +704,15 @@ void installBindings(
308
704
  runtime, bindings, "cancelAnimation", 3,
309
705
  [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
310
706
  if (count < 3 || !validDriverId(args[0].asNumber())) {
311
- return cancelResultArray(rt, false, {{0, 0, 0, 0, 0}, 0, 0});
707
+ return cancelResultArray(
708
+ rt, false, {{0, 0, 0, 0, 0}, 0, 0});
312
709
  }
313
710
  const CancelResult result = cancelAnimation(
314
711
  static_cast<uint64_t>(args[0].asNumber()),
315
712
  static_cast<int32_t>(args[1].asNumber()),
316
713
  static_cast<int32_t>(args[2].asNumber()) == 1);
317
- return cancelResultArray(rt, result.handled, result.presentation);
714
+ return cancelResultArray(
715
+ rt, result.handled, result.presentation);
318
716
  });
319
717
 
320
718
  setHostFunction(
@@ -349,6 +747,29 @@ void installBindings(
349
747
  return subscription;
350
748
  });
351
749
 
750
+ setHostFunction(
751
+ runtime, bindings, "onClipGroupAnimationComplete", 1,
752
+ [state](Runtime &rt, const Value &, const Value *args, size_t count)
753
+ -> Value {
754
+ if (count < 1 || !args[0].isObject() ||
755
+ !args[0].getObject(rt).isFunction(rt)) {
756
+ return Value::undefined();
757
+ }
758
+ auto listener = std::make_shared<Function>(
759
+ args[0].getObject(rt).getFunction(rt));
760
+ const int32_t listenerId = ++state->nextListenerId;
761
+ state->groupListeners.emplace(listenerId, std::move(listener));
762
+ Object subscription(rt);
763
+ setHostFunction(
764
+ rt, subscription, "remove", 0,
765
+ [state, listenerId](
766
+ Runtime &, const Value &, const Value *, size_t) -> Value {
767
+ state->groupListeners.erase(listenerId);
768
+ return Value::undefined();
769
+ });
770
+ return subscription;
771
+ });
772
+
352
773
  runtime.global().setProperty(runtime, "__SmoothClipView", bindings);
353
774
  }
354
775
 
@@ -359,7 +780,9 @@ void invalidateBindings() {
359
780
  // own runtime.
360
781
  auto state = bindingsState();
361
782
  clearCompletionCallback(state.get());
783
+ clearGroupCompletionCallback(state.get());
362
784
  state->listeners.clear();
785
+ state->groupListeners.clear();
363
786
  state->runtime = nullptr;
364
787
  state->callInvoker = nullptr;
365
788
  }
@@ -388,19 +811,70 @@ void nativeRegisterView(
388
811
  jdouble y,
389
812
  jdouble width,
390
813
  jdouble height,
391
- jdouble radius,
814
+ jdouble topLeftRadius,
815
+ jdouble topRightRadius,
816
+ jdouble bottomRightRadius,
817
+ jdouble bottomLeftRadius,
818
+ jint curveCode,
392
819
  jdouble contentTranslateX,
393
820
  jdouble contentTranslateY,
821
+ jdouble contentScale,
822
+ jboolean shadowEnabled,
823
+ jdouble shadowRed,
824
+ jdouble shadowGreen,
825
+ jdouble shadowBlue,
826
+ jdouble shadowAlpha,
827
+ jdouble shadowOffsetX,
828
+ jdouble shadowOffsetY,
829
+ jdouble shadowBlurRadius,
830
+ jdouble shadowSpreadDistance,
394
831
  jdouble density,
395
832
  jdouble widthPx,
396
833
  jdouble heightPx,
397
834
  jboolean lifecycleVisible) {
398
- if (driverId <= 0 || !std::isfinite(driverId)) return;
835
+ if (driverId <= 0 || !std::isfinite(driverId) || !std::isfinite(x) ||
836
+ !std::isfinite(y) || !std::isfinite(width) || !std::isfinite(height) ||
837
+ !std::isfinite(topLeftRadius) || !std::isfinite(topRightRadius) ||
838
+ !std::isfinite(bottomRightRadius) || !std::isfinite(bottomLeftRadius) ||
839
+ !std::isfinite(contentTranslateX) ||
840
+ !std::isfinite(contentTranslateY) || !std::isfinite(contentScale) ||
841
+ !std::isfinite(shadowRed) || !std::isfinite(shadowGreen) ||
842
+ !std::isfinite(shadowBlue) || !std::isfinite(shadowAlpha) ||
843
+ !std::isfinite(shadowOffsetX) ||
844
+ !std::isfinite(shadowOffsetY) || !std::isfinite(shadowBlurRadius) ||
845
+ !std::isfinite(shadowSpreadDistance) || contentScale <= 0 ||
846
+ shadowRed < 0 || shadowRed > 1 || shadowGreen < 0 || shadowGreen > 1 ||
847
+ shadowBlue < 0 || shadowBlue > 1 || shadowAlpha < 0 || shadowAlpha > 1 ||
848
+ shadowBlurRadius < 0 ||
849
+ (curveCode != static_cast<jint>(smoothclip::ClipCurve::Circular) &&
850
+ curveCode != static_cast<jint>(smoothclip::ClipCurve::Continuous))) {
851
+ return;
852
+ }
853
+ smoothclip::Geometry geometry{x, y, width, height, 0.0};
854
+ geometry.topLeftRadius = topLeftRadius;
855
+ geometry.topRightRadius = topRightRadius;
856
+ geometry.bottomRightRadius = bottomRightRadius;
857
+ geometry.bottomLeftRadius = bottomLeftRadius;
858
+ geometry.curve = static_cast<smoothclip::ClipCurve>(curveCode);
859
+ if (topLeftRadius == topRightRadius && topLeftRadius == bottomRightRadius &&
860
+ topLeftRadius == bottomLeftRadius) {
861
+ geometry.radius = topLeftRadius;
862
+ }
863
+ smoothclip::Shadow shadow{
864
+ shadowEnabled != 0,
865
+ shadowRed,
866
+ shadowGreen,
867
+ shadowBlue,
868
+ shadowAlpha,
869
+ shadowOffsetX,
870
+ shadowOffsetY,
871
+ shadowBlurRadius,
872
+ shadowSpreadDistance};
399
873
  smoothclip::registerViewAndroid(
400
874
  static_cast<uint64_t>(driverId),
401
875
  view,
402
876
  smoothclip::Presentation{
403
- {x, y, width, height, radius}, contentTranslateX, contentTranslateY},
877
+ geometry, contentTranslateX, contentTranslateY, contentScale, shadow},
404
878
  density,
405
879
  widthPx,
406
880
  heightPx,
@@ -427,6 +901,13 @@ void nativeUnregisterView(
427
901
  smoothclip::unregisterViewAndroid(static_cast<uint64_t>(driverId), view);
428
902
  }
429
903
 
904
+ void nativeDestroyDriver(
905
+ jni::alias_ref<jni::JObject>,
906
+ jdouble driverId) {
907
+ if (driverId <= 0 || !std::isfinite(driverId)) return;
908
+ smoothclip::destroyDriver(static_cast<uint64_t>(driverId));
909
+ }
910
+
430
911
  void nativeSetViewLifecycleVisibility(
431
912
  jni::alias_ref<jni::JObject>,
432
913
  jdouble driverId,
@@ -457,6 +938,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
457
938
  makeNativeMethod(
458
939
  "nativeSetViewHostGeometry", nativeSetViewHostGeometry),
459
940
  makeNativeMethod("nativeUnregisterView", nativeUnregisterView),
941
+ makeNativeMethod("nativeDestroyDriver", nativeDestroyDriver),
460
942
  makeNativeMethod(
461
943
  "nativeSetViewLifecycleVisibility",
462
944
  nativeSetViewLifecycleVisibility),