react-native-reanimated 4.4.1 → 4.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 (95) 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/Fabric/updates/AnimatedPropsRegistry.cpp +54 -24
  8. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h +13 -6
  9. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.cpp +6 -2
  10. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +44 -1
  11. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +125 -43
  12. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +228 -102
  13. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h +4 -0
  14. package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +8 -3
  15. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +7 -21
  16. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +0 -16
  17. package/android/CMakeLists.txt +6 -0
  18. package/android/build.gradle.kts +21 -1
  19. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +46 -2
  20. package/android/src/main/java/com/swmansion/reanimated/keyboard/KeyboardAnimationCallback.kt +8 -1
  21. package/apple/reanimated/apple/ReanimatedModule.mm +12 -1
  22. package/compatibility.json +1 -1
  23. package/lib/module/PropsRegistryGarbageCollector.js +3 -6
  24. package/lib/module/PropsRegistryGarbageCollector.js.map +1 -1
  25. package/lib/module/animation/styleAnimation.js +4 -3
  26. package/lib/module/animation/styleAnimation.js.map +1 -1
  27. package/lib/module/common/constants/platform.js +4 -4
  28. package/lib/module/common/constants/platform.js.map +1 -1
  29. package/lib/module/createAnimatedComponent/AnimatedComponent.js +1 -1
  30. package/lib/module/createAnimatedComponent/AnimatedComponent.js.map +1 -1
  31. package/lib/module/createAnimatedComponent/getViewInfo.js +3 -1
  32. package/lib/module/createAnimatedComponent/getViewInfo.js.map +1 -1
  33. package/lib/module/css/component/AnimatedComponent.js +0 -2
  34. package/lib/module/css/component/AnimatedComponent.js.map +1 -1
  35. package/lib/module/css/native/managers/CSSManager.js +5 -5
  36. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  37. package/lib/module/css/native/managers/CSSTransitionsManager.js +8 -1
  38. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  39. package/lib/module/css/svg/native/processors/stroke.js +9 -5
  40. package/lib/module/css/svg/native/processors/stroke.js.map +1 -1
  41. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js +6 -3
  42. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js.map +1 -1
  43. package/lib/module/layoutReanimation/animationsManager.js +39 -11
  44. package/lib/module/layoutReanimation/animationsManager.js.map +1 -1
  45. package/lib/module/mappers.js +1 -1
  46. package/lib/module/mappers.js.map +1 -1
  47. package/lib/module/platform-specific/findHostInstance.js +8 -5
  48. package/lib/module/platform-specific/findHostInstance.js.map +1 -1
  49. package/lib/module/platform-specific/jsVersion.js +1 -1
  50. package/lib/module/updateProps/updateProps.js +3 -2
  51. package/lib/module/updateProps/updateProps.js.map +1 -1
  52. package/lib/module/valueSetter.js +6 -7
  53. package/lib/module/valueSetter.js.map +1 -1
  54. package/lib/typescript/PropsRegistryGarbageCollector.d.ts +0 -1
  55. package/lib/typescript/PropsRegistryGarbageCollector.d.ts.map +1 -1
  56. package/lib/typescript/animation/styleAnimation.d.ts +1 -1
  57. package/lib/typescript/animation/styleAnimation.d.ts.map +1 -1
  58. package/lib/typescript/common/constants/platform.d.ts.map +1 -1
  59. package/lib/typescript/createAnimatedComponent/AnimatedComponent.d.ts.map +1 -1
  60. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +0 -1
  61. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  62. package/lib/typescript/createAnimatedComponent/getViewInfo.d.ts.map +1 -1
  63. package/lib/typescript/css/component/AnimatedComponent.d.ts +0 -1
  64. package/lib/typescript/css/component/AnimatedComponent.d.ts.map +1 -1
  65. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts +5 -1
  66. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts.map +1 -1
  67. package/lib/typescript/css/svg/native/processors/stroke.d.ts.map +1 -1
  68. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts +17 -14
  69. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts.map +1 -1
  70. package/lib/typescript/layoutReanimation/animationsManager.d.ts.map +1 -1
  71. package/lib/typescript/platform-specific/findHostInstance.d.ts.map +1 -1
  72. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  73. package/lib/typescript/platform-specific/types.d.ts +1 -0
  74. package/lib/typescript/platform-specific/types.d.ts.map +1 -1
  75. package/lib/typescript/updateProps/updateProps.d.ts.map +1 -1
  76. package/package.json +3 -3
  77. package/scripts/reanimated_utils.rb +7 -0
  78. package/src/PropsRegistryGarbageCollector.ts +3 -6
  79. package/src/animation/styleAnimation.ts +5 -3
  80. package/src/common/constants/platform.ts +4 -4
  81. package/src/createAnimatedComponent/AnimatedComponent.tsx +3 -1
  82. package/src/createAnimatedComponent/commonTypes.ts +0 -1
  83. package/src/createAnimatedComponent/getViewInfo.ts +3 -1
  84. package/src/css/component/AnimatedComponent.tsx +0 -2
  85. package/src/css/native/managers/CSSManager.ts +8 -8
  86. package/src/css/native/managers/CSSTransitionsManager.ts +9 -2
  87. package/src/css/svg/native/processors/stroke.ts +9 -6
  88. package/src/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts +39 -36
  89. package/src/layoutReanimation/animationsManager.ts +41 -11
  90. package/src/mappers.ts +1 -1
  91. package/src/platform-specific/findHostInstance.ts +7 -7
  92. package/src/platform-specific/jsVersion.ts +1 -1
  93. package/src/platform-specific/types.ts +2 -0
  94. package/src/updateProps/updateProps.ts +3 -2
  95. package/src/valueSetter.ts +7 -7
