react-native-reanimated 4.4.0 → 4.4.2

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 (81) hide show
  1. package/Common/cpp/reanimated/CSS/interpolation/transforms/TransformOperationInterpolator.cpp +12 -5
  2. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.cpp +63 -36
  3. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.h +6 -8
  4. package/Common/cpp/reanimated/CSS/svg/values/SVGStrokeDashArray.cpp +5 -0
  5. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +25 -12
  6. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
  7. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.cpp +6 -2
  8. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +44 -1
  9. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +125 -43
  10. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +189 -102
  11. package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +8 -3
  12. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +4 -16
  13. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +0 -16
  14. package/android/CMakeLists.txt +6 -0
  15. package/android/build.gradle.kts +12 -1
  16. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +46 -2
  17. package/android/src/main/java/com/swmansion/reanimated/keyboard/KeyboardAnimationCallback.kt +8 -1
  18. package/apple/reanimated/apple/ReanimatedModule.mm +12 -1
  19. package/compatibility.json +1 -1
  20. package/lib/module/common/constants/platform.js +4 -4
  21. package/lib/module/common/constants/platform.js.map +1 -1
  22. package/lib/module/core.js +27 -5
  23. package/lib/module/core.js.map +1 -1
  24. package/lib/module/createAnimatedComponent/getViewInfo.js +3 -1
  25. package/lib/module/createAnimatedComponent/getViewInfo.js.map +1 -1
  26. package/lib/module/css/component/AnimatedComponent.js +0 -2
  27. package/lib/module/css/component/AnimatedComponent.js.map +1 -1
  28. package/lib/module/css/native/managers/CSSManager.js +5 -5
  29. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  30. package/lib/module/css/native/managers/CSSTransitionsManager.js +8 -1
  31. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  32. package/lib/module/css/svg/native/processors/stroke.js +9 -5
  33. package/lib/module/css/svg/native/processors/stroke.js.map +1 -1
  34. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js +6 -3
  35. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js.map +1 -1
  36. package/lib/module/layoutReanimation/animationsManager.js +31 -6
  37. package/lib/module/layoutReanimation/animationsManager.js.map +1 -1
  38. package/lib/module/mappers.js +1 -1
  39. package/lib/module/mappers.js.map +1 -1
  40. package/lib/module/platform-specific/findHostInstance.js +8 -5
  41. package/lib/module/platform-specific/findHostInstance.js.map +1 -1
  42. package/lib/module/platform-specific/jsVersion.js +1 -1
  43. package/lib/module/updateProps/updateProps.js +3 -2
  44. package/lib/module/updateProps/updateProps.js.map +1 -1
  45. package/lib/module/valueSetter.js +6 -7
  46. package/lib/module/valueSetter.js.map +1 -1
  47. package/lib/typescript/common/constants/platform.d.ts.map +1 -1
  48. package/lib/typescript/core.d.ts.map +1 -1
  49. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +0 -1
  50. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  51. package/lib/typescript/createAnimatedComponent/getViewInfo.d.ts.map +1 -1
  52. package/lib/typescript/css/component/AnimatedComponent.d.ts +0 -1
  53. package/lib/typescript/css/component/AnimatedComponent.d.ts.map +1 -1
  54. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts +5 -1
  55. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts.map +1 -1
  56. package/lib/typescript/css/svg/native/processors/stroke.d.ts.map +1 -1
  57. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts +17 -14
  58. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts.map +1 -1
  59. package/lib/typescript/layoutReanimation/animationsManager.d.ts.map +1 -1
  60. package/lib/typescript/platform-specific/findHostInstance.d.ts.map +1 -1
  61. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  62. package/lib/typescript/platform-specific/types.d.ts +1 -0
  63. package/lib/typescript/platform-specific/types.d.ts.map +1 -1
  64. package/lib/typescript/updateProps/updateProps.d.ts.map +1 -1
  65. package/package.json +2 -2
  66. package/src/common/constants/platform.ts +4 -4
  67. package/src/core.ts +27 -5
  68. package/src/createAnimatedComponent/commonTypes.ts +0 -1
  69. package/src/createAnimatedComponent/getViewInfo.ts +3 -1
  70. package/src/css/component/AnimatedComponent.tsx +0 -2
  71. package/src/css/native/managers/CSSManager.ts +8 -8
  72. package/src/css/native/managers/CSSTransitionsManager.ts +9 -2
  73. package/src/css/svg/native/processors/stroke.ts +9 -6
  74. package/src/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts +39 -36
  75. package/src/layoutReanimation/animationsManager.ts +33 -5
  76. package/src/mappers.ts +1 -1
  77. package/src/platform-specific/findHostInstance.ts +7 -7
  78. package/src/platform-specific/jsVersion.ts +1 -1
  79. package/src/platform-specific/types.ts +2 -0
  80. package/src/updateProps/updateProps.ts +3 -2
  81. package/src/valueSetter.ts +7 -7
