react-native-smooth-clip-view 0.4.1 → 0.4.3

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 (47) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +92 -5
  3. package/android/src/main/cpp/SmoothClipAndroid.h +2 -1
  4. package/android/src/main/cpp/SmoothClipBindings.cpp +22 -8
  5. package/android/src/main/cpp/SmoothClipRegistry.cpp +85 -10
  6. package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +23 -0
  7. package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +8 -0
  8. package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +39 -1
  9. package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +124 -32
  10. package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +16 -0
  11. package/cpp/SmoothClipAnimationCurve.h +21 -10
  12. package/cpp/SmoothClipRegistry.h +2 -0
  13. package/cpp/SmoothClipSharedGeometry.h +20 -0
  14. package/cpp/SmoothClipVelocityTracker.h +6 -0
  15. package/ios/SmoothClipRegistry.mm +173 -17
  16. package/ios/SmoothClipTurboModule.cpp +17 -6
  17. package/ios/SmoothClipTurboModule.h +3 -1
  18. package/ios/SmoothClipView.mm +408 -20
  19. package/lib/module/NativeSmoothClipModule.js.map +1 -1
  20. package/lib/module/SmoothClipView.ios.js +3 -0
  21. package/lib/module/SmoothClipView.ios.js.map +1 -1
  22. package/lib/module/SmoothClipViewNativeComponent.ts +5 -1
  23. package/lib/module/geometry.js +22 -2
  24. package/lib/module/geometry.js.map +1 -1
  25. package/lib/module/presentationCodec.js +5 -3
  26. package/lib/module/presentationCodec.js.map +1 -1
  27. package/lib/typescript/src/NativeSmoothClipModule.d.ts +1 -1
  28. package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
  29. package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
  30. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +3 -1
  31. package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
  32. package/lib/typescript/src/controllerTypes.d.ts +5 -1
  33. package/lib/typescript/src/controllerTypes.d.ts.map +1 -1
  34. package/lib/typescript/src/geometry.d.ts +9 -0
  35. package/lib/typescript/src/geometry.d.ts.map +1 -1
  36. package/lib/typescript/src/index.d.ts +1 -1
  37. package/lib/typescript/src/index.d.ts.map +1 -1
  38. package/lib/typescript/src/presentationCodec.d.ts +1 -1
  39. package/lib/typescript/src/presentationCodec.d.ts.map +1 -1
  40. package/package.json +2 -1
  41. package/src/NativeSmoothClipModule.ts +3 -1
  42. package/src/SmoothClipView.ios.tsx +3 -0
  43. package/src/SmoothClipViewNativeComponent.ts +5 -1
  44. package/src/controllerTypes.ts +5 -1
  45. package/src/geometry.ts +35 -0
  46. package/src/index.ts +1 -0
  47. package/src/presentationCodec.ts +8 -2
@@ -44,6 +44,12 @@
44
44
  animationId:(int32_t)animationId
45
45
  sharedBeginTime:(CFTimeInterval)sharedBeginTime;
46
46
  - (void)smoothClipCancelAnimationUsingTarget:(BOOL)useTarget;
47
+ - (BOOL)smoothClipPauseSpringAnimation:(int32_t)animationId
48
+ atMediaTime:(CFTimeInterval)mediaTime;
49
+ - (BOOL)smoothClipResumeSpringAnimation:(int32_t)animationId
50
+ atMediaTime:(CFTimeInterval)mediaTime
51
+ catchUpBy:(CFTimeInterval)catchUp;
52
+ - (BOOL)smoothClipHasInstalledAnimation:(int32_t)animationId;
47
53
  - (BOOL)smoothClipIsJoinable;
48
54
  - (BOOL)smoothClipCanDisplay;
49
55
  - (double)smoothClipInheritedVelocityToPresentation:
@@ -76,6 +82,8 @@ struct ActiveAnimation {
76
82
  // False while a pre-ready run waits for its first displayable host. Host
77
83
  // loss after the run starts finishes it at the target.
78
84
  bool started = false;
85
+ bool exposedToInactivity = false;
86
+ bool needsForegroundReconciliation = false;
79
87
  };
80
88
 