@@ -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>
@@ -37,17 +38,25 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
37
38
  std::vector<std::shared_ptr<MutationNode>> roots;
38
39
  std::unordered_map<Tag, Tag> movedViews;
39
40
 
41
+ reconcileContradictedRemovals(mutations, filteredMutations, surfaceId);
42
+
40
43
  addOngoingAnimations(surfaceId, filteredMutations);
41
44
 
42
45
  #ifdef ANDROID
43
46
  restoreOpacityInCaseOfFlakyEnteringAnimation(surfaceId);
44
47
  #endif // ANDROID
45
- for (const auto tag : finishedAnimationTags_) {
46
- auto &updateMap = surfaceManager.getUpdateMap(surfaceId);
47
- layoutAnimations_.erase(tag);
48
+ auto &updateMap = surfaceManager.getUpdateMap(surfaceId);
49
+ for (const auto tag : maybeSettledAnimationTags_) {
50
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
51
+ if (layoutAnimationIt == layoutAnimations_.end() || !layoutAnimationIt->second.isSettled()) {
52
+ continue;
53
+ }
54
+ layoutAnimations_.erase(layoutAnimationIt);
48
55
  updateMap.erase(tag);
49
56
  }
50
- finishedAnimationTags_.clear();
57
+ maybeSettledAnimationTags_.clear();
58
+ // Past this point no animation can be settled - we hold the mutex for the whole
59
+ // transaction, so no end callback can run until we return.
51
60
 
52
61
  parseRemoveMutations(movedViews, mutations, roots);
53
62
 
@@ -62,6 +71,40 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
62
71
  return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
63
72
  }
64
73
 
