react-native-reanimated 4.3.0 → 4.3.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 (92) hide show
  1. package/Common/cpp/reanimated/CSS/interpolation/transforms/TransformOperationInterpolator.cpp +12 -5
  2. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.cpp +59 -37
  3. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.h +11 -8
  4. package/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp +11 -3
  5. package/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.h +9 -2
  6. package/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +6 -2
  7. package/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +8 -2
  8. package/Common/cpp/reanimated/CSS/registries/StaticPropsRegistry.cpp +17 -4
  9. package/Common/cpp/reanimated/CSS/registries/StaticPropsRegistry.h +5 -0
  10. package/Common/cpp/reanimated/CSS/svg/values/SVGStrokeDashArray.cpp +5 -0
  11. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +25 -12
  12. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
  13. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp +12 -5
  14. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h +6 -3
  15. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.cpp +4 -6
  16. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.h +25 -7
  17. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +38 -0
  18. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +106 -33
  19. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +165 -92
  20. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +12 -34
  21. package/README.md +1 -1
  22. package/android/CMakeLists.txt +4 -1
  23. package/android/build.gradle +2 -0
  24. package/android/generate-stub-pch.gradle.kts +84 -0
  25. package/android/src/main/cpp/ReanimatedPCH.h +46 -0
  26. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.java +14 -1
  27. package/android/src/main/java/com/swmansion/reanimated/keyboard/KeyboardAnimationCallback.java +9 -1
  28. package/apple/reanimated/apple/ReanimatedModule.mm +12 -1
  29. package/lib/module/common/constants/platform.js +5 -5
  30. package/lib/module/common/constants/platform.js.map +1 -1
  31. package/lib/module/createAnimatedComponent/AnimatedComponent.js +1 -7
  32. package/lib/module/createAnimatedComponent/AnimatedComponent.js.map +1 -1
  33. package/lib/module/createAnimatedComponent/getViewInfo.js +3 -1
  34. package/lib/module/createAnimatedComponent/getViewInfo.js.map +1 -1
  35. package/lib/module/css/component/AnimatedComponent.js +0 -2
  36. package/lib/module/css/component/AnimatedComponent.js.map +1 -1
  37. package/lib/module/css/native/managers/CSSManager.js +16 -7
  38. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  39. package/lib/module/css/native/managers/CSSTransitionsManager.js +8 -1
  40. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  41. package/lib/module/css/svg/native/processors/stroke.js +9 -5
  42. package/lib/module/css/svg/native/processors/stroke.js.map +1 -1
  43. package/lib/module/css/utils/props.js +6 -0
  44. package/lib/module/css/utils/props.js.map +1 -1
  45. package/lib/module/hook/useAnimatedRef.js +2 -2
  46. package/lib/module/hook/useAnimatedRef.js.map +1 -1
  47. package/lib/module/mutables.js +5 -1
  48. package/lib/module/mutables.js.map +1 -1
  49. package/lib/module/platform-specific/findHostInstance.js +8 -5
  50. package/lib/module/platform-specific/findHostInstance.js.map +1 -1
  51. package/lib/module/platform-specific/jsVersion.js +1 -1
  52. package/lib/module/updateProps/updateProps.js +3 -2
  53. package/lib/module/updateProps/updateProps.js.map +1 -1
  54. package/lib/module/valueSetter.js +1 -1
  55. package/lib/module/valueSetter.js.map +1 -1
  56. package/lib/typescript/common/constants/platform.d.ts.map +1 -1
  57. package/lib/typescript/createAnimatedComponent/AnimatedComponent.d.ts.map +1 -1
  58. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +0 -1
  59. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  60. package/lib/typescript/createAnimatedComponent/getViewInfo.d.ts.map +1 -1
  61. package/lib/typescript/css/component/AnimatedComponent.d.ts +0 -1
  62. package/lib/typescript/css/component/AnimatedComponent.d.ts.map +1 -1
  63. package/lib/typescript/css/native/managers/CSSManager.d.ts +7 -0
  64. package/lib/typescript/css/native/managers/CSSManager.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/css/utils/props.d.ts.map +1 -1
  69. package/lib/typescript/mutables.d.ts.map +1 -1
  70. package/lib/typescript/platform-specific/findHostInstance.d.ts.map +1 -1
  71. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  72. package/lib/typescript/platform-specific/types.d.ts +1 -0
  73. package/lib/typescript/platform-specific/types.d.ts.map +1 -1
  74. package/lib/typescript/updateProps/updateProps.d.ts.map +1 -1
  75. package/lib/typescript/valueSetter.d.ts.map +1 -1
  76. package/package.json +5 -3
  77. package/src/common/constants/platform.ts +6 -5
  78. package/src/createAnimatedComponent/AnimatedComponent.tsx +1 -7
  79. package/src/createAnimatedComponent/commonTypes.ts +0 -1
  80. package/src/createAnimatedComponent/getViewInfo.ts +3 -1
  81. package/src/css/component/AnimatedComponent.tsx +0 -2
  82. package/src/css/native/managers/CSSManager.ts +33 -12
  83. package/src/css/native/managers/CSSTransitionsManager.ts +9 -2
  84. package/src/css/svg/native/processors/stroke.ts +9 -6
  85. package/src/css/utils/props.ts +7 -0
  86. package/src/hook/useAnimatedRef.ts +2 -2
  87. package/src/mutables.ts +5 -1
  88. package/src/platform-specific/findHostInstance.ts +7 -7
  89. package/src/platform-specific/jsVersion.ts +1 -1
  90. package/src/platform-specific/types.ts +2 -0
  91. package/src/updateProps/updateProps.ts +3 -2
  92. package/src/valueSetter.ts +2 -1