@@ -276,11 +276,10 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Experimental::endLayoutAnimation(
276
276
  // one after the other, so we need to keep count of how many
277
277
  // were actually triggered, so that we don't cleanup necessary
278
278
  // structures too early
279
- if (layoutAnimation.count > 1) {
280
- layoutAnimation.count--;
279
+ if (--layoutAnimation.count > 0) {
281
280
  return {};
282
281
  }
283
- finishedAnimationTags_.push_back(tag);
282
+ maybeSettledAnimationTags_.insert(tag);
284
283
  auto surfaceId = layoutAnimation.finalView.surfaceId;
285
284
 
286
285
  if (sharedTransitionManager_->tagToName_.contains(tag)) {
@@ -397,7 +396,7 @@ void LayoutAnimationsProxy_Experimental::addOngoingAnimations(SurfaceId surfaceI
397
396
 
398
397
  const auto layoutAnimationIt = layoutAnimations_.find(tag);
399
398
 
400
- if (layoutAnimationIt == layoutAnimations_.end()) {
399
+ if (layoutAnimationIt == layoutAnimations_.end() || layoutAnimationIt->second.isSettled()) {
401
400
  continue;
402
401
  }
403
402
 
@@ -516,11 +515,10 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively(
516
515
 
517
516
  if (hasExitAnimation) {
518
517
  node->state = ANIMATING;
519
- startExitingAnimation(node);
520
518
  lightNodes_[node->current.tag] = node;
519
+ startExitingAnimation(node);
521
520
  } else {
522
- // TODO (future): add proper cleanup
523
- // layoutAnimationsManager_->clearLayoutAnimationConfig(node->tag);
521
+ layoutAnimationsManager_->clearLayoutAnimationConfig(node->current.tag);
524
522
  }
525
523
 
526
524
  return wantAnimateExit;
@@ -532,10 +530,22 @@ void LayoutAnimationsProxy_Experimental::updateOngoingAnimationTarget(const int
532
530
  }
533
531
 
534
532
  void LayoutAnimationsProxy_Experimental::maybeCancelAnimation(const int tag) const {
535
- if (!layoutAnimations_.contains(tag)) {
533
+ #ifdef ANDROID
534
+ // also invalidate animation starts that are scheduled but haven't run yet,
535
+ // so they don't re-create the animation after this cancellation
536
+ if (const auto it = pendingStarts_.find(tag); it != pendingStarts_.end()) {
537
+ it->second.handle++;
538
+ }
539
+ #endif
540
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
541
+ if (layoutAnimationIt == layoutAnimations_.end()) {
542
+ return;
543
+ }
544
+ if (layoutAnimationIt->second.isSettled()) {
545
+ // Already settled - cleanupAnimations will erase it together with its updateMap entry.
536
546
  return;
537
547
  }
538
- layoutAnimations_.erase(tag);
548
+ layoutAnimations_.erase(layoutAnimationIt);
539
549
  scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag]() {
540
550
  auto strongThis = weakThis.lock();
541
551
  if (!strongThis) {
@@ -625,12 +635,17 @@ void LayoutAnimationsProxy_Experimental::cleanupAnimations(
625
635
  #ifdef ANDROID
626
636
  restoreOpacityInCaseOfFlakyEnteringAnimation(surfaceId);
627
637
  #endif // ANDROID
628
- for (const auto tag : finishedAnimationTags_) {
629
- auto &updateMap = surfaceManager.getUpdateMap(surfaceId);
630
- layoutAnimations_.erase(tag);
638
+ auto &updateMap = surfaceManager.getUpdateMap(surfaceId);
639
+ for (const auto tag : maybeSettledAnimationTags_) {
640
+ // Skip tags re-animated since they settled (count back above 0).
641
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
642
+ if (layoutAnimationIt == layoutAnimations_.end() || !layoutAnimationIt->second.isSettled()) {
643
+ continue;
644
+ }
645
+ layoutAnimations_.erase(layoutAnimationIt);
631
646
  updateMap.erase(tag);
632
647
  }
633
- finishedAnimationTags_.clear();
648
+ maybeSettledAnimationTags_.clear();
634
649
  }
635
650
 
636
651
  // MARK: Start Animation
@@ -676,9 +691,24 @@ void LayoutAnimationsProxy_Experimental::startEnteringAnimation(const std::share
676
691
  const auto &parent = node->parent.lock();
677
692
  react_native_assert(parent && "Parent node is nullptr");
678
693
  const auto parentTag = parent->current.tag;
694
+ #ifdef ANDROID
695
+ const auto handle = pendingStarts_[newChildShadowView.tag].handle;
696
+ pendingStarts_[newChildShadowView.tag].count++;
697
+ #endif
679
698
 
680
699
  scheduleOnUI(
681
- uiScheduler_, [weakThis = weak_from_this(), finalView, currentView, newChildShadowView, parentTag, opacity]() {
700
+ uiScheduler_,
701
+ [weakThis = weak_from_this(),
702
+ finalView,
703
+ currentView,
704
+ newChildShadowView,
705
+ parentTag,
706
+ opacity
707
+ #ifdef ANDROID
708
+ ,
709
+ handle
710
+ #endif
711
+ ]() {
682
712
  auto strongThis = weakThis.lock();
683
713
  if (!strongThis) {
684
714
  return;
@@ -688,6 +718,12 @@ void LayoutAnimationsProxy_Experimental::startEnteringAnimation(const std::share
688
718
  const auto tag = newChildShadowView.tag;
689
719
  {
690
720
  auto lock = std::unique_lock<std::recursive_mutex>(strongThis->mutex);
721
+ #ifdef ANDROID
722
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
723
+ // the view was removed before this start could run
724
+ return;
725
+ }
726
+ #endif
691
727
  strongThis->layoutAnimations_[tag] = {
692
728
  .finalView = newChildShadowView,
693
729
  .currentView = newChildShadowView,
@@ -721,38 +757,63 @@ void LayoutAnimationsProxy_Experimental::startExitingAnimation(const std::shared
721
757
  const auto &parent = node->parent.lock();
722
758
  react_native_assert(parent && "Parent node is nullptr");
723
759
  const auto parentTag = parent->current.tag;
760
+ #ifdef ANDROID
761
+ const auto handle = pendingStarts_[tag].handle;
762
+ pendingStarts_[tag].count++;
763
+ #endif
724
764
 
725
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag, parentTag, oldChildShadowView, surfaceId]() {
726
- auto strongThis = weakThis.lock();
727
- if (!strongThis) {
728
- return;
729
- }
765
+ scheduleOnUI(
766
+ uiScheduler_,
767
+ [weakThis = weak_from_this(),
768
+ tag,
769
+ parentTag,
770
+ oldChildShadowView,
771
+ surfaceId
772
+ #ifdef ANDROID
773
+ ,
774
+ handle
775
+ #endif
776
+ ]() {
777
+ auto strongThis = weakThis.lock();
778
+ if (!strongThis) {
779
+ return;
780
+ }
730
781
 
731
- auto oldView = oldChildShadowView;
732
- Rect window{};
733
- {
734
- auto &mutex = strongThis->mutex;
735
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
736
- oldView = strongThis->maybeCreateLayoutAnimation(oldView, oldView, parentTag);
737
- window = strongThis->surfaceManager.getWindow(surfaceId);
738
- }
782
+ auto oldView = oldChildShadowView;
783
+ Rect window{};
784
+ {
785
+ auto &mutex = strongThis->mutex;
786
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
787
+ #ifdef ANDROID
788
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
789
+ // the view was removed (e.g. its subtree was force-ended by a screen
790
+ // pop) before this start could run — its Remove+Delete are already on
791
+ // their way to the mounting layer, so starting the animation now
792
+ // would emit updates for a view that's about to be deleted
793
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
794
+ return;
795
+ }
796
+ #endif
797
+ oldView = strongThis->maybeCreateLayoutAnimation(oldView, oldView, parentTag);
798
+ window = strongThis->surfaceManager.getWindow(surfaceId);
799
+ }
739
800
 
740
- const Snapshot values(oldView, window);
801
+ const Snapshot values(oldView, window);
741
802
 
742
- auto &uiRuntime = strongThis->uiRuntime_;
743
- const jsi::Object yogaValues(uiRuntime);
744
- yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
745
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
746
- yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
747
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
748
- yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
749
- yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
750
- yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
751
- yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
752
- strongThis->layoutAnimationsManager_->startLayoutAnimation(
753
- uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
754
- strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
755
- });
803
+ auto &uiRuntime = strongThis->uiRuntime_;
804
+ const jsi::Object yogaValues(uiRuntime);
805
+ yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
806
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
807
+ yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
808
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
809
+ yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
810
+ yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
811
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
812
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
813
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
814
+ uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
815
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
816
+ });
756
817
  }
757
818
 
758
819
  void LayoutAnimationsProxy_Experimental::startLayoutAnimation(const std::shared_ptr<LightNode> &node) const {
@@ -763,9 +824,24 @@ void LayoutAnimationsProxy_Experimental::startLayoutAnimation(const std::shared_
763
824
  const auto &parent = node->parent.lock();
764
825
  react_native_assert(parent && "Parent node is nullptr");
765
826
  const auto parentTag = parent->current.tag;
827
+ #ifdef ANDROID
828
+ const auto handle = pendingStarts_[tag].handle;
829
+ pendingStarts_[tag].count++;
830
+ #endif
766
831
 
767
832
  scheduleOnUI(
768
- uiScheduler_, [weakThis = weak_from_this(), surfaceId, oldChildShadowView, newChildShadowView, parentTag, tag]() {
833
+ uiScheduler_,
834
+ [weakThis = weak_from_this(),
835
+ surfaceId,
836
+ oldChildShadowView,
837
+ newChildShadowView,
838
+ parentTag,
839
+ tag
840
+ #ifdef ANDROID
841
+ ,
842
+ handle
843
+ #endif
844
+ ]() {
769
845
  auto strongThis = weakThis.lock();
770
846
  if (!strongThis) {
771
847
  return;
@@ -776,6 +852,12 @@ void LayoutAnimationsProxy_Experimental::startLayoutAnimation(const std::shared_
776
852
  {
777
853
  auto &mutex = strongThis->mutex;
778
854
  auto lock = std::unique_lock<std::recursive_mutex>(mutex);
855
+ #ifdef ANDROID
856
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
857
+ // the view was removed before this start could run
858
+ return;
859
+ }
860
+ #endif
779
861
  oldView = strongThis->maybeCreateLayoutAnimation(oldView, newChildShadowView, parentTag);
780
862
  window = strongThis->surfaceManager.getWindow(surfaceId);
781
863
  }
@@ -1,5 +1,6 @@
1
1
  #include <reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h>
2
2
 
3
+ #include <react/debug/react_native_assert.h>
3
4
  #include <react/renderer/mounting/ShadowViewMutation.h>
4
5
 
5
6
  #include <memory>
@@ -42,12 +43,18 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
42
43
  #ifdef ANDROID
43
44
  restoreOpacityInCaseOfFlakyEnteringAnimation(surfaceId);
44
45
  #endif // ANDROID
45
- for (const auto tag : finishedAnimationTags_) {
46
- auto &updateMap = surfaceManager.getUpdateMap(surfaceId);
47
- layoutAnimations_.erase(tag);
46
+ auto &updateMap = surfaceManager.getUpdateMap(surfaceId);
47
+ for (const auto tag : maybeSettledAnimationTags_) {
48
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
49
+ if (layoutAnimationIt == layoutAnimations_.end() || !layoutAnimationIt->second.isSettled()) {
50
+ continue;
51
+ }
52
+ layoutAnimations_.erase(layoutAnimationIt);
48
53
  updateMap.erase(tag);
49
54
  }
50
- finishedAnimationTags_.clear();
55
+ maybeSettledAnimationTags_.clear();
56
+ // Past this point no animation can be settled - we hold the mutex for the whole
57
+ // transaction, so no end callback can run until we return.
51
58
 
52
59
  parseRemoveMutations(movedViews, mutations, roots);
53
60
 
@@ -109,11 +116,10 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::endLayoutAnimation(int ta
109
116
  // one after the other, so we need to keep count of how many
110
117
  // were actually triggered, so that we don't cleanup necessary
111
118
  // structures too early
112
- if (layoutAnimation.count > 1) {
113
- layoutAnimation.count--;
119
+ if (--layoutAnimation.count > 0) {
114
120
  return {};
115
121
  }
116
- finishedAnimationTags_.push_back(tag);
122
+ maybeSettledAnimationTags_.insert(tag);
117
123
  auto surfaceId = layoutAnimation.finalView.surfaceId;
118
124
 
119
125
  if (!shouldRemove || !nodeForTag_.contains(tag)) {
@@ -121,6 +127,7 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::endLayoutAnimation(int ta
121
127
  }
122
128
 
123
129
  auto node = nodeForTag_[tag];
130
+ react_native_assert(node->isMutationNode() && "exiting tag must map to a MutationNode");
124
131
  auto mutationNode = std::static_pointer_cast<MutationNode>(node);
125
132
  mutationNode->state = ExitingState_Legacy::DEAD;
126
133
  auto &[deadNodes] = surfaceContext_[surfaceId];
@@ -406,7 +413,13 @@ void LayoutAnimationsProxy_Legacy::addOngoingAnimations(SurfaceId surfaceId, Sha
406
413
 
407
414
  auto layoutAnimationIt = layoutAnimations_.find(tag);
408
415
 
409
- if (layoutAnimationIt == layoutAnimations_.end()) {
416
+ if (layoutAnimationIt == layoutAnimations_.end() ||
417
+ // A settled animation is normally cleaned up without applying further
418
+ // updates. The exception is a flaky entering animation whose opacity was
419
+ // never restored (the view wasn't mounted in time) - we still need to
420
+ // apply that pending opacity, otherwise the view stays invisible. Only
421
+ // entering animations carry an opacity value.
422
+ (layoutAnimationIt->second.isSettled() && !layoutAnimationIt->second.opacity.has_value())) {
410
423
  continue;
411
424
  }
412
425
 
@@ -633,38 +646,60 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
633
646
 
634
647
  auto &viewProps = static_cast<const ViewProps &>(*mutation.newChildShadowView.props);
635
648
  auto opacity = viewProps.opacity;
649
+ #ifdef ANDROID
650
+ const auto handle = pendingStarts_[tag].handle;
651
+ pendingStarts_[tag].count++;
652
+ #endif
636
653
 
637
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), finalView, current, mutation, opacity, tag]() {
638
- auto strongThis = weakThis.lock();
639
- if (!strongThis) {
640
- return;
641
- }
654
+ scheduleOnUI(
655
+ uiScheduler_,
656
+ [weakThis = weak_from_this(),
657
+ finalView,
658
+ current,
659
+ mutation,
660
+ opacity,
661
+ tag
662
+ #ifdef ANDROID
663
+ ,
664
+ handle
665
+ #endif
666
+ ]() {
667
+ auto strongThis = weakThis.lock();
668
+ if (!strongThis) {
669
+ return;
670
+ }
642
671
 
643
- Rect window{};
644
- {
645
- auto &mutex = strongThis->mutex;
646
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
647
- strongThis->layoutAnimations_.insert_or_assign(
648
- tag,
649
- LayoutAnimation{
650
- .finalView = finalView, .currentView = current, .parentTag = mutation.parentTag, .opacity = opacity});
651
- window = strongThis->surfaceManager.getWindow(mutation.newChildShadowView.surfaceId);
652
- }
672
+ Rect window{};
673
+ {
674
+ auto &mutex = strongThis->mutex;
675
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
676
+ #ifdef ANDROID
677
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
678
+ // the view was removed before this start could run
679
+ return;
680
+ }
681
+ #endif
682
+ strongThis->layoutAnimations_.insert_or_assign(
683
+ tag,
684
+ LayoutAnimation{
685
+ .finalView = finalView, .currentView = current, .parentTag = mutation.parentTag, .opacity = opacity});
686
+ window = strongThis->surfaceManager.getWindow(mutation.newChildShadowView.surfaceId);
687
+ }
653
688
 
654
- Snapshot values(mutation.newChildShadowView, window);
655
- auto &uiRuntime = strongThis->uiRuntime_;
656
- jsi::Object yogaValues(uiRuntime);
657
- yogaValues.setProperty(uiRuntime, "targetOriginX", values.x);
658
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", values.x);
659
- yogaValues.setProperty(uiRuntime, "targetOriginY", values.y);
660
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", values.y);
661
- yogaValues.setProperty(uiRuntime, "targetWidth", values.width);
662
- yogaValues.setProperty(uiRuntime, "targetHeight", values.height);
663
- yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
664
- yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
665
- strongThis->layoutAnimationsManager_->startLayoutAnimation(
666
- uiRuntime, tag, LayoutAnimationType::ENTERING, yogaValues);
667
- });
689
+ Snapshot values(mutation.newChildShadowView, window);
690
+ auto &uiRuntime = strongThis->uiRuntime_;
691
+ jsi::Object yogaValues(uiRuntime);
692
+ yogaValues.setProperty(uiRuntime, "targetOriginX", values.x);
693
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", values.x);
694
+ yogaValues.setProperty(uiRuntime, "targetOriginY", values.y);
695
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", values.y);
696
+ yogaValues.setProperty(uiRuntime, "targetWidth", values.width);
697
+ yogaValues.setProperty(uiRuntime, "targetHeight", values.height);
698
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
699
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
700
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
701
+ uiRuntime, tag, LayoutAnimationType::ENTERING, yogaValues);
702
+ });
668
703
  }
669
704
 
670
705
  void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowViewMutation &mutation) const {
@@ -672,38 +707,62 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
672
707
  LOG(INFO) << "start exiting animation for tag " << tag << std::endl;
673
708
  #endif
674
709
  auto surfaceId = mutation.oldChildShadowView.surfaceId;
710
+ #ifdef ANDROID
711
+ const auto handle = pendingStarts_[tag].handle;
712
+ pendingStarts_[tag].count++;
713
+ #endif
675
714
 
676
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag, mutation, surfaceId]() {
677
- auto strongThis = weakThis.lock();
678
- if (!strongThis) {
679
- return;
680
- }
681
-
682
- auto oldView = mutation.oldChildShadowView;
683
- Rect window{};
684
- {
685
- auto &mutex = strongThis->mutex;
686
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
687
- strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
688
- window = strongThis->surfaceManager.getWindow(surfaceId);
689
- }
715
+ scheduleOnUI(
716
+ uiScheduler_,
717
+ [weakThis = weak_from_this(),
718
+ tag,
719
+ mutation,
720
+ surfaceId
721
+ #ifdef ANDROID
722
+ ,
723
+ handle
724
+ #endif
725
+ ]() {
726
+ auto strongThis = weakThis.lock();
727
+ if (!strongThis) {
728
+ return;
729
+ }
690
730
 
691
- Snapshot values(oldView, window);
731
+ auto oldView = mutation.oldChildShadowView;
732
+ Rect window{};
733
+ {
734
+ auto &mutex = strongThis->mutex;
735
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
736
+ #ifdef ANDROID
737
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
738
+ // the view was removed (e.g. its subtree was force-ended by a screen
739
+ // pop) before this start could run — its Remove+Delete are already on
740
+ // their way to the mounting layer, so starting the animation now
741
+ // would emit updates for a view that's about to be deleted
742
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
743
+ return;
744
+ }
745
+ #endif
746
+ strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
747
+ window = strongThis->surfaceManager.getWindow(surfaceId);
748
+ }
692
749
 
693
- auto &uiRuntime = strongThis->uiRuntime_;
694
- jsi::Object yogaValues(uiRuntime);
695
- yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
696
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
697
- yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
698
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
699
- yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
700
- yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
701
- yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
702
- yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
703
- strongThis->layoutAnimationsManager_->startLayoutAnimation(
704
- uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
705
- strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
706
- });
750
+ Snapshot values(oldView, window);
751
+
752
+ auto &uiRuntime = strongThis->uiRuntime_;
753
+ jsi::Object yogaValues(uiRuntime);
754
+ yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
755
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
756
+ yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
757
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
758
+ yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
759
+ yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
760
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
761
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
762
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
763
+ uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
764
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
765
+ });
707
766
  }
708
767
 
709
768
  void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const ShadowViewMutation &mutation) const {
@@ -711,43 +770,64 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
711
770
  LOG(INFO) << "start layout animation for tag " << tag << std::endl;
712
771
  #endif
713
772
  auto surfaceId = mutation.oldChildShadowView.surfaceId;
773
+ #ifdef ANDROID
774
+ const auto handle = pendingStarts_[tag].handle;
775
+ pendingStarts_[tag].count++;
776
+ #endif
714
777
 
715
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), mutation, surfaceId, tag]() {
716
- auto strongThis = weakThis.lock();
717
- if (!strongThis) {
718
- return;
719
- }
720
-
721
- auto oldView = mutation.oldChildShadowView;
722
- Rect window{};
723
- {
724
- auto &mutex = strongThis->mutex;
725
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
726
- strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
727
- window = strongThis->surfaceManager.getWindow(surfaceId);
728
- }
778
+ scheduleOnUI(
779
+ uiScheduler_,
780
+ [weakThis = weak_from_this(),
781
+ mutation,
782
+ surfaceId,
783
+ tag
784
+ #ifdef ANDROID
785
+ ,
786
+ handle
787
+ #endif
788
+ ]() {
789
+ auto strongThis = weakThis.lock();
790
+ if (!strongThis) {
791
+ return;
792
+ }
729
793
 
730
- Snapshot currentValues(oldView, window);
731
- Snapshot targetValues(mutation.newChildShadowView, window);
794
+ auto oldView = mutation.oldChildShadowView;
795
+ Rect window{};
796
+ {
797
+ auto &mutex = strongThis->mutex;
798
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
799
+ #ifdef ANDROID
800
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
801
+ // the view was removed before this start could run
802
+ return;
803
+ }
804
+ #endif
805
+ strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
806
+ window = strongThis->surfaceManager.getWindow(surfaceId);
807
+ }
732
808
 
733
- auto &uiRuntime = strongThis->uiRuntime_;
734
- jsi::Object yogaValues(uiRuntime);
735
- yogaValues.setProperty(uiRuntime, "currentOriginX", currentValues.x);
736
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", currentValues.x);
737
- yogaValues.setProperty(uiRuntime, "currentOriginY", currentValues.y);
738
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", currentValues.y);
739
- yogaValues.setProperty(uiRuntime, "currentWidth", currentValues.width);
740
- yogaValues.setProperty(uiRuntime, "currentHeight", currentValues.height);
741
- yogaValues.setProperty(uiRuntime, "targetOriginX", targetValues.x);
742
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", targetValues.x);
743
- yogaValues.setProperty(uiRuntime, "targetOriginY", targetValues.y);
744
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", targetValues.y);
745
- yogaValues.setProperty(uiRuntime, "targetWidth", targetValues.width);
746
- yogaValues.setProperty(uiRuntime, "targetHeight", targetValues.height);
747
- yogaValues.setProperty(uiRuntime, "windowWidth", targetValues.windowWidth);
748
- yogaValues.setProperty(uiRuntime, "windowHeight", targetValues.windowHeight);
749
- strongThis->layoutAnimationsManager_->startLayoutAnimation(uiRuntime, tag, LayoutAnimationType::LAYOUT, yogaValues);
750
- });
809
+ Snapshot currentValues(oldView, window);
810
+ Snapshot targetValues(mutation.newChildShadowView, window);
811
+
812
+ auto &uiRuntime = strongThis->uiRuntime_;
813
+ jsi::Object yogaValues(uiRuntime);
814
+ yogaValues.setProperty(uiRuntime, "currentOriginX", currentValues.x);
815
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", currentValues.x);
816
+ yogaValues.setProperty(uiRuntime, "currentOriginY", currentValues.y);
817
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", currentValues.y);
818
+ yogaValues.setProperty(uiRuntime, "currentWidth", currentValues.width);
819
+ yogaValues.setProperty(uiRuntime, "currentHeight", currentValues.height);
820
+ yogaValues.setProperty(uiRuntime, "targetOriginX", targetValues.x);
821
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", targetValues.x);
822
+ yogaValues.setProperty(uiRuntime, "targetOriginY", targetValues.y);
823
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", targetValues.y);
824
+ yogaValues.setProperty(uiRuntime, "targetWidth", targetValues.width);
825
+ yogaValues.setProperty(uiRuntime, "targetHeight", targetValues.height);
826
+ yogaValues.setProperty(uiRuntime, "windowWidth", targetValues.windowWidth);
827
+ yogaValues.setProperty(uiRuntime, "windowHeight", targetValues.windowHeight);
828
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
829
+ uiRuntime, tag, LayoutAnimationType::LAYOUT, yogaValues);
830
+ });
751
831
  }
752
832
 
753
833
  void LayoutAnimationsProxy_Legacy::updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation)
@@ -757,10 +837,17 @@ void LayoutAnimationsProxy_Legacy::updateOngoingAnimationTarget(const int tag, c
757
837
  }
758
838
 
759
839
  void LayoutAnimationsProxy_Legacy::maybeCancelAnimation(const int tag) const {
760
- if (!layoutAnimations_.contains(tag)) {
840
+ #ifdef ANDROID
841
+ // invalidate animation starts that are scheduled but haven't run yet
842
+ if (const auto it = pendingStarts_.find(tag); it != pendingStarts_.end()) {
843
+ it->second.handle++;
844
+ }
845
+ #endif
846
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
847
+ if (layoutAnimationIt == layoutAnimations_.end()) {
761
848
  return;
762
849
  }
763
- layoutAnimations_.erase(tag);
850
+ layoutAnimations_.erase(layoutAnimationIt);
764
851
  scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag]() {
765
852
  auto strongThis = weakThis.lock();
766
853
  if (!strongThis) {
@@ -210,7 +210,11 @@ Tag LayoutAnimationsProxy_Experimental::getOrCreateContainer(
210
210
  ShadowViewMutationList &filteredMutations,
211
211
  SurfaceId surfaceId) const {
212
212
  auto containerTag = sharedTransitionManager_->containerTags_[sharedTag];
213
- auto shouldCreateContainer = (containerTag == -1 || !layoutAnimations_.contains(containerTag));
213
+ auto shouldCreateContainer = true;
214
+ if (containerTag != -1) {
215
+ const auto layoutAnimationIt = layoutAnimations_.find(containerTag);
216
+ shouldCreateContainer = layoutAnimationIt == layoutAnimations_.end() || layoutAnimationIt->second.isSettled();
217
+ }
214
218
 
215
219
  if (shouldCreateContainer) {
216
220
  containerTag = containerTag_;
@@ -264,11 +268,12 @@ void LayoutAnimationsProxy_Experimental::handleSharedTransitionsStart(
264
268
  auto &[_, after] = transition.snapshot;
265
269
 
266
270
  auto containerTag = sharedTransitionManager_->containerTags_[sharedTag];
267
- if (!layoutAnimations_.contains(containerTag)) {
271
+ const auto layoutAnimationIt = layoutAnimations_.find(containerTag);
272
+ if (layoutAnimationIt == layoutAnimations_.end() || layoutAnimationIt->second.isSettled()) {
268
273
  continue;
269
274
  }
270
275
  after.tag = containerTag;
271
- const auto &la = layoutAnimations_[containerTag];
276
+ const auto &la = layoutAnimationIt->second;
272
277
  if (la.finalView.layoutMetrics != after.layoutMetrics) {
273
278
  overrideTransform(after, transition.transform[AFTER], propsParserContext);
274
279
  startSharedTransition(containerTag, la.currentView, after, surfaceId);