74
+ // If React re-creates or re-inserts a tag whose exiting removal we are still
75
+ // withholding, it has contradicted that withheld Remove/Delete. Flush it now
76
+ // instead of letting it fire later against a stale hierarchy (which would
77
+ // unmount the wrong, still-live view and crash the mounting layer).
78
+ //
79
+ // This must run before addOngoingAnimations (which would otherwise emit an
80
+ // Update for a tag we are about to Delete this frame) and before
81
+ // parseRemoveMutations, so the rest of the pipeline sees clean bookkeeping.
82
+ void LayoutAnimationsProxy_Legacy::reconcileContradictedRemovals(
83
+ ShadowViewMutationList &mutations,
84
+ ShadowViewMutationList &filteredMutations,
85
+ SurfaceId surfaceId) const {
86
+ auto &[deadNodes] = surfaceContext_[surfaceId];
87
+ for (auto &mutation : mutations) {
88
+ if (mutation.type != ShadowViewMutation::Type::Create && mutation.type != ShadowViewMutation::Type::Insert) {
89
+ continue;
90
+ }
91
+ auto tag = mutation.newChildShadowView.tag;
92
+ auto it = nodeForTag_.find(tag);
93
+ // Only a MutationNode represents a withheld removal; a plain Node is just a
94
+ // live parent of some removed child and must be left untouched.
95
+ if (it == nodeForTag_.end() || !it->second->isMutationNode()) {
96
+ continue;
97
+ }
98
+ auto node = std::static_pointer_cast<MutationNode>(it->second);
99
+ // Flush the withheld Remove/Delete for this tag (and its withheld subtree)
100
+ // right now, mirroring the deadNodes cleanup in handleRemovals. This removes
101
+ // the stale view before React's Create/Insert re-registers the same tag.
102
+ endAnimationsRecursively(node, filteredMutations);
103
+ maybeDropAncestors(node->unflattenedParent, node, filteredMutations);
104
+ deadNodes.erase(node);
105
+ }
106
+ }
107
+
65
108
  std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::progressLayoutAnimation(int tag, const jsi::Object &newStyle) {
66
109
  #ifdef LAYOUT_ANIMATIONS_LOGS
67
110
  LOG(INFO) << "progress layout animation for tag " << tag << std::endl;
@@ -109,11 +152,10 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::endLayoutAnimation(int ta
109
152
  // one after the other, so we need to keep count of how many
110
153
  // were actually triggered, so that we don't cleanup necessary
111
154
  // structures too early
112
- if (layoutAnimation.count > 1) {
113
- layoutAnimation.count--;
155
+ if (--layoutAnimation.count > 0) {
114
156
  return {};
115
157
  }
116
- finishedAnimationTags_.push_back(tag);
158
+ maybeSettledAnimationTags_.insert(tag);
117
159
  auto surfaceId = layoutAnimation.finalView.surfaceId;
118
160
 
119
161
  if (!shouldRemove || !nodeForTag_.contains(tag)) {
@@ -121,6 +163,10 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::endLayoutAnimation(int ta
121
163
  }
122
164
 
123
165
  auto node = nodeForTag_[tag];
166
+ if (!node->isMutationNode()) {
167
+ react_native_assert(false && "exiting tag must map to a MutationNode");
168
+ return {};
169
+ }
124
170
  auto mutationNode = std::static_pointer_cast<MutationNode>(node);
125
171
  mutationNode->state = ExitingState_Legacy::DEAD;
126
172
  auto &[deadNodes] = surfaceContext_[surfaceId];
@@ -406,7 +452,13 @@ void LayoutAnimationsProxy_Legacy::addOngoingAnimations(SurfaceId surfaceId, Sha
406
452
 
407
453
  auto layoutAnimationIt = layoutAnimations_.find(tag);
408
454
 
409
- if (layoutAnimationIt == layoutAnimations_.end()) {
455
+ if (layoutAnimationIt == layoutAnimations_.end() ||
456
+ // A settled animation is normally cleaned up without applying further
457
+ // updates. The exception is a flaky entering animation whose opacity was
458
+ // never restored (the view wasn't mounted in time) - we still need to
459
+ // apply that pending opacity, otherwise the view stays invisible. Only
460
+ // entering animations carry an opacity value.
461
+ (layoutAnimationIt->second.isSettled() && !layoutAnimationIt->second.opacity.has_value())) {
410
462
  continue;
411
463
  }
412
464
 
@@ -633,38 +685,60 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
633
685
 
634
686
  auto &viewProps = static_cast<const ViewProps &>(*mutation.newChildShadowView.props);
635
687
  auto opacity = viewProps.opacity;
688
+ #ifdef ANDROID
689
+ const auto handle = pendingStarts_[tag].handle;
690
+ pendingStarts_[tag].count++;
691
+ #endif
636
692
 
637
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), finalView, current, mutation, opacity, tag]() {
638
- auto strongThis = weakThis.lock();
639
- if (!strongThis) {
640
- return;
641
- }
693
+ scheduleOnUI(
694
+ uiScheduler_,
695
+ [weakThis = weak_from_this(),
696
+ finalView,
697
+ current,
698
+ mutation,
699
+ opacity,
700
+ tag
701
+ #ifdef ANDROID
702
+ ,
703
+ handle
704
+ #endif
705
+ ]() {
706
+ auto strongThis = weakThis.lock();
707
+ if (!strongThis) {
708
+ return;
709
+ }
642
710
 
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
- }
711
+ Rect window{};
712
+ {
713
+ auto &mutex = strongThis->mutex;
714
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
715
+ #ifdef ANDROID
716
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
717
+ // the view was removed before this start could run
718
+ return;
719
+ }
720
+ #endif
721
+ strongThis->layoutAnimations_.insert_or_assign(
722
+ tag,
723
+ LayoutAnimation{
724
+ .finalView = finalView, .currentView = current, .parentTag = mutation.parentTag, .opacity = opacity});
725
+ window = strongThis->surfaceManager.getWindow(mutation.newChildShadowView.surfaceId);
726
+ }
653
727
 
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
- });
728
+ Snapshot values(mutation.newChildShadowView, window);
729
+ auto &uiRuntime = strongThis->uiRuntime_;
730
+ jsi::Object yogaValues(uiRuntime);
731
+ yogaValues.setProperty(uiRuntime, "targetOriginX", values.x);
732
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", values.x);
733
+ yogaValues.setProperty(uiRuntime, "targetOriginY", values.y);
734
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", values.y);
735
+ yogaValues.setProperty(uiRuntime, "targetWidth", values.width);
736
+ yogaValues.setProperty(uiRuntime, "targetHeight", values.height);
737
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
738
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
739
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
740
+ uiRuntime, tag, LayoutAnimationType::ENTERING, yogaValues);
741
+ });
668
742
  }