@@ -1,11 +1,11 @@
1
1
  #pragma once
2
2
 
3
- #include <reanimated/Fabric/ShadowTreeCloner.h>
4
-
3
+ #include <jsi/jsi.h>
5
4
  #include <react/renderer/core/ShadowNode.h>
5
+ #include <reanimated/Fabric/ShadowTreeCloner.h>
6
6
 
7
- #include <jsi/jsi.h>
8
7
  #include <memory>
8
+ #include <mutex>
9
9
  #include <string>
10
10
  #include <unordered_map>
11
11
  #include <unordered_set>
@@ -33,18 +33,23 @@ class UpdatesRegistry {
33
33
  public:
34
34
  virtual ~UpdatesRegistry() {}
35
35
 
36
- std::lock_guard<std::mutex> lock() const;
37
-
38
36
  virtual bool isEmpty() const;
39
37
  folly::dynamic get(Tag tag) const;
40
- virtual void remove(Tag tag) = 0;
38
+ void remove(Tag tag) {
39
+ std::lock_guard<std::mutex> lock{mutex_};
40
+ removeTag(tag);
41
+ }
41
42
 
42
43
  #ifdef ANDROID
43
44
  bool hasPropsToRevert() const;
44
45
  void collectPropsToRevert(PropsToRevertMap &propsToRevertMap);
45
46
  #endif
46
47
 
47
- void flushUpdates(UpdatesBatch &updatesBatch);
48
+ void flushUpdates(UpdatesBatch &updatesBatch) {
49
+ std::lock_guard<std::mutex> lock{mutex_};
50
+ flush(updatesBatch);
51
+ }
52
+
48
53
  void collectProps(PropsMap &propsMap);
49
54
  UpdatesBatch getPendingUpdates();
50
55
 
@@ -52,9 +57,22 @@ class UpdatesRegistry {
52
57
  mutable std::mutex mutex_;
53
58
  RegistryMap updatesRegistry_;
54
59
 
60
+ /// Assumes the caller already locked the registry.
61
+ void flush(UpdatesBatch &updatesBatch);
62
+
63
+ /// Assumes the caller already locked the registry.
64
+ virtual void removeTag(Tag tag) = 0;
65
+
66
+ /// Assumes the caller already locked the registry.
55
67
  void addUpdatesToBatch(const std::shared_ptr<const ShadowNode> &shadowNode, const folly::dynamic &props);
68
+
69
+ /// Assumes the caller already locked the registry.
56
70
  folly::dynamic getUpdatesFromRegistry(const Tag tag) const;
71
+
72
+ /// Assumes the caller already locked the registry.
57
73
  void setInUpdatesRegistry(const std::shared_ptr<const ShadowNode> &shadowNode, const folly::dynamic &props);
74
+
75
+ /// Assumes the caller already locked the registry.
58
76
  void removeFromUpdatesRegistry(Tag tag);
59
77
 
60
78
  private:
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <jsi/jsi.h>
4
+ #include <react/debug/react_native_assert.h>
4
5
  #include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
5
6
  #include <react/renderer/mounting/MountingOverrideDelegate.h>
6
7
  #include <react/renderer/uimanager/UIManager.h>
@@ -24,6 +25,31 @@ struct LayoutAnimation {
24
25
  LayoutAnimation &operator=(const LayoutAnimation &other) = default;
25
26
  };
26
27
 
28
+ #ifdef ANDROID
29
+ // Bookkeeping for animation starts that were scheduled onto the UI thread but
30
+ // haven't run yet — see `pendingStarts_` below.
31
+ struct PendingStart {
32
+ int count = 0;
33
+ uint64_t handle = 0;
34
+ };
35
+
36
+ // Removes one pending start for the given tag and returns whether it was
37
+ // cancelled since it was scheduled (i.e. its handle is no longer current).
38
+ // Call under the proxy mutex.
39
+ inline bool
40
+ consumeIsCancelled(std::unordered_map<Tag, PendingStart> &pendingStarts, const Tag tag, const uint64_t handle) {
41
+ const auto it = pendingStarts.find(tag);
42
+ // every scheduled start keeps its entry alive until it is consumed —
43
+ // cancellations only bump the handle, they never erase
44
+ react_native_assert(it != pendingStarts.end() && "PendingStart not found");
45
+ const bool isCancelled = it->second.handle != handle;
46
+ if (--it->second.count == 0) {
47
+ pendingStarts.erase(it);
48
+ }
49
+ return isCancelled;
50
+ }
51
+ #endif
52
+
27
53
  class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDelegate {
28
54
  public:
29
55
  LayoutAnimationsProxyCommon(
@@ -75,6 +101,18 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
75
101
 
76
102
  void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const;
77
103
 
104
+ // On Android pullTransaction can run on the JS thread, so animation starts
105
+ // are scheduled onto the UI thread. If `maybeCancelAnimation` is called between the
106
+ // start was scheduled and the lambda runs, it
107
+ // finds no `layoutAnimations_` entry to erase (the start lambda hasn't created it
108
+ // yet) and the cancellation is lost — the stale start would later
109
+ // "resurrect" the animation for a view whose Remove+Delete are already on
110
+ // their way to the mounting layer
111
+ // (https://github.com/software-mansion/react-native-reanimated/issues/7493).
112
+
113
+ // To work around this, we keep a separate `pendingStarts_` map that tracks scheduled starts by tag,
114
+ // with a generation counter to detect cancellations.
115
+ mutable std::unordered_map<Tag, PendingStart> pendingStarts_;
78
116
  #endif
79
117
  };
80
118
 
@@ -518,11 +518,10 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively(
518
518
 
519
519
  if (hasExitAnimation) {
520
520
  node->state = ANIMATING;
521
- startExitingAnimation(node);
522
521
  lightNodes_[node->current.tag] = node;
522
+ startExitingAnimation(node);
523
523
  } else {
524
- // TODO (future): add proper cleanup
525
- // layoutAnimationsManager_->clearLayoutAnimationConfig(node->tag);
524
+ layoutAnimationsManager_->clearLayoutAnimationConfig(node->current.tag);
526
525
  }
527
526
 
528
527
  return wantAnimateExit;
@@ -534,6 +533,13 @@ void LayoutAnimationsProxy_Experimental::updateOngoingAnimationTarget(const int
534
533
  }
535
534
 
536
535
  void LayoutAnimationsProxy_Experimental::maybeCancelAnimation(const int tag) const {
536
+ #ifdef ANDROID
537
+ // also invalidate animation starts that are scheduled but haven't run yet,
538
+ // so they don't re-create the animation after this cancellation
539
+ if (const auto it = pendingStarts_.find(tag); it != pendingStarts_.end()) {
540
+ it->second.handle++;
541
+ }
542
+ #endif
537
543
  if (!layoutAnimations_.contains(tag)) {
538
544
  return;
539
545
  }
@@ -678,9 +684,24 @@ void LayoutAnimationsProxy_Experimental::startEnteringAnimation(const std::share
678
684
  const auto &parent = node->parent.lock();
679
685
  react_native_assert(parent && "Parent node is nullptr");
680
686
  const auto parentTag = parent->current.tag;
687
+ #ifdef ANDROID
688
+ const auto handle = pendingStarts_[newChildShadowView.tag].handle;
689
+ pendingStarts_[newChildShadowView.tag].count++;
690
+ #endif
681
691
 
682
692
  scheduleOnUI(
683
- uiScheduler_, [weakThis = weak_from_this(), finalView, currentView, newChildShadowView, parentTag, opacity]() {
693
+ uiScheduler_,
694
+ [weakThis = weak_from_this(),
695
+ finalView,
696
+ currentView,
697
+ newChildShadowView,
698
+ parentTag,
699
+ opacity
700
+ #ifdef ANDROID
701
+ ,
702
+ handle
703
+ #endif
704
+ ]() {
684
705
  auto strongThis = weakThis.lock();
685
706
  if (!strongThis) {
686
707
  return;
@@ -690,6 +711,12 @@ void LayoutAnimationsProxy_Experimental::startEnteringAnimation(const std::share
690
711
  const auto tag = newChildShadowView.tag;
691
712
  {
692
713
  auto lock = std::unique_lock<std::recursive_mutex>(strongThis->mutex);
714
+ #ifdef ANDROID
715
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
716
+ // the view was removed before this start could run
717
+ return;
718
+ }
719
+ #endif
693
720
  strongThis->layoutAnimations_[tag] = {
694
721
  .finalView = newChildShadowView,
695
722
  .currentView = newChildShadowView,
@@ -723,38 +750,63 @@ void LayoutAnimationsProxy_Experimental::startExitingAnimation(const std::shared
723
750
  const auto &parent = node->parent.lock();
724
751
  react_native_assert(parent && "Parent node is nullptr");
725
752
  const auto parentTag = parent->current.tag;
753
+ #ifdef ANDROID
754
+ const auto handle = pendingStarts_[tag].handle;
755
+ pendingStarts_[tag].count++;
756
+ #endif
726
757
 
727
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag, parentTag, oldChildShadowView, surfaceId]() {
728
- auto strongThis = weakThis.lock();
729
- if (!strongThis) {
730
- return;
731
- }
758
+ scheduleOnUI(
759
+ uiScheduler_,
760
+ [weakThis = weak_from_this(),
761
+ tag,
762
+ parentTag,
763
+ oldChildShadowView,
764
+ surfaceId
765
+ #ifdef ANDROID
766
+ ,
767
+ handle
768
+ #endif
769
+ ]() {
770
+ auto strongThis = weakThis.lock();
771
+ if (!strongThis) {
772
+ return;
773
+ }
732
774
 
733
- auto oldView = oldChildShadowView;
734
- Rect window{};
735
- {
736
- auto &mutex = strongThis->mutex;
737
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
738
- oldView = strongThis->maybeCreateLayoutAnimation(oldView, oldView, parentTag);
739
- window = strongThis->surfaceManager.getWindow(surfaceId);
740
- }
775
+ auto oldView = oldChildShadowView;
776
+ Rect window{};
777
+ {
778
+ auto &mutex = strongThis->mutex;
779
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
780
+ #ifdef ANDROID
781
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
782
+ // the view was removed (e.g. its subtree was force-ended by a screen
783
+ // pop) before this start could run — its Remove+Delete are already on
784
+ // their way to the mounting layer, so starting the animation now
785
+ // would emit updates for a view that's about to be deleted
786
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
787
+ return;
788
+ }
789
+ #endif
790
+ oldView = strongThis->maybeCreateLayoutAnimation(oldView, oldView, parentTag);
791
+ window = strongThis->surfaceManager.getWindow(surfaceId);
792
+ }
741
793
 
742
- const Snapshot values(oldView, window);
794
+ const Snapshot values(oldView, window);
743
795
 
744
- auto &uiRuntime = strongThis->uiRuntime_;
745
- const jsi::Object yogaValues(uiRuntime);
746
- yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
747
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
748
- yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
749
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
750
- yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
751
- yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
752
- yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
753
- yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
754
- strongThis->layoutAnimationsManager_->startLayoutAnimation(
755
- uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
756
- strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
757
- });
796
+ auto &uiRuntime = strongThis->uiRuntime_;
797
+ const jsi::Object yogaValues(uiRuntime);
798
+ yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
799
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
800
+ yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
801
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
802
+ yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
803
+ yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
804
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
805
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
806
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
807
+ uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
808
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
809
+ });
758
810
  }
759
811
 
760
812
  void LayoutAnimationsProxy_Experimental::startLayoutAnimation(const std::shared_ptr<LightNode> &node) const {
@@ -765,9 +817,24 @@ void LayoutAnimationsProxy_Experimental::startLayoutAnimation(const std::shared_
765
817
  const auto &parent = node->parent.lock();
766
818
  react_native_assert(parent && "Parent node is nullptr");
767
819
  const auto parentTag = parent->current.tag;
820
+ #ifdef ANDROID
821
+ const auto handle = pendingStarts_[tag].handle;
822
+ pendingStarts_[tag].count++;
823
+ #endif
768
824
 
769
825
  scheduleOnUI(
770
- uiScheduler_, [weakThis = weak_from_this(), surfaceId, oldChildShadowView, newChildShadowView, parentTag, tag]() {
826
+ uiScheduler_,
827
+ [weakThis = weak_from_this(),
828
+ surfaceId,
829
+ oldChildShadowView,
830
+ newChildShadowView,
831
+ parentTag,
832
+ tag
833
+ #ifdef ANDROID
834
+ ,
835
+ handle
836
+ #endif
837
+ ]() {
771
838
  auto strongThis = weakThis.lock();
772
839
  if (!strongThis) {
773
840
  return;
@@ -778,6 +845,12 @@ void LayoutAnimationsProxy_Experimental::startLayoutAnimation(const std::shared_
778
845
  {
779
846
  auto &mutex = strongThis->mutex;
780
847
  auto lock = std::unique_lock<std::recursive_mutex>(mutex);
848
+ #ifdef ANDROID
849
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
850
+ // the view was removed before this start could run
851
+ return;
852
+ }
853
+ #endif
781
854
  oldView = strongThis->maybeCreateLayoutAnimation(oldView, newChildShadowView, parentTag);
782
855
  window = strongThis->surfaceManager.getWindow(surfaceId);
783
856
  }
@@ -635,38 +635,60 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
635
635
 
636
636
  auto &viewProps = static_cast<const ViewProps &>(*mutation.newChildShadowView.props);
637
637
  auto opacity = viewProps.opacity;
638
+ #ifdef ANDROID
639
+ const auto handle = pendingStarts_[tag].handle;
640
+ pendingStarts_[tag].count++;
641
+ #endif
638
642
 
639
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), finalView, current, mutation, opacity, tag]() {
640
- auto strongThis = weakThis.lock();
641
- if (!strongThis) {
642
- return;
643
- }
643
+ scheduleOnUI(
644
+ uiScheduler_,
645
+ [weakThis = weak_from_this(),
646
+ finalView,
647
+ current,
648
+ mutation,
649
+ opacity,
650
+ tag
651
+ #ifdef ANDROID
652
+ ,
653
+ handle
654
+ #endif
655
+ ]() {
656
+ auto strongThis = weakThis.lock();
657
+ if (!strongThis) {
658
+ return;
659
+ }
644
660
 
645
- Rect window{};
646
- {
647
- auto &mutex = strongThis->mutex;
648
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
649
- strongThis->layoutAnimations_.insert_or_assign(
650
- tag,
651
- LayoutAnimation{
652
- .finalView = finalView, .currentView = current, .parentTag = mutation.parentTag, .opacity = opacity});
653
- window = strongThis->surfaceManager.getWindow(mutation.newChildShadowView.surfaceId);
654
- }
661
+ Rect window{};
662
+ {
663
+ auto &mutex = strongThis->mutex;
664
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
665
+ #ifdef ANDROID
666
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
667
+ // the view was removed before this start could run
668
+ return;
669
+ }
670
+ #endif
671
+ strongThis->layoutAnimations_.insert_or_assign(
672
+ tag,
673
+ LayoutAnimation{
674
+ .finalView = finalView, .currentView = current, .parentTag = mutation.parentTag, .opacity = opacity});
675
+ window = strongThis->surfaceManager.getWindow(mutation.newChildShadowView.surfaceId);
676
+ }
655
677
 
656
- Snapshot values(mutation.newChildShadowView, window);
657
- auto &uiRuntime = strongThis->uiRuntime_;
658
- jsi::Object yogaValues(uiRuntime);
659
- yogaValues.setProperty(uiRuntime, "targetOriginX", values.x);
660
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", values.x);
661
- yogaValues.setProperty(uiRuntime, "targetOriginY", values.y);
662
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", values.y);
663
- yogaValues.setProperty(uiRuntime, "targetWidth", values.width);
664
- yogaValues.setProperty(uiRuntime, "targetHeight", values.height);
665
- yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
666
- yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
667
- strongThis->layoutAnimationsManager_->startLayoutAnimation(
668
- uiRuntime, tag, LayoutAnimationType::ENTERING, yogaValues);
669
- });
678
+ Snapshot values(mutation.newChildShadowView, window);
679
+ auto &uiRuntime = strongThis->uiRuntime_;
680
+ jsi::Object yogaValues(uiRuntime);
681
+ yogaValues.setProperty(uiRuntime, "targetOriginX", values.x);
682
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", values.x);
683
+ yogaValues.setProperty(uiRuntime, "targetOriginY", values.y);
684
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", values.y);
685
+ yogaValues.setProperty(uiRuntime, "targetWidth", values.width);
686
+ yogaValues.setProperty(uiRuntime, "targetHeight", values.height);
687
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
688
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
689
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
690
+ uiRuntime, tag, LayoutAnimationType::ENTERING, yogaValues);
691
+ });
670
692
  }
671
693
 
672
694
  void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowViewMutation &mutation) const {
@@ -674,38 +696,62 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
674
696
  LOG(INFO) << "start exiting animation for tag " << tag << std::endl;
675
697
  #endif
676
698
  auto surfaceId = mutation.oldChildShadowView.surfaceId;
699
+ #ifdef ANDROID
700
+ const auto handle = pendingStarts_[tag].handle;
701
+ pendingStarts_[tag].count++;
702
+ #endif
677
703
 
678
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), tag, mutation, surfaceId]() {
679
- auto strongThis = weakThis.lock();
680
- if (!strongThis) {
681
- return;
682
- }
683
-
684
- auto oldView = mutation.oldChildShadowView;
685
- Rect window{};
686
- {
687
- auto &mutex = strongThis->mutex;
688
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
689
- strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
690
- window = strongThis->surfaceManager.getWindow(surfaceId);
691
- }
704
+ scheduleOnUI(
705
+ uiScheduler_,
706
+ [weakThis = weak_from_this(),
707
+ tag,
708
+ mutation,
709
+ surfaceId
710
+ #ifdef ANDROID
711
+ ,
712
+ handle
713
+ #endif
714
+ ]() {
715
+ auto strongThis = weakThis.lock();
716
+ if (!strongThis) {
717
+ return;
718
+ }
692
719
 
693
- Snapshot values(oldView, window);
720
+ auto oldView = mutation.oldChildShadowView;
721
+ Rect window{};
722
+ {
723
+ auto &mutex = strongThis->mutex;
724
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
725
+ #ifdef ANDROID
726
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
727
+ // the view was removed (e.g. its subtree was force-ended by a screen
728
+ // pop) before this start could run — its Remove+Delete are already on
729
+ // their way to the mounting layer, so starting the animation now
730
+ // would emit updates for a view that's about to be deleted
731
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
732
+ return;
733
+ }
734
+ #endif
735
+ strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
736
+ window = strongThis->surfaceManager.getWindow(surfaceId);
737
+ }
694
738
 
695
- auto &uiRuntime = strongThis->uiRuntime_;
696
- jsi::Object yogaValues(uiRuntime);
697
- yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
698
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
699
- yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
700
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
701
- yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
702
- yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
703
- yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
704
- yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
705
- strongThis->layoutAnimationsManager_->startLayoutAnimation(
706
- uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
707
- strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
708
- });
739
+ Snapshot values(oldView, window);
740
+
741
+ auto &uiRuntime = strongThis->uiRuntime_;
742
+ jsi::Object yogaValues(uiRuntime);
743
+ yogaValues.setProperty(uiRuntime, "currentOriginX", values.x);
744
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", values.x);
745
+ yogaValues.setProperty(uiRuntime, "currentOriginY", values.y);
746
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", values.y);
747
+ yogaValues.setProperty(uiRuntime, "currentWidth", values.width);
748
+ yogaValues.setProperty(uiRuntime, "currentHeight", values.height);
749
+ yogaValues.setProperty(uiRuntime, "windowWidth", values.windowWidth);
750
+ yogaValues.setProperty(uiRuntime, "windowHeight", values.windowHeight);
751
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
752
+ uiRuntime, tag, LayoutAnimationType::EXITING, yogaValues);
753
+ strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
754
+ });
709
755
  }
710
756
 
711
757
  void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const ShadowViewMutation &mutation) const {
@@ -713,43 +759,64 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
713
759
  LOG(INFO) << "start layout animation for tag " << tag << std::endl;
714
760
  #endif
715
761
  auto surfaceId = mutation.oldChildShadowView.surfaceId;
762
+ #ifdef ANDROID
763
+ const auto handle = pendingStarts_[tag].handle;
764
+ pendingStarts_[tag].count++;
765
+ #endif
716
766
 
717
- scheduleOnUI(uiScheduler_, [weakThis = weak_from_this(), mutation, surfaceId, tag]() {
718
- auto strongThis = weakThis.lock();
719
- if (!strongThis) {
720
- return;
721
- }
722
-
723
- auto oldView = mutation.oldChildShadowView;
724
- Rect window{};
725
- {
726
- auto &mutex = strongThis->mutex;
727
- auto lock = std::unique_lock<std::recursive_mutex>(mutex);
728
- strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
729
- window = strongThis->surfaceManager.getWindow(surfaceId);
730
- }
767
+ scheduleOnUI(
768
+ uiScheduler_,
769
+ [weakThis = weak_from_this(),
770
+ mutation,
771
+ surfaceId,
772
+ tag
773
+ #ifdef ANDROID
774
+ ,
775
+ handle
776
+ #endif
777
+ ]() {
778
+ auto strongThis = weakThis.lock();
779
+ if (!strongThis) {
780
+ return;
781
+ }
731
782
 
732
- Snapshot currentValues(oldView, window);
733
- Snapshot targetValues(mutation.newChildShadowView, window);
783
+ auto oldView = mutation.oldChildShadowView;
784
+ Rect window{};
785
+ {
786
+ auto &mutex = strongThis->mutex;
787
+ auto lock = std::unique_lock<std::recursive_mutex>(mutex);
788
+ #ifdef ANDROID
789
+ if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
790
+ // the view was removed before this start could run
791
+ return;
792
+ }
793
+ #endif
794
+ strongThis->createLayoutAnimation(mutation, oldView, surfaceId, tag);
795
+ window = strongThis->surfaceManager.getWindow(surfaceId);
796
+ }
734
797
 
735
- auto &uiRuntime = strongThis->uiRuntime_;
736
- jsi::Object yogaValues(uiRuntime);
737
- yogaValues.setProperty(uiRuntime, "currentOriginX", currentValues.x);
738
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", currentValues.x);
739
- yogaValues.setProperty(uiRuntime, "currentOriginY", currentValues.y);
740
- yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", currentValues.y);
741
- yogaValues.setProperty(uiRuntime, "currentWidth", currentValues.width);
742
- yogaValues.setProperty(uiRuntime, "currentHeight", currentValues.height);
743
- yogaValues.setProperty(uiRuntime, "targetOriginX", targetValues.x);
744
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", targetValues.x);
745
- yogaValues.setProperty(uiRuntime, "targetOriginY", targetValues.y);
746
- yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", targetValues.y);
747
- yogaValues.setProperty(uiRuntime, "targetWidth", targetValues.width);
748
- yogaValues.setProperty(uiRuntime, "targetHeight", targetValues.height);
749
- yogaValues.setProperty(uiRuntime, "windowWidth", targetValues.windowWidth);
750
- yogaValues.setProperty(uiRuntime, "windowHeight", targetValues.windowHeight);
751
- strongThis->layoutAnimationsManager_->startLayoutAnimation(uiRuntime, tag, LayoutAnimationType::LAYOUT, yogaValues);
752
- });
798
+ Snapshot currentValues(oldView, window);
799
+ Snapshot targetValues(mutation.newChildShadowView, window);
800
+
801
+ auto &uiRuntime = strongThis->uiRuntime_;
802
+ jsi::Object yogaValues(uiRuntime);
803
+ yogaValues.setProperty(uiRuntime, "currentOriginX", currentValues.x);
804
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginX", currentValues.x);
805
+ yogaValues.setProperty(uiRuntime, "currentOriginY", currentValues.y);
806
+ yogaValues.setProperty(uiRuntime, "currentGlobalOriginY", currentValues.y);
807
+ yogaValues.setProperty(uiRuntime, "currentWidth", currentValues.width);
808
+ yogaValues.setProperty(uiRuntime, "currentHeight", currentValues.height);
809
+ yogaValues.setProperty(uiRuntime, "targetOriginX", targetValues.x);
810
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginX", targetValues.x);
811
+ yogaValues.setProperty(uiRuntime, "targetOriginY", targetValues.y);
812
+ yogaValues.setProperty(uiRuntime, "targetGlobalOriginY", targetValues.y);
813
+ yogaValues.setProperty(uiRuntime, "targetWidth", targetValues.width);
814
+ yogaValues.setProperty(uiRuntime, "targetHeight", targetValues.height);
815
+ yogaValues.setProperty(uiRuntime, "windowWidth", targetValues.windowWidth);
816
+ yogaValues.setProperty(uiRuntime, "windowHeight", targetValues.windowHeight);
817
+ strongThis->layoutAnimationsManager_->startLayoutAnimation(
818
+ uiRuntime, tag, LayoutAnimationType::LAYOUT, yogaValues);
819
+ });
753
820
  }
754
821
 
755
822
  void LayoutAnimationsProxy_Legacy::updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation)
@@ -759,6 +826,12 @@ void LayoutAnimationsProxy_Legacy::updateOngoingAnimationTarget(const int tag, c
759
826
  }
760
827
 
761
828
  void LayoutAnimationsProxy_Legacy::maybeCancelAnimation(const int tag) const {
829
+ #ifdef ANDROID
830
+ // invalidate animation starts that are scheduled but haven't run yet
831
+ if (const auto it = pendingStarts_.find(tag); it != pendingStarts_.end()) {
832
+ it->second.handle++;
833
+ }
834
+ #endif
762
835
  if (!layoutAnimations_.contains(tag)) {
763
836
  return;
764
837
  }