81
89
  struct DriverState {
@@ -155,6 +163,11 @@ bool &applicationActiveState() {
155
163
  return active;
156
164
  }
157
165
 
166
+ std::optional<CFTimeInterval> &applicationInactiveSince() {
167
+ static std::optional<CFTimeInterval> value;
168
+ return value;
169
+ }
170
+
158
171
  CompletionSink &completionSink() {
159
172
  static CompletionSink sink;
160
173
  return sink;
@@ -185,6 +198,113 @@ std::vector<DriverSnapshot> cancelGroupInternal(
185
198
  void tryStartGroup(int32_t groupId);
186
199
  void finishGroupForHostLoss(int32_t groupId);
187
200
  DriverSnapshot snapshotForDriver(uint64_t driverId);
201
+ SmoothClipView *viewForKey(ViewKey key);
202
+
203
+ bool animationNeedsForegroundReconciliation(const DriverState &state) {
204
+ if (!state.animation.has_value()) return false;
205
+ const ActiveAnimation &animation = *state.animation;
206
+ if (animation.needsForegroundReconciliation) return true;
207
+ if (!animation.exposedToInactivity) return false;
208
+ if (animation.participants.empty()) return true;
209
+ for (const ViewKey key : animation.participants) {
210
+ if (![viewForKey(key)
211
+ smoothClipHasInstalledAnimation:animation.animationId]) {
212
+ return true;
213
+ }
214
+ }
215
+ return false;
216
+ }
217
+
218
+ bool groupNeedsForegroundReconciliation(int32_t groupId) {
219
+ const auto groupIterator = groupRegistry().find(groupId);
220
+ if (groupIterator == groupRegistry().end()) return false;
221
+ for (const uint64_t driverId : groupIterator->second.driverIds) {
222
+ const auto stateIterator = registry().find(driverId);
223
+ if (stateIterator != registry().end() &&
224
+ stateIterator->second.animation.has_value() &&
225
+ stateIterator->second.animation->groupId == groupId &&
226
+ animationNeedsForegroundReconciliation(stateIterator->second)) {
227
+ return true;
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+
233
+ bool pauseSpringForInactivity(
234
+ DriverState &state,
235
+ CFTimeInterval mediaTime) {
236
+ if (!state.animation.has_value() || !state.animation->started ||
237
+ state.animation->kind != AnimationKind::Spring) {
238
+ return true;
239
+ }
240
+ ActiveAnimation &animation = *state.animation;
241
+ if (animation.participants.empty()) {
242
+ animation.needsForegroundReconciliation = true;
243
+ return false;
244
+ }
245
+ bool paused = true;
246
+ for (const ViewKey key : animation.participants) {
247
+ paused = [viewForKey(key)
248
+ smoothClipPauseSpringAnimation:animation.animationId
249
+ atMediaTime:mediaTime] && paused;
250
+ }
251
+ if (!paused) animation.needsForegroundReconciliation = true;
252
+ return paused;
253
+ }
254
+
255
+ bool resumeSpringAfterInactivity(
256
+ DriverState &state,
257
+ CFTimeInterval mediaTime,
258
+ CFTimeInterval catchUp) {
259
+ if (!state.animation.has_value() || !state.animation->started ||
260
+ state.animation->kind != AnimationKind::Spring) {
261
+ return true;
262
+ }
263
+ ActiveAnimation &animation = *state.animation;
264
+ bool resumed = !animation.participants.empty();
265
+ for (const ViewKey key : animation.participants) {
266
+ resumed = [viewForKey(key)
267
+ smoothClipResumeSpringAnimation:animation.animationId
268
+ atMediaTime:mediaTime
269
+ catchUpBy:catchUp] && resumed;
270
+ }
271
+ if (!resumed) animation.needsForegroundReconciliation = true;
272
+ return resumed;
273
+ }
274
+
275
+ bool resumeGroupSpringsAfterInactivity(
276
+ int32_t groupId,
277
+ CFTimeInterval mediaTime,
278
+ CFTimeInterval catchUp) {
279
+ const auto groupIterator = groupRegistry().find(groupId);
280
+ if (groupIterator == groupRegistry().end()) return false;
281
+ bool resumed = true;
282
+ for (const uint64_t driverId : groupIterator->second.driverIds) {
283
+ const auto stateIterator = registry().find(driverId);
284
+ if (stateIterator == registry().end() ||
285
+ !stateIterator->second.animation.has_value() ||
286
+ stateIterator->second.animation->groupId != groupId) {
287
+ return false;
288
+ }
289
+ resumed = resumeSpringAfterInactivity(
290
+ stateIterator->second, mediaTime, catchUp) && resumed;
291
+ }
292
+ return resumed;
293
+ }
294
+
295
+ void clearGroupInactivityExposure(int32_t groupId) {
296
+ const auto groupIterator = groupRegistry().find(groupId);
297
+ if (groupIterator == groupRegistry().end()) return;
298
+ for (const uint64_t driverId : groupIterator->second.driverIds) {
299
+ const auto stateIterator = registry().find(driverId);
300
+ if (stateIterator != registry().end() &&
301
+ stateIterator->second.animation.has_value() &&
302
+ stateIterator->second.animation->groupId == groupId) {
303
+ stateIterator->second.animation->exposedToInactivity = false;
304
+ stateIterator->second.animation->needsForegroundReconciliation = false;
305
+ }
306
+ }
307
+ }
188
308
 
189
309
  Presentation unavailablePresentation() {
190
310
  // Non-finite geometry makes the JS side fall back to its current value
@@ -647,6 +767,7 @@ void tryStartGroup(int32_t groupId) {
647
767
  if (stateIterator == registry().end() ||
648
768
  !stateIterator->second.animation.has_value() ||
649
769
  stateIterator->second.animation->groupId != groupId ||
770
+ stateIterator->second.animation->started ||
650
771
  !driverReady(stateIterator->second)) {
651
772
  return;
652
773
  }
@@ -689,19 +810,14 @@ bool applicationIsActive() {
689
810
  void applicationWillResignActive() {
690
811
  NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
691
812
  if (!applicationActiveState()) return;
813
+ const CFTimeInterval mediaTime = CACurrentMediaTime();
692
814
  applicationActiveState() = false;
693
- std::vector<int32_t> groupIds;
694
- groupIds.reserve(groupRegistry().size());
695
- for (const auto &[groupId, group] : groupRegistry()) {
696
- (void)group;
697
- groupIds.push_back(groupId);
698
- }
699
- for (const int32_t groupId : groupIds) {
700
- finishGroupForHostLoss(groupId);
701
- }
815
+ applicationInactiveSince() = mediaTime;
702
816
  for (auto &[driverId, state] : registry()) {
703
- if (!state.animation.has_value() || state.animation->groupId == 0) {
704
- finishStandaloneAtTarget(driverId, state);
817
+ (void)driverId;
818
+ if (state.animation.has_value() && state.animation->started) {
819
+ state.animation->exposedToInactivity = true;
820
+ pauseSpringForInactivity(state, mediaTime);
705
821
  }
706
822
  }
707
823
  }
@@ -709,6 +825,14 @@ void applicationWillResignActive() {
709
825
  void applicationDidBecomeActive() {
710
826
  NSCAssert(NSThread.isMainThread, @"SmoothClip registry is main-thread only");
711
827
  if (applicationActiveState()) return;
828
+ const CFTimeInterval mediaTime = CACurrentMediaTime();
829
+ const CFTimeInterval inactiveDuration = applicationInactiveSince().has_value()
830
+ ? std::max((CFTimeInterval)0,
831
+ mediaTime - *applicationInactiveSince())
832
+ : 0;
833
+ const CFTimeInterval springCatchUp =
834
+ std::min((CFTimeInterval)0.064, inactiveDuration);
835
+ applicationInactiveSince().reset();
712
836
  applicationActiveState() = true;
713
837
  std::vector<int32_t> groupIds;
714
838
  groupIds.reserve(groupRegistry().size());
@@ -717,13 +841,35 @@ void applicationDidBecomeActive() {
717
841
  groupIds.push_back(groupId);
718
842
  }
719
843
  for (const int32_t groupId : groupIds) {
720
- tryStartGroup(groupId);
844
+ bool needsReconciliation = groupNeedsForegroundReconciliation(groupId);
845
+ if (!needsReconciliation) {
846
+ needsReconciliation = !resumeGroupSpringsAfterInactivity(
847
+ groupId, mediaTime, springCatchUp);
848
+ }
849
+ if (needsReconciliation) {
850
+ cancelGroupInternal(groupId, GroupCancelBehavior::Finish, true);
851
+ } else {
852
+ clearGroupInactivityExposure(groupId);
853
+ tryStartGroup(groupId);
854
+ }
721
855
  }
722
856
  for (auto &[driverId, state] : registry()) {
723
- (void)driverId;
724
- if (state.animation.has_value() && !state.animation->started &&
725
- state.animation->groupId == 0 && anyDisplayableView(state)) {
726
- startPendingAnimation(state);
857
+ if (!state.animation.has_value() || state.animation->groupId != 0) {
858
+ continue;
859
+ }
860
+ bool needsReconciliation = animationNeedsForegroundReconciliation(state);
861
+ if (!needsReconciliation) {
862
+ needsReconciliation = !resumeSpringAfterInactivity(
863
+ state, mediaTime, springCatchUp);
864
+ }
865
+ if (needsReconciliation) {
866
+ finishStandaloneAtTarget(driverId, state);
867
+ } else {
868
+ state.animation->exposedToInactivity = false;
869
+ state.animation->needsForegroundReconciliation = false;
870
+ if (!state.animation->started && anyDisplayableView(state)) {
871
+ startPendingAnimation(state);
872
+ }
727
873
  }
728
874
  }
729
875
  }
@@ -818,6 +964,9 @@ void viewDisplayabilityChanged(uint64_t driverId, SmoothClipView *view) {
818
964
  return;
819
965
  }
820
966
  if (![view smoothClipCanDisplay]) {
967
+ if (!applicationActiveState()) {
968
+ return;
969
+ }
821
970
  if (state.animation->groupId != 0 && state.animation->started) {
822
971
  finishGroupForHostLoss(state.animation->groupId);
823
972
  } else if (state.animation->started) {
@@ -841,7 +990,10 @@ void unregisterView(uint64_t driverId, SmoothClipView *view) {
841
990
  DriverState &state = iterator->second;
842
991
  const ViewKey key = keyForView(view);
843
992
  if (state.animation.has_value()) {
844
- if (state.animation->groupId != 0 && state.animation->started) {
993
+ if (!applicationActiveState() && state.animation->started) {
994
+ state.animation->participants.erase(key);
995
+ state.animation->needsForegroundReconciliation = true;
996
+ } else if (state.animation->groupId != 0 && state.animation->started) {
845
997
  finishGroupForHostLoss(state.animation->groupId);
846
998
  } else if (state.animation->groupId == 0 && state.animation->started) {
847
999
  finishStandaloneAtTarget(driverId, state);
@@ -1611,6 +1763,10 @@ void viewAnimationDidStop(
1611
1763
 
1612
1764
  const ViewKey key = keyForView(view);
1613
1765
  if (state.animation->participants.erase(key) == 0) return;
1766
+ if (!applicationActiveState() || state.animation->exposedToInactivity) {
1767
+ state.animation->needsForegroundReconciliation = true;
1768
+ return;
1769
+ }
1614
1770
  state.animation->finished = state.animation->finished && finished;
1615
1771
  finishIfNoInstalledParticipants(driverId, state);
1616
1772
  }
@@ -10,7 +10,7 @@
10
10
  namespace facebook::react {
11
11
  namespace {
12
12
 
13
- constexpr size_t kPresentationStride = 21;
13
+ constexpr size_t kPresentationStride = 23;
14
14
  constexpr size_t kSnapshotStride = kPresentationStride + 1;
15
15
  constexpr size_t kMotionEntryStride = kPresentationStride * 2 + 2;
16
16
 
@@ -65,7 +65,9 @@ smoothclip::Presentation makePresentation(
65
65
  double shadowOffsetX = 0,
66
66
  double shadowOffsetY = 0,
67
67
  double shadowBlurRadius = 0,
68
- double shadowSpreadDistance = 0) {
68
+ double shadowSpreadDistance = 0,
69
+ double rotation = 0,
70
+ double opacity = 1) {
69
71
  const bool uniform = topLeftRadius == topRightRadius &&
70
72
  topLeftRadius == bottomRightRadius &&
71
73
  topLeftRadius == bottomLeftRadius;
@@ -82,7 +84,8 @@ smoothclip::Presentation makePresentation(
82
84
  contentTranslateY,
83
85
  contentScale,
84
86
  {shadowEnabled, shadowRed, shadowGreen, shadowBlue, shadowAlpha,
85
- shadowOffsetX, shadowOffsetY, shadowBlurRadius, shadowSpreadDistance}};
87
+ shadowOffsetX, shadowOffsetY, shadowBlurRadius, shadowSpreadDistance},
88
+ rotation, opacity};
86
89
  }
87
90
 
88
91
  bool finitePresentation(const smoothclip::Presentation &presentation) {
@@ -97,6 +100,7 @@ bool finitePresentation(const smoothclip::Presentation &presentation) {
97
100
  std::isfinite(presentation.contentTranslateY) &&
98
101
  std::isfinite(presentation.contentScale) &&
99
102
  presentation.contentScale > 0 &&
103
+ std::isfinite(presentation.rotation) && std::isfinite(presentation.opacity) &&
100
104
  std::isfinite(presentation.shadow.red) &&
101
105
  std::isfinite(presentation.shadow.green) &&
102
106
  std::isfinite(presentation.shadow.blue) &&
@@ -141,7 +145,7 @@ bool presentationAt(
141
145
  values[11],
142
146
  values[12] == 1,
143
147
  values[13], values[14], values[15], values[16],
144
- values[17], values[18], values[19], values[20]);
148
+ values[17], values[18], values[19], values[20], values[21], values[22]);
145
149
  return finitePresentation(result);
146
150
  }
147
151
 
@@ -181,6 +185,8 @@ void writePresentation(
181
185
  result.setValueAtIndex(runtime, offset + 18, presentation.shadow.offsetY);
182
186
  result.setValueAtIndex(runtime, offset + 19, presentation.shadow.blurRadius);
183
187
  result.setValueAtIndex(runtime, offset + 20, presentation.shadow.spreadDistance);
188
+ result.setValueAtIndex(runtime, offset + 21, presentation.rotation);
189
+ result.setValueAtIndex(runtime, offset + 22, presentation.opacity);
184
190
  }
185
191
 
186
192
  jsi::Array snapshotArray(
@@ -226,6 +232,8 @@ std::vector<double> snapshotValues(
226
232
  result.push_back(presentation.shadow.offsetY);
227
233
  result.push_back(presentation.shadow.blurRadius);
228
234
  result.push_back(presentation.shadow.spreadDistance);
235
+ result.push_back(presentation.rotation);
236
+ result.push_back(presentation.opacity);
229
237
  }
230
238
  return result;
231
239
  }
@@ -504,13 +512,16 @@ void SmoothClipTurboModule::setClipFrameScalars(
504
512
  double shadowOffsetX,
505
513
  double shadowOffsetY,
506
514
  double shadowBlurRadius,
507
- double shadowSpreadDistance) {
515
+ double shadowSpreadDistance,
516
+ double rotation,
517
+ double opacity) {
508
518
  const smoothclip::Presentation presentation = makePresentation(
509
519
  x, y, width, height, topLeftRadius, topRightRadius,
510
520
  bottomRightRadius, bottomLeftRadius, curveCode,
511
521
  contentTranslateX, contentTranslateY, contentScale,
512
522
  shadowEnabled, shadowRed, shadowGreen, shadowBlue, shadowAlpha,
513
- shadowOffsetX, shadowOffsetY, shadowBlurRadius, shadowSpreadDistance);
523
+ shadowOffsetX, shadowOffsetY, shadowBlurRadius, shadowSpreadDistance,
524
+ rotation, opacity);
514
525
  if (!validDriverId(driverId) || !finitePresentation(presentation)) return;
515
526
  smoothclip::setPresentation(
516
527
  static_cast<uint64_t>(driverId), presentation, true, false, true);
@@ -160,7 +160,9 @@ class SmoothClipTurboModule final
160
160
  double shadowOffsetX,
161
161
  double shadowOffsetY,
162
162
  double shadowBlurRadius,
163
- double shadowSpreadDistance);
163
+ double shadowSpreadDistance,
164
+ double rotation,
165
+ double opacity);
164
166
  jsi::Array beginInteraction(jsi::Runtime &runtime, double driverId);
165
167
  jsi::Array snapshotCurrent(jsi::Runtime &runtime, double driverId);
166
168