669
743
 
670
744
  void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowViewMutation &mutation) const {
@@ -672,38 +746,62 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
672
746
  LOG(INFO) << "start exiting animation for tag " << tag << std::endl;
673
747
  #endif
674
748
  auto surfaceId = mutation.oldChildShadowView.surfaceId;
749
+ #ifdef ANDROID
750
+ const auto handle = pendingStarts_[tag].handle;
751
+ pendingStarts_[tag].count++;
752
+ #endif
675
753
 
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
- }
754
+ scheduleOnUI(
755
+ uiScheduler_,
756
+ [weakThis = weak_from_this(),
757
+ tag,
758
+ mutation,
759
+ surfaceId
760
+ #ifdef ANDROID
761
+ ,
762
+ handle
763
+ #endif
764
+ ]() {
765
+ auto strongThis = weakThis.lock();
766
+ if (!strongThis) {
767
+ return;
768
+ }
690
769
 
691
- Snapshot values(oldView, window);
770
+ auto oldView = mutation.oldChildShadowView;
771
+ Rect window{};
772
+ {
773
+ auto &mutex = strongThis->mutex;
774
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
775
+ #ifdef ANDROID
776
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
777
+ // the view was removed (e.g. its subtree was force-ended by a screen
778
+ // pop) before this start could run — its Remove+Delete are already on
779
+ // their way to the mounting layer, so starting the animation now
780
+ // would emit updates for a view that's about to be deleted
781
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
782
+ return;
783
+ }
784
+ #endif
785
+ strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
786
+ window = strongThis->surfaceManager.getWindow(surfaceId);
787
+ }
692
788
 
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
- });
789
+ Snapshot values(oldView, window);
790
+
791
+ auto &uiRuntime = strongThis->uiRuntime_;
792
+ jsi::Object yogaValues(uiRuntime);
793
+ yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
794
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
795
+ yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
796
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
797
+ yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
798
+ yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
799
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
800
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
801
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
802
+ uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
803
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
804
+ });
707
805
  }
708
806
 
709
807
  void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const ShadowViewMutation &mutation) const {
@@ -711,43 +809,64 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
711
809
  LOG(INFO) << "start layout animation for tag " << tag << std::endl;
712
810
  #endif
713
811
  auto surfaceId = mutation.oldChildShadowView.surfaceId;
812
+ #ifdef ANDROID
813
+ const auto handle = pendingStarts_[tag].handle;
814
+ pendingStarts_[tag].count++;
815
+ #endif
714
816
 
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
- }
817
+ scheduleOnUI(
818
+ uiScheduler_,
819
+ [weakThis = weak_from_this(),
820
+ mutation,
821
+ surfaceId,
822
+ tag
823
+ #ifdef ANDROID
824
+ ,
825
+ handle
826
+ #endif
827
+ ]() {
828
+ auto strongThis = weakThis.lock();
829
+ if (!strongThis) {
830
+ return;
831
+ }
729
832
 
730
- Snapshot currentValues(oldView, window);
731
- Snapshot targetValues(mutation.newChildShadowView, window);
833
+ auto oldView = mutation.oldChildShadowView;
834
+ Rect window{};
835
+ {
836
+ auto &mutex = strongThis->mutex;
837
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
838
+ #ifdef ANDROID
839
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
840
+ // the view was removed before this start could run
841
+ return;
842
+ }
843
+ #endif
844
+ strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
845
+ window = strongThis->surfaceManager.getWindow(surfaceId);
846
+ }
732
847
 
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
- });
848
+ Snapshot currentValues(oldView, window);
849
+ Snapshot targetValues(mutation.newChildShadowView, window);
850
+
851
+ auto &uiRuntime = strongThis->uiRuntime_;
852
+ jsi::Object yogaValues(uiRuntime);
853
+ yogaValues.setProperty(uiRuntime, "currentOriginX", currentValues.x);
854
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", currentValues.x);
855
+ yogaValues.setProperty(uiRuntime, "currentOriginY", currentValues.y);
856
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", currentValues.y);
857
+ yogaValues.setProperty(uiRuntime, "currentWidth", currentValues.width);
858
+ yogaValues.setProperty(uiRuntime, "currentHeight", currentValues.height);
859
+ yogaValues.setProperty(uiRuntime, "targetOriginX", targetValues.x);
860
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", targetValues.x);
861
+ yogaValues.setProperty(uiRuntime, "targetOriginY", targetValues.y);
862
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", targetValues.y);
863
+ yogaValues.setProperty(uiRuntime, "targetWidth", targetValues.width);
864
+ yogaValues.setProperty(uiRuntime, "targetHeight", targetValues.height);
865
+ yogaValues.setProperty(uiRuntime, "windowWidth", targetValues.windowWidth);
866
+ yogaValues.setProperty(uiRuntime, "windowHeight", targetValues.windowHeight);
867
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
868
+ uiRuntime, tag, LayoutAnimationType::LAYOUT, yogaValues);
869
+ });
751
870
  }
752
871
 
753
872
  void LayoutAnimationsProxy_Legacy::updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation)
@@ -757,10 +876,17 @@ void LayoutAnimationsProxy_Legacy::updateOngoingAnimationTarget(const int tag, c
757
876
  }
758
877
 
759
878
  void LayoutAnimationsProxy_Legacy::maybeCancelAnimation(const int tag) const {
760
- if (!layoutAnimations_.contains(tag)) {
879
+ #ifdef ANDROID
880
+ // invalidate animation starts that are scheduled but haven't run yet
881
+ if (const auto it = pendingStarts_.find(tag); it != pendingStarts_.end()) {
882
+ it->second.handle++;
883
+ }
884
+ #endif
885
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
886
+ if (layoutAnimationIt == layoutAnimations_.end()) {
761
887
  return;
762
888
  }
763
- layoutAnimations_.erase(tag);
889
+ layoutAnimations_.erase(layoutAnimationIt);
764
890
  scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag]() {
765
891
  auto strongThis = weakThis.lock();
766
892
  if (!strongThis) {
@@ -148,6 +148,10 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
148
148
  std::optional<SurfaceId> endLayoutAnimation(int tag, bool shouldRemove) override;
149
149
  void maybeCancelAnimation(const int tag) const;
150
150
 
151
+ void reconcileContradictedRemovals(
152
+ ShadowViewMutationList &mutations,
153
+ ShadowViewMutationList &filteredMutations,
154
+ SurfaceId surfaceId) const;
151
155
  void parseRemoveMutations(
152
156
  std::unordered_map<Tag, Tag> &movedViews,
153
157
  ShadowViewMutationList &mutations,
@@ -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);
@@ -322,9 +322,9 @@ void ReanimatedModuleProxy::init(const PlatformDepMethodsHolder &platformDepMeth
322
322
  if (!surfaceId) {
323
323
  return;
324
324
  }
325
+ // The flush set is drained by `executeLayoutAnimationsRequests`, driven
326
+ // by `runGrandCallback` (backend) or a JS `requestAnimationFrameFinalizer` (non-backend).
325
327
  strongThis->layoutAnimationFlushRequests_.insert(*surfaceId);
326
-
327
- strongThis->requestRenderForLayoutAnimations();
328
328
  };
329
329
 
330
330
  EndLayoutAnimationFunction endLayoutAnimation = [weakThis = weak_from_this()](int tag, bool shouldRemove) {
@@ -335,14 +335,6 @@ void ReanimatedModuleProxy::init(const PlatformDepMethodsHolder &platformDepMeth
335
335
  }
336
336
 
337
337
  auto surfaceId = strongThis->layoutAnimationsProxy_->endLayoutAnimation(tag, shouldRemove);
338
-
339
- if constexpr (StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND")) {
340
- // in the backend path this is called from runGrandCallback,
341
- // we are guaranteed to have the changes flushed
342
- } else {
343
- strongThis->requestRenderForLayoutAnimations();
344
- }
345
-
346
338
  if (!surfaceId) {
347
339
  return;
348
340
  }
@@ -649,16 +641,14 @@ jsi::Value ReanimatedModuleProxy::getSettledUpdates(jsi::Runtime &rt) {
649
641
  StaticFeatureFlags::getFlag("FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS") &&
650
642
  "getSettledUpdates requires FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS static feature flag to be enabled");
651
643
 
644
+ constexpr double SETTLED_ANIMATION_THRESHOLD_MS = 1000;
645
+
652
646
  // TODO(future): use unified timestamp
653
647
  const auto currentTimestamp = getAnimationTimestamp_();
654
648
 
655
- // TODO: fix bug when threshold difference is smaller than 1 second
656
649
  // TODO(future): flush updates from CSS animations and CSS transitions registries
657
- // TODO(future): find a better way to obtain timestamp for removing updates
658
- // TODO(future): move removing old updates to separate method
659
650
  auto lock = updatesRegistryManager_->lock();
660
- return animatedPropsRegistry_->getUpdatesOlderThanTimestamp(
661
- rt, currentTimestamp - 1000 /* 1 second */, currentTimestamp - 2000 /* 2 seconds */);
651
+ return animatedPropsRegistry_->collectSettledUpdates(rt, currentTimestamp - SETTLED_ANIMATION_THRESHOLD_MS);
662
652
  }
663
653
 
664
654
  bool ReanimatedModuleProxy::handleEvent(
@@ -808,11 +798,6 @@ void ReanimatedModuleProxy::performOperations() {
808
798
  }
809
799
 
810
800
  commitUpdates(uiRuntime, updatesBatch);
811
-
812
- // Clear the entire cache after the commit
813
- // (we don't know if the view is updated from outside of Reanimated
814
- // so we have to clear the entire cache)
815
- viewStylesRepository_->clearNodesCache();
816
801
  }
817
802
 
818
803
  void ReanimatedModuleProxy::performNonLayoutOperations() {
@@ -1206,7 +1191,8 @@ void ReanimatedModuleProxy::initializeFabric(const std::shared_ptr<UIManager> &u
1206
1191
  // TODO: we don't use the mount hook here, but we still need a way to handleNodeRemovals
1207
1192
  // for now we leave this to leak the memory, a fix will come in a follow-up
1208
1193
  } else {
1209
- mountHook_ = std::make_shared<ReanimatedMountHook>(uiManager_, updatesRegistryManager_, request);
1194
+ mountHook_ =
1195
+ std::make_shared<ReanimatedMountHook>(uiManager_, updatesRegistryManager_, viewStylesRepository_, request);
1210
1196
  }
1211
1197
 
1212
1198
  commitHook_ = std::make_shared<ReanimatedCommitHook>(uiManager_, updatesRegistryManager_, layoutAnimationsProxy_);
@@ -201,20 +201,6 @@ class ReanimatedModuleProxy : public std::enable_shared_from_this<ReanimatedModu
201
201
  void commitUpdates(jsi::Runtime &rt, const UpdatesBatch &updatesBatch);
202
202
  void applySynchronousUpdates(UpdatesBatch &updatesBatch, bool allowPartialUpdates);
203
203
 
204
- /** Use only on the UI thread. */
205
- void requestRenderForLayoutAnimations() {
206
- if (layoutAnimationRenderRequested_) [[likely]] {
207
- return;
208
- }
209
-
210
- layoutAnimationRenderRequested_ = true;
211
- requestRender_([weakThis = weak_from_this()](double) {
212
- if (auto strongThis = weakThis.lock()) {
213
- strongThis->layoutAnimationRenderRequested_ = false;
214
- }
215
- });
216
- }
217
-
218
204
  #if REACT_NATIVE_VERSION_MINOR >= 85
219
205
  std::shared_ptr<UIManagerAnimationBackend> getAnimationBackend();
220
206
  AnimationMutations runGrandCallback(AnimationTimestamp timestamp, GrandCallbackSource source);
@@ -279,8 +265,6 @@ class ReanimatedModuleProxy : public std::enable_shared_from_this<ReanimatedModu
279
265
  std::shared_ptr<ReanimatedMountHook> mountHook_;
280
266
  /// Access only on UI thread.
281
267
  std::set<SurfaceId> layoutAnimationFlushRequests_;
282
- /// Access only on UI thread.
283
- bool layoutAnimationRenderRequested_;
284
268
 
285
269
  const KeyboardEventSubscribeFunction subscribeForKeyboardEventsFunction_;
286
270
  const KeyboardEventUnsubscribeFunction unsubscribeFromKeyboardEventsFunction_;
@@ -49,6 +49,12 @@ add_library(reanimated SHARED ${REANIMATED_COMMON_CPP_SOURCES}
49
49
  target_precompile_headers(reanimated PRIVATE
50
50
  "${ANDROID_CPP_DIR}/ReanimatedPCH.h")
51
51
 
52
+ # Stop Clang from embedding a build timestamp in the .pch. Without this the PCH
53
+ # is non-reproducible across builds, so ccache can't reuse it and translation
54
+ # units reject a restored PCH as "modified since built".
55
+ target_compile_options(reanimated PRIVATE
56
+ "$<$<COMPILE_LANGUAGE:CXX>:-Xclang;-fno-pch-timestamp>")
57
+
52
58
  if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
53
59
  include(
54
60
  "${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake")
@@ -100,6 +100,15 @@ fun validateConflictingFeatureFlags(featureFlags: HashMap<String, String>) {
100
100
  "[Reanimated] The feature flags `ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS` and `ENABLE_SHARED_ELEMENT_TRANSITIONS` cannot be enabled simultaneously. Please disable one of them in your package.json."
101
101
  )
102
102
  }
103
+
104
+ val useAnimationBackend = featureFlags["USE_ANIMATION_BACKEND"] == "true"
105
+ val forceReactRenderForSettledAnimations = featureFlags["FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS"] == "true"
106
+
107
+ if (useAnimationBackend && forceReactRenderForSettledAnimations) {
108
+ throw GradleException(
109
+ "[Reanimated] The feature flags `USE_ANIMATION_BACKEND` and `FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS` cannot be enabled simultaneously. If you want to use the animation backend, you need to explicitly disable `FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS` feature flag (enabled by default) in your package.json."
110
+ )
111
+ }
103
112
  }
104
113
 
105
114
  if (project != rootProject) {
@@ -109,6 +118,12 @@ if (project != rootProject) {
109
118
  val packageDir: File = project.projectDir.parentFile
110
119
  val reactNativeRootDir: File = resolveReactNativeDirectory()
111
120
  val REACT_NATIVE_VERSION: String = getReactNativeVersion()
121
+ val IS_REACT_NATIVE_86_OR_NEWER: Boolean = run {
122
+ val parts = REACT_NATIVE_VERSION.split(".")
123
+ val major = parts.getOrNull(0)?.toIntOrNull() ?: 0
124
+ val minor = parts.getOrNull(1)?.toIntOrNull() ?: 0
125
+ major > 0 || minor >= 86
126
+ }
112
127
  val REANIMATED_VERSION: String = getReanimatedVersion()
113
128
  val IS_REANIMATED_EXAMPLE_APP: Boolean = safeAppExtGet("isReanimatedExampleApp", false)?.toString()?.toBoolean() ?: false
114
129
  val REANIMATED_PROFILING: Boolean = safeAppExtGet("enableReanimatedProfiling", false)?.toString()?.toBoolean() ?: false
@@ -121,7 +136,11 @@ val reanimatedPrefabHeadersDir: File = project.file("${layout.buildDirectory.get
121
136
 
122
137
  fun reactNativeArchitectures(): List<String> {
123
138
  val value = project.findProperty("reactNativeArchitectures") as String?
124
- return value?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
139
+ return value?.split(",")
140
+ ?.map { it.trim() }
141
+ ?.filter { it.isNotEmpty() }
142
+ ?.ifEmpty { null }
143
+ ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
125
144
  }
126
145
 
127
146
  if (project == rootProject) {
@@ -168,6 +187,7 @@ android {
168
187
  buildConfigField("String", "REANIMATED_VERSION_JAVA", "\"$REANIMATED_VERSION\"")
169
188
  buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
170
189
  buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
190
+ buildConfigField("boolean", "IS_REACT_NATIVE_86_OR_NEWER", IS_REACT_NATIVE_86_OR_NEWER.toString())
171
191
 
172
192
  @Suppress("UnstableApiUsage")
173
193
  externalNativeBuild {