react-native-reanimated 4.7.0 → 4.7.1
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.
- package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp +34 -2
- package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.h +15 -1
- package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +9 -0
- package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
- package/Common/cpp/reanimated/Fabric/updates/SynchronousWritesTracker.cpp +103 -0
- package/Common/cpp/reanimated/Fabric/updates/SynchronousWritesTracker.h +74 -0
- package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.cpp +15 -0
- package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.h +5 -0
- package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistryManager.cpp +10 -0
- package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistryManager.h +3 -0
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy.cpp +97 -54
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy.h +5 -1
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.cpp +23 -5
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +17 -1
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +9 -9
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +35 -8
- package/Common/cpp/reanimated/LayoutAnimations/PropsDiffer.h +2 -2
- package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +11 -11
- package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +75 -2
- package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +11 -1
- package/Common/cpp/reanimated/Tools/PlatformDepMethodsHolder.h +4 -1
- package/android/src/main/cpp/reanimated/android/NativeProxy.cpp +13 -3
- package/android/src/main/cpp/reanimated/android/NativeProxy.h +2 -0
- package/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +84 -9
- package/android/src/main/java/com/swmansion/reanimated/NodesManager.kt +2 -4
- package/android/src/main/java/com/swmansion/reanimated/nativeProxy/EventHandler.kt +4 -3
- package/android/src/main/java/com/swmansion/reanimated/sensor/ReanimatedSensor.kt +1 -1
- package/android/src/main/java/com/swmansion/reanimated/sensor/ReanimatedSensorListener.kt +44 -33
- package/apple/reanimated/apple/REASynchronousPropsRewriter.h +18 -0
- package/apple/reanimated/apple/REASynchronousPropsRewriter.mm +37 -0
- package/apple/reanimated/apple/ReanimatedModule.mm +7 -0
- package/apple/reanimated/apple/keyboardObserver/REAKeyboardEventObserver.mm +20 -3
- package/apple/reanimated/apple/native/PlatformDepMethodsHolderImpl.mm +24 -1
- package/lib/module/animation/timing.js +15 -2
- package/lib/module/animation/timing.js.map +1 -1
- package/lib/module/platform-specific/jsVersion.js +1 -1
- package/lib/typescript/animation/timing.d.ts.map +1 -1
- package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
- package/package.json +3 -1
- package/src/animation/timing.ts +20 -2
- package/src/platform-specific/jsVersion.ts +1 -1
|
@@ -188,7 +188,9 @@ std::optional<MountingTransaction> LayoutAnimationsProxy::pullTransaction(
|
|
|
188
188
|
// shared element, the issue should be gone with the new stack
|
|
189
189
|
// implementation
|
|
190
190
|
if (auto screen = findParentRNSScreen(afterTopScreen)) {
|
|
191
|
-
forceScreenSnapshot_(screen->current.tag)
|
|
191
|
+
if (forceScreenSnapshot_(screen->current.tag)) {
|
|
192
|
+
snapshottedScreens_.insert(screen->current.tag);
|
|
193
|
+
}
|
|
192
194
|
}
|
|
193
195
|
#endif
|
|
194
196
|
}
|
|
@@ -256,6 +258,7 @@ void LayoutAnimationsProxy::unmapLightNode(const std::shared_ptr<LightNode> &nod
|
|
|
256
258
|
return;
|
|
257
259
|
}
|
|
258
260
|
lightNodes_.erase(it);
|
|
261
|
+
snapshottedScreens_.erase(node->current.tag);
|
|
259
262
|
if (node == topScreen_) {
|
|
260
263
|
topScreen_ = nullptr;
|
|
261
264
|
}
|
|
@@ -288,17 +291,13 @@ void LayoutAnimationsProxy::reconcileContradictedRemovals(
|
|
|
288
291
|
}
|
|
289
292
|
const auto tag = mutation.newChildShadowView.tag;
|
|
290
293
|
const auto it = lightNodes_.find(tag);
|
|
291
|
-
if (it == lightNodes_.end() || it->second->state ==
|
|
294
|
+
if (it == lightNodes_.end() || it->second->state == LIVE) {
|
|
292
295
|
continue;
|
|
293
296
|
}
|
|
294
297
|
const auto node = it->second;
|
|
295
298
|
completedAnimations_.erase(tag);
|
|
296
299
|
updateMap_.erase(tag);
|
|
297
300
|
unmapLightNode(node);
|
|
298
|
-
if (node->state == DELETED) {
|
|
299
|
-
// already unmounted — only the stale map entry had to go
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
301
|
const auto parent = node->parent.lock();
|
|
303
302
|
react_native_assert(parent && "Parent node is nullptr");
|
|
304
303
|
if (!parent) {
|
|
@@ -331,6 +330,7 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
331
330
|
std::unordered_map<Tag, IndexCursors> indexCursors;
|
|
332
331
|
std::unordered_map<Tag, ShadowView> updatedViews;
|
|
333
332
|
std::unordered_map<Tag, std::vector<AncestorOrigin>> oldChains;
|
|
333
|
+
std::unordered_set<Tag> removedSubtreeRoots;
|
|
334
334
|
for (auto it = mutations.rbegin(); it != mutations.rend(); it++) {
|
|
335
335
|
const auto &mutation = *it;
|
|
336
336
|
switch (mutation.type) {
|
|
@@ -407,16 +407,21 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
407
407
|
break;
|
|
408
408
|
}
|
|
409
409
|
case ShadowViewMutation::Delete: {
|
|
410
|
-
const auto
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
410
|
+
const auto tag = mutation.oldChildShadowView.tag;
|
|
411
|
+
const auto it = lightNodes_.find(tag);
|
|
412
|
+
react_native_assert(
|
|
413
|
+
(it == lightNodes_.end() || it->second->isExiting()) &&
|
|
414
|
+
"Delete mutation for a view that React did not remove");
|
|
415
|
+
// The differ removes everything that moves out of a view before its Delete, and inserts into the parent
|
|
416
|
+
// of the view only after it.
|
|
417
|
+
if (removedSubtreeRoots.erase(tag)) {
|
|
418
|
+
react_native_assert(it != lightNodes_.end() && "Delete mutation for an unknown node");
|
|
416
419
|
const auto node = it->second;
|
|
417
|
-
|
|
420
|
+
const auto parent = node->parent.lock();
|
|
421
|
+
react_native_assert(parent && "Parent node is nullptr");
|
|
422
|
+
handleSubtreeRemoval(node, parent, transaction);
|
|
418
423
|
}
|
|
419
|
-
staleSynchronousProps_.forget(
|
|
424
|
+
staleSynchronousProps_.forget(tag);
|
|
420
425
|
break;
|
|
421
426
|
}
|
|
422
427
|
case ShadowViewMutation::Insert: {
|
|
@@ -430,10 +435,13 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
430
435
|
bool hasSharedTransition = false;
|
|
431
436
|
if constexpr (StaticFeatureFlags::getFlag("ENABLE_SHARED_ELEMENT_TRANSITIONS")) {
|
|
432
437
|
auto sharedTransitionLock = std::unique_lock<std::mutex>(sharedTransitionManager_->mutex_);
|
|
433
|
-
hasSharedTransition =
|
|
438
|
+
hasSharedTransition = mutation.newChildShadowView.traits.check(ShadowNodeTraits::Trait::ViewKind) &&
|
|
439
|
+
sharedTransitionManager_->tagToName_.contains(tag);
|
|
434
440
|
}
|
|
435
441
|
const auto layoutConfig = layoutAnimationsManager_->getLayoutAnimationConfig(tag, LAYOUT);
|
|
436
|
-
const auto enteringConfig =
|
|
442
|
+
const auto enteringConfig = isViewKind(mutation.newChildShadowView)
|
|
443
|
+
? layoutAnimationsManager_->getLayoutAnimationConfig(tag, ENTERING)
|
|
444
|
+
: nullptr;
|
|
437
445
|
if (moved.contains(tag)) {
|
|
438
446
|
const auto offset = reparentOffset(oldChains.at(tag), ancestorOrigins(parent, {}));
|
|
439
447
|
node->previous.layoutMetrics.frame.origin += offset;
|
|
@@ -449,8 +457,13 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
449
457
|
filteredMutations.push_back(
|
|
450
458
|
ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, hostIndex));
|
|
451
459
|
} else {
|
|
452
|
-
|
|
453
|
-
|
|
460
|
+
auto view = mutation.newChildShadowView;
|
|
461
|
+
const auto updatedViewIt = updatedViews.find(tag);
|
|
462
|
+
const bool propsChanged = updatedViewIt != updatedViews.end() && updatedViewIt->second.props != view.props;
|
|
463
|
+
if (!propsChanged) {
|
|
464
|
+
view.props = propsOfMountedView(view);
|
|
465
|
+
}
|
|
466
|
+
filteredMutations.push_back(ShadowViewMutation::InsertMutation(mutation.parentTag, view, hostIndex));
|
|
454
467
|
}
|
|
455
468
|
} else if (enteringConfig) {
|
|
456
469
|
transaction.entering.push_back({node, enteringConfig});
|
|
@@ -476,6 +489,7 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
476
489
|
const auto tag = node->current.tag;
|
|
477
490
|
const auto parentTag = mutation.parentTag;
|
|
478
491
|
const auto &parent = lightNodes_[parentTag];
|
|
492
|
+
react_native_assert(parent && "Remove mutation from an unknown parent");
|
|
479
493
|
const auto hostIndex = parent->toHostIndexForRemove(mutation.index, indexCursors[parentTag]);
|
|
480
494
|
react_native_assert(
|
|
481
495
|
hostIndex < static_cast<int>(parent->children.size()) &&
|
|
@@ -490,8 +504,11 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
490
504
|
filteredMutations.push_back(
|
|
491
505
|
ShadowViewMutation::RemoveMutation(parentTag, mutation.oldChildShadowView, hostIndex));
|
|
492
506
|
parent->children.erase(parent->children.begin() + hostIndex);
|
|
493
|
-
} else
|
|
494
|
-
|
|
507
|
+
} else {
|
|
508
|
+
node->setExitingState(DECISION_PENDING);
|
|
509
|
+
if (!deleted.contains(parentTag)) {
|
|
510
|
+
removedSubtreeRoots.insert(tag);
|
|
511
|
+
}
|
|
495
512
|
}
|
|
496
513
|
break;
|
|
497
514
|
}
|
|
@@ -501,6 +518,7 @@ void LayoutAnimationsProxy::updateLightTree(
|
|
|
501
518
|
}
|
|
502
519
|
}
|
|
503
520
|
}
|
|
521
|
+
react_native_assert(removedSubtreeRoots.empty() && "React removed a view without deleting it");
|
|
504
522
|
}
|
|
505
523
|
|
|
506
524
|
void LayoutAnimationsProxy::applyInitialMutationsToLightTree(const ShadowViewMutationList &mutations) const {
|
|
@@ -656,14 +674,16 @@ void LayoutAnimationsProxy::initializeLightTree(const ShadowTreeRevision &baseRe
|
|
|
656
674
|
|
|
657
675
|
const auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
658
676
|
react_native_assert(!isLightTreeInitialized() && "Light tree is already initialized");
|
|
677
|
+
const auto root = std::make_shared<LightNode>();
|
|
659
678
|
if (baseRevision.rootShadowNode) {
|
|
660
679
|
const auto &size = baseRevision.rootShadowNode->getLayoutMetrics().frame.size;
|
|
661
680
|
window_ = {size.width, size.height};
|
|
681
|
+
root->current = ShadowView(*baseRevision.rootShadowNode);
|
|
682
|
+
} else {
|
|
683
|
+
root->current.componentName = "RootView";
|
|
684
|
+
root->current.tag = surfaceId_;
|
|
685
|
+
root->current.props = std::make_shared<BaseViewProps>();
|
|
662
686
|
}
|
|
663
|
-
const auto root = std::make_shared<LightNode>();
|
|
664
|
-
root->current.componentName = "RootView";
|
|
665
|
-
root->current.tag = surfaceId_;
|
|
666
|
-
root->current.props = std::make_shared<BaseViewProps>();
|
|
667
687
|
lightNodes_[surfaceId_] = root;
|
|
668
688
|
applyInitialMutationsToLightTree(initialMutations);
|
|
669
689
|
for (const auto &[revisionNumber, mutations] : pendingTransactions_) {
|
|
@@ -703,37 +723,57 @@ std::optional<SurfaceId> LayoutAnimationsProxy::endLayoutAnimation(int tag, bool
|
|
|
703
723
|
react_native_assert(false && "LightNode not found");
|
|
704
724
|
return surfaceId_;
|
|
705
725
|
}
|
|
706
|
-
nodeIt->second->setExitingState(
|
|
726
|
+
nodeIt->second->setExitingState(COMPLETED);
|
|
707
727
|
|
|
708
728
|
return surfaceId_;
|
|
709
729
|
}
|
|
710
730
|
|
|
711
731
|
// A subtree that animates keeps its place in the host tree, so nothing is emitted for its root.
|
|
712
|
-
// A subtree that does not animate
|
|
713
|
-
//
|
|
732
|
+
// A subtree that does not animate is removed at its current host index after its descendants, like React
|
|
733
|
+
// removes it. On iOS a subtree that holds a screen React Native Screens snapshots is removed before its
|
|
734
|
+
// descendants instead, and their teardown mounts at the end of the transaction.
|
|
714
735
|
void LayoutAnimationsProxy::handleSubtreeRemoval(
|
|
715
736
|
const std::shared_ptr<LightNode> &node,
|
|
716
737
|
const std::shared_ptr<LightNode> &parent,
|
|
717
|
-
const int hostIndex,
|
|
718
738
|
TransactionMeta &transaction) const {
|
|
719
739
|
ReanimatedSystraceSection s("handleSubtreeRemoval");
|
|
740
|
+
const bool defersTeardown = holdsSnapshottedScreen(node);
|
|
720
741
|
const StartAnimationsRecursivelyConfig config = {
|
|
721
742
|
.shouldRemoveSubviewsWithoutAnimations = true,
|
|
722
743
|
.shouldAnimate = !transaction.surfaceDropped,
|
|
723
744
|
.isScreenPop = false,
|
|
745
|
+
.defersTeardown = defersTeardown,
|
|
724
746
|
};
|
|
725
747
|
if (startAnimationsRecursively(node, transaction, config)) {
|
|
726
748
|
return;
|
|
727
749
|
}
|
|
728
|
-
react_native_assert(
|
|
750
|
+
react_native_assert(node->state == DECISION_PENDING && "A subtree that does not animate stays DECISION_PENDING");
|
|
751
|
+
const auto hostIndex = parent->removeChild(node);
|
|
752
|
+
react_native_assert(hostIndex != -1 && "Removed node not found in its parent");
|
|
753
|
+
node->setExitingState(TORN_DOWN);
|
|
754
|
+
unmapLightNode(node);
|
|
729
755
|
cancelLayoutAnimation(node->current.tag);
|
|
730
756
|
if constexpr (StaticFeatureFlags::getFlag("ENABLE_SHARED_ELEMENT_TRANSITIONS")) {
|
|
731
757
|
hiddenViewTags_.erase(node->current.tag);
|
|
732
758
|
}
|
|
733
759
|
transaction.filteredMutations.push_back(
|
|
734
760
|
ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, hostIndex));
|
|
735
|
-
transaction.teardownMutations.
|
|
736
|
-
|
|
761
|
+
(defersTeardown ? transaction.teardownMutations : transaction.filteredMutations)
|
|
762
|
+
.push_back(ShadowViewMutation::DeleteMutation(node->current));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// React Native Screens snapshots a screen in snapshottedScreens_ after pending updates when the screen is removed,
|
|
766
|
+
// so its content must still be mounted then.
|
|
767
|
+
bool LayoutAnimationsProxy::holdsSnapshottedScreen([[maybe_unused]] const std::shared_ptr<LightNode> &node) const {
|
|
768
|
+
#ifdef __APPLE__
|
|
769
|
+
if constexpr (StaticFeatureFlags::getFlag("ENABLE_SHARED_ELEMENT_TRANSITIONS")) {
|
|
770
|
+
return std::ranges::any_of(snapshottedScreens_, [&](const Tag tag) {
|
|
771
|
+
const auto it = lightNodes_.find(tag);
|
|
772
|
+
return it != lightNodes_.end() && isInSubtree(it->second, node);
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
#endif
|
|
776
|
+
return false;
|
|
737
777
|
}
|
|
738
778
|
|
|
739
779
|
void LayoutAnimationsProxy::flushCompletedRemovals(ShadowViewMutationList &filteredMutations) const {
|
|
@@ -755,14 +795,14 @@ void LayoutAnimationsProxy::flushCompletedRemovals(ShadowViewMutationList &filte
|
|
|
755
795
|
continue;
|
|
756
796
|
}
|
|
757
797
|
const auto nodeIt = lightNodes_.find(tag);
|
|
758
|
-
if (nodeIt == lightNodes_.end() || nodeIt->second->state !=
|
|
798
|
+
if (nodeIt == lightNodes_.end() || nodeIt->second->state != COMPLETED) {
|
|
759
799
|
continue;
|
|
760
800
|
}
|
|
761
801
|
const auto node = nodeIt->second;
|
|
762
802
|
auto parent = node->parent.lock();
|
|
763
803
|
react_native_assert(parent && "Parent node is nullptr");
|
|
764
804
|
auto index = parent->removeChild(node);
|
|
765
|
-
react_native_assert(index != -1 && "
|
|
805
|
+
react_native_assert(index != -1 && "Completed node not found");
|
|
766
806
|
|
|
767
807
|
endAnimationsRecursively(node, index, filteredMutations);
|
|
768
808
|
maybeDropAncestors(parent, filteredMutations);
|
|
@@ -824,7 +864,7 @@ void LayoutAnimationsProxy::addOngoingAnimations(ShadowViewMutationList &mutatio
|
|
|
824
864
|
mutations.push_back(
|
|
825
865
|
ShadowViewMutation::UpdateMutation(layoutAnimation.currentView, newView, layoutAnimation.parentTag));
|
|
826
866
|
layoutAnimation.currentView = newView;
|
|
827
|
-
if (layoutAnimation.opacity &&
|
|
867
|
+
if (layoutAnimation.opacity && getViewProps(newView).opacity == *layoutAnimation.opacity) {
|
|
828
868
|
layoutAnimation.opacity.reset();
|
|
829
869
|
}
|
|
830
870
|
}
|
|
@@ -837,7 +877,7 @@ void LayoutAnimationsProxy::endAnimationsRecursively(
|
|
|
837
877
|
ShadowViewMutationList &mutations) const {
|
|
838
878
|
const auto tag = node->current.tag;
|
|
839
879
|
cancelLayoutAnimation(tag);
|
|
840
|
-
node->setExitingState(
|
|
880
|
+
node->setExitingState(TORN_DOWN);
|
|
841
881
|
unmapLightNode(node);
|
|
842
882
|
// iterate from the end, so that children
|
|
843
883
|
// with higher indices appear first in the mutations list
|
|
@@ -845,7 +885,7 @@ void LayoutAnimationsProxy::endAnimationsRecursively(
|
|
|
845
885
|
const int childrenSize = static_cast<int>(node->children.size());
|
|
846
886
|
for (int i = childrenSize - 1; i >= 0; i--) {
|
|
847
887
|
auto &subNode = node->children[i];
|
|
848
|
-
if (subNode->state !=
|
|
888
|
+
if (subNode->state != TORN_DOWN) {
|
|
849
889
|
endAnimationsRecursively(subNode, i, mutations);
|
|
850
890
|
}
|
|
851
891
|
}
|
|
@@ -863,7 +903,7 @@ void LayoutAnimationsProxy::endAnimationsRecursively(
|
|
|
863
903
|
void LayoutAnimationsProxy::maybeDropAncestors(
|
|
864
904
|
const std::shared_ptr<LightNode> &node,
|
|
865
905
|
ShadowViewMutationList &cleanupMutations) const {
|
|
866
|
-
if (node->children.size() != 0 || node->state == ANIMATING || node->state ==
|
|
906
|
+
if (node->children.size() != 0 || node->state == ANIMATING || node->state == LIVE) {
|
|
867
907
|
return;
|
|
868
908
|
}
|
|
869
909
|
|
|
@@ -872,7 +912,7 @@ void LayoutAnimationsProxy::maybeDropAncestors(
|
|
|
872
912
|
auto index = parent->removeChild(node);
|
|
873
913
|
react_native_assert(index != -1 && "Child node not found");
|
|
874
914
|
|
|
875
|
-
node->setExitingState(
|
|
915
|
+
node->setExitingState(TORN_DOWN);
|
|
876
916
|
unmapLightNode(node);
|
|
877
917
|
cancelLayoutAnimation(node->current.tag);
|
|
878
918
|
if constexpr (StaticFeatureFlags::getFlag("ENABLE_SHARED_ELEMENT_TRANSITIONS")) {
|
|
@@ -887,8 +927,9 @@ bool LayoutAnimationsProxy::startAnimationsRecursively(
|
|
|
887
927
|
const std::shared_ptr<LightNode> &node,
|
|
888
928
|
TransactionMeta &transaction,
|
|
889
929
|
StartAnimationsRecursivelyConfig config) const {
|
|
890
|
-
|
|
891
|
-
auto &[shouldRemoveSubviewsWithoutAnimations, shouldAnimate, isScreenPop] = config;
|
|
930
|
+
react_native_assert(node->state == DECISION_PENDING && "Subtree removal of a view that React does not delete");
|
|
931
|
+
auto &[shouldRemoveSubviewsWithoutAnimations, shouldAnimate, isScreenPop, defersTeardown] = config;
|
|
932
|
+
auto &mutations = defersTeardown ? transaction.teardownMutations : transaction.filteredMutations;
|
|
892
933
|
if (isRNSScreenOrStack(node)) {
|
|
893
934
|
isScreenPop = true;
|
|
894
935
|
}
|
|
@@ -909,8 +950,9 @@ bool LayoutAnimationsProxy::startAnimationsRecursively(
|
|
|
909
950
|
for (auto it = node->children.rbegin(); it != node->children.rend(); it++) {
|
|
910
951
|
index--;
|
|
911
952
|
auto &subNode = *it;
|
|
912
|
-
|
|
913
|
-
|
|
953
|
+
react_native_assert(subNode->isExiting() && "Subtree removal of a view that React does not delete");
|
|
954
|
+
if (subNode->state != DECISION_PENDING) {
|
|
955
|
+
if (shouldAnimate && subNode->state != COMPLETED) {
|
|
914
956
|
hasAnimatedChildren = true;
|
|
915
957
|
} else {
|
|
916
958
|
endAnimationsRecursively(subNode, index, mutations);
|
|
@@ -925,13 +967,11 @@ bool LayoutAnimationsProxy::startAnimationsRecursively(
|
|
|
925
967
|
}
|
|
926
968
|
mutations.push_back(ShadowViewMutation::RemoveMutation(node->current.tag, subNode->current, index));
|
|
927
969
|
toBeRemoved.push_back(subNode);
|
|
928
|
-
subNode->setExitingState(
|
|
970
|
+
subNode->setExitingState(TORN_DOWN);
|
|
971
|
+
unmapLightNode(subNode);
|
|
929
972
|
mutations.push_back(ShadowViewMutation::DeleteMutation(subNode->current));
|
|
930
973
|
} else {
|
|
931
974
|
subNode->setExitingState(WAITING);
|
|
932
|
-
// register withheld subtree members, so that reconcileContradictedRemovals
|
|
933
|
-
// can find them when React re-creates their tags
|
|
934
|
-
lightNodes_[subNode->current.tag] = subNode;
|
|
935
975
|
}
|
|
936
976
|
}
|
|
937
977
|
|
|
@@ -943,7 +983,6 @@ bool LayoutAnimationsProxy::startAnimationsRecursively(
|
|
|
943
983
|
|
|
944
984
|
if (hasExitAnimation) {
|
|
945
985
|
node->setExitingState(ANIMATING);
|
|
946
|
-
lightNodes_[node->current.tag] = node;
|
|
947
986
|
transaction.exiting.push_back({node, exitConfig});
|
|
948
987
|
} else {
|
|
949
988
|
if (!shouldAnimate) {
|
|
@@ -951,7 +990,6 @@ bool LayoutAnimationsProxy::startAnimationsRecursively(
|
|
|
951
990
|
}
|
|
952
991
|
if (hasAnimatedChildren) {
|
|
953
992
|
node->setExitingState(WAITING);
|
|
954
|
-
lightNodes_[node->current.tag] = node;
|
|
955
993
|
}
|
|
956
994
|
}
|
|
957
995
|
|
|
@@ -967,6 +1005,7 @@ void LayoutAnimationsProxy::clearSurfaceState() const {
|
|
|
967
1005
|
LayoutAnimationsProxyCommon::clearSurfaceState();
|
|
968
1006
|
if constexpr (StaticFeatureFlags::getFlag("ENABLE_SHARED_ELEMENT_TRANSITIONS")) {
|
|
969
1007
|
sharedContainers_.clear();
|
|
1008
|
+
snapshottedScreens_.clear();
|
|
970
1009
|
transition_.reset();
|
|
971
1010
|
uncommittedScreenPop_.reset();
|
|
972
1011
|
}
|
|
@@ -981,6 +1020,7 @@ void LayoutAnimationsProxy::clearSurfaceState() const {
|
|
|
981
1020
|
ShadowView LayoutAnimationsProxy::cloneViewWithoutOpacity(
|
|
982
1021
|
const ShadowView &shadowView,
|
|
983
1022
|
const PropsParserContext &propsParserContext) const {
|
|
1023
|
+
react_native_assert(isViewKind(shadowView) && "Only ViewKind views have ViewProps");
|
|
984
1024
|
auto newView = shadowView;
|
|
985
1025
|
folly::dynamic rawProps = folly::dynamic::object("opacity", 0);
|
|
986
1026
|
#ifdef ANDROID
|
|
@@ -996,11 +1036,16 @@ ShadowView LayoutAnimationsProxy::cloneViewWithoutOpacity(
|
|
|
996
1036
|
|
|
997
1037
|
// Android's push model applies JS-thread transactions asynchronously on the UI
|
|
998
1038
|
// thread. A UI-thread pull can overtake them, so completed animations must not
|
|
999
|
-
// add structural cleanup mutations there.
|
|
1000
|
-
//
|
|
1039
|
+
// add structural cleanup mutations there.
|
|
1040
|
+
//
|
|
1041
|
+
// Under the pull model there is nothing left to overtake, because every
|
|
1042
|
+
// transaction is pulled from the UI thread's frame callback. Keeping the thread
|
|
1043
|
+
// check on its own there would mean this never returns true, so no exiting view
|
|
1044
|
+
// would ever have its removal flushed, and every one of them would stay mounted
|
|
1045
|
+
// for the lifetime of the surface.
|
|
1001
1046
|
bool LayoutAnimationsProxy::shouldFlushStructuralMutations() const {
|
|
1002
1047
|
#ifdef ANDROID
|
|
1003
|
-
return !worklets::isOnUIThread(uiScheduler_);
|
|
1048
|
+
return isMountingCoordinatorPullModelEnabled() || !worklets::isOnUIThread(uiScheduler_);
|
|
1004
1049
|
#else
|
|
1005
1050
|
return true;
|
|
1006
1051
|
#endif
|
|
@@ -1061,9 +1106,7 @@ void LayoutAnimationsProxy::startEnteringAnimation(
|
|
|
1061
1106
|
const std::shared_ptr<Serializable> &config) const {
|
|
1062
1107
|
resolveLightNodeProps(node);
|
|
1063
1108
|
const auto &newChildShadowView = node->current;
|
|
1064
|
-
const auto
|
|
1065
|
-
auto &viewProps = static_cast<const ViewProps &>(*props);
|
|
1066
|
-
const auto opacity = viewProps.opacity;
|
|
1109
|
+
const auto opacity = getViewProps(newChildShadowView).opacity;
|
|
1067
1110
|
const auto &parent = node->parent.lock();
|
|
1068
1111
|
react_native_assert(parent && "Parent node is nullptr");
|
|
1069
1112
|
enqueueLayoutAnimation(ManagedLayoutAnimationStart{
|
|
@@ -33,6 +33,7 @@ struct StartAnimationsRecursivelyConfig {
|
|
|
33
33
|
bool shouldRemoveSubviewsWithoutAnimations;
|
|
34
34
|
bool shouldAnimate;
|
|
35
35
|
bool isScreenPop;
|
|
36
|
+
bool defersTeardown;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
struct PendingNodeAnimation {
|
|
@@ -93,6 +94,9 @@ struct LayoutAnimationsProxy : public LayoutAnimationsProxyCommon {
|
|
|
93
94
|
mutable std::optional<ProgressTransition> transition_;
|
|
94
95
|
mutable std::optional<UncommittedScreenPop> uncommittedScreenPop_;
|
|
95
96
|
mutable std::shared_ptr<LightNode> topScreen_;
|
|
97
|
+
// screens that forceScreenSnapshot_ switched to snapshots after updates; React Native Screens never switches them
|
|
98
|
+
// back
|
|
99
|
+
mutable std::unordered_set<Tag> snapshottedScreens_;
|
|
96
100
|
mutable std::unordered_map<Tag, SharedContainer> sharedContainers_;
|
|
97
101
|
mutable std::unordered_set<Tag> hiddenViewTags_;
|
|
98
102
|
std::shared_ptr<SharedTransitionManager> sharedTransitionManager_;
|
|
@@ -244,8 +248,8 @@ struct LayoutAnimationsProxy : public LayoutAnimationsProxyCommon {
|
|
|
244
248
|
void handleSubtreeRemoval(
|
|
245
249
|
const std::shared_ptr<LightNode> &node,
|
|
246
250
|
const std::shared_ptr<LightNode> &parent,
|
|
247
|
-
int hostIndex,
|
|
248
251
|
TransactionMeta &transaction) const;
|
|
252
|
+
bool holdsSnapshottedScreen(const std::shared_ptr<LightNode> &node) const;
|
|
249
253
|
void flushCompletedRemovals(ShadowViewMutationList &filteredMutations) const;
|
|
250
254
|
|
|
251
255
|
void addOngoingAnimations(ShadowViewMutationList &mutations) const;
|
|
@@ -156,6 +156,25 @@ void LayoutAnimationsProxyCommon::flushLayoutAnimationOperations(std::unique_loc
|
|
|
156
156
|
flushLayoutAnimationOperationsLocked();
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// iOS, and Android with accumulated raw props, write the props of an Insert to the view. The shadow props of a view
|
|
160
|
+
// that only moves lack the values that were written synchronously.
|
|
161
|
+
Props::Shared LayoutAnimationsProxyCommon::propsOfMountedView(const ShadowView &view) const {
|
|
162
|
+
#ifdef __APPLE__
|
|
163
|
+
if (readMountedViewProps_) {
|
|
164
|
+
if (auto mountedProps = readMountedViewProps_(view.tag)) {
|
|
165
|
+
return mountedProps;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
#elif defined(ANDROID)
|
|
169
|
+
if (readSynchronousProps_) {
|
|
170
|
+
if (const auto synchronousProps = readSynchronousProps_(view.tag); !synchronousProps.empty()) {
|
|
171
|
+
return mergeSynchronousProps(view, synchronousProps);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
#endif
|
|
175
|
+
return view.props;
|
|
176
|
+
}
|
|
177
|
+
|
|
159
178
|
Props::Shared LayoutAnimationsProxyCommon::mergeSynchronousProps(const ShadowView &view, const folly::dynamic &props)
|
|
160
179
|
const {
|
|
161
180
|
auto rawProps = props;
|
|
@@ -204,7 +223,7 @@ void LayoutAnimationsProxyCommon::applySynchronousPropsToLayoutAnimation(const T
|
|
|
204
223
|
start.after.props = mergeSynchronousProps(start.after, props);
|
|
205
224
|
if constexpr (std::is_same_v<Start, ManagedLayoutAnimationStart>) {
|
|
206
225
|
if (propsIncludeOpacity && start.opacity) {
|
|
207
|
-
start.opacity =
|
|
226
|
+
start.opacity = getViewProps(start.after).opacity;
|
|
208
227
|
}
|
|
209
228
|
}
|
|
210
229
|
}
|
|
@@ -226,7 +245,7 @@ void LayoutAnimationsProxyCommon::applySynchronousPropsToLayoutAnimation(const T
|
|
|
226
245
|
animation->finalView.props = mergeSynchronousProps(animation->finalView, props);
|
|
227
246
|
animation->currentView.props = mergeSynchronousProps(animation->currentView, props);
|
|
228
247
|
if (propsIncludeOpacity && animation->opacity) {
|
|
229
|
-
animation->opacity =
|
|
248
|
+
animation->opacity = getViewProps(animation->finalView).opacity;
|
|
230
249
|
}
|
|
231
250
|
if (const auto it = updateMap_.find(tag); it != updateMap_.end() && it->second.newProps) {
|
|
232
251
|
auto pendingView = animation->finalView;
|
|
@@ -506,7 +525,6 @@ std::shared_ptr<Serializable> LayoutAnimationsProxyCommon::getRetargetLayoutAnim
|
|
|
506
525
|
|
|
507
526
|
bool LayoutAnimationsProxyCommon::updateEnteringAnimationTarget(const Tag tag, const ShadowView &finalView) const {
|
|
508
527
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
509
|
-
const auto opacity = static_cast<const ViewProps &>(*finalView.props).opacity;
|
|
510
528
|
if (const auto pendingIt = pendingLayoutAnimations_.find(tag); pendingIt != pendingLayoutAnimations_.end()) {
|
|
511
529
|
if (pendingIt->second.type != LayoutAnimationType::ENTERING) {
|
|
512
530
|
return false;
|
|
@@ -515,7 +533,7 @@ bool LayoutAnimationsProxyCommon::updateEnteringAnimationTarget(const Tag tag, c
|
|
|
515
533
|
react_native_assert(operationIndex < layoutAnimationOperations_.size());
|
|
516
534
|
if (auto *start = std::get_if<ManagedLayoutAnimationStart>(&layoutAnimationOperations_[operationIndex])) {
|
|
517
535
|
start->after = finalView;
|
|
518
|
-
start->opacity = opacity;
|
|
536
|
+
start->opacity = getViewProps(finalView).opacity;
|
|
519
537
|
return true;
|
|
520
538
|
}
|
|
521
539
|
react_native_assert(false && "Pending managed layout animation not found");
|
|
@@ -524,7 +542,7 @@ bool LayoutAnimationsProxyCommon::updateEnteringAnimationTarget(const Tag tag, c
|
|
|
524
542
|
animationIt != layoutAnimations_.end() && animationIt->second.type == LayoutAnimationType::ENTERING) {
|
|
525
543
|
animationIt->second.finalView = finalView;
|
|
526
544
|
if (animationIt->second.opacity) {
|
|
527
|
-
animationIt->second.opacity = opacity;
|
|
545
|
+
animationIt->second.opacity = getViewProps(finalView).opacity;
|
|
528
546
|
}
|
|
529
547
|
return true;
|
|
530
548
|
}
|
|
@@ -63,6 +63,10 @@ struct LayoutAnimationCancellation {
|
|
|
63
63
|
using LayoutAnimationOperation =
|
|
64
64
|
std::variant<ManagedLayoutAnimationStart, ProgressLayoutAnimationStart, LayoutAnimationCancellation>;
|
|
65
65
|
|
|
66
|
+
#ifdef ANDROID
|
|
67
|
+
using SynchronousPropsReader = std::function<folly::dynamic(Tag tag)>;
|
|
68
|
+
#endif
|
|
69
|
+
|
|
66
70
|
struct LayoutAnimationsProxyDependencies {
|
|
67
71
|
std::shared_ptr<LayoutAnimationsManager> layoutAnimationsManager;
|
|
68
72
|
SharedComponentDescriptorRegistry componentDescriptorRegistry;
|
|
@@ -74,9 +78,11 @@ struct LayoutAnimationsProxyDependencies {
|
|
|
74
78
|
#ifdef ANDROID
|
|
75
79
|
PreserveMountedTagsFunction filterUnmountedTagsFunction;
|
|
76
80
|
std::shared_ptr<facebook::react::CallInvoker> jsInvoker;
|
|
81
|
+
SynchronousPropsReader readSynchronousProps;
|
|
77
82
|
#endif
|
|
78
83
|
#ifdef __APPLE__
|
|
79
84
|
ForceScreenSnapshotFunction forceScreenSnapshot;
|
|
85
|
+
ReadMountedViewPropsFunction readMountedViewProps;
|
|
80
86
|
#endif
|
|
81
87
|
};
|
|
82
88
|
|
|
@@ -95,7 +101,12 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
|
|
95
101
|
#ifdef ANDROID
|
|
96
102
|
,
|
|
97
103
|
preserveMountedTags_(dependencies.filterUnmountedTagsFunction),
|
|
98
|
-
jsInvoker_(dependencies.jsInvoker)
|
|
104
|
+
jsInvoker_(dependencies.jsInvoker),
|
|
105
|
+
readSynchronousProps_(dependencies.readSynchronousProps)
|
|
106
|
+
#endif
|
|
107
|
+
#ifdef __APPLE__
|
|
108
|
+
,
|
|
109
|
+
readMountedViewProps_(dependencies.readMountedViewProps)
|
|
99
110
|
#endif
|
|
100
111
|
{
|
|
101
112
|
}
|
|
@@ -115,6 +126,7 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
|
|
115
126
|
void flushLayoutAnimationOperations() const;
|
|
116
127
|
|
|
117
128
|
protected:
|
|
129
|
+
Props::Shared propsOfMountedView(const ShadowView &view) const;
|
|
118
130
|
Props::Shared mergeSynchronousProps(const ShadowView &view, const folly::dynamic &props) const;
|
|
119
131
|
bool hasLayoutAnimationRecords() const;
|
|
120
132
|
void applySynchronousPropsToLayoutAnimation(Tag tag, const folly::dynamic &props) const;
|
|
@@ -169,6 +181,10 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
|
|
169
181
|
#ifdef ANDROID
|
|
170
182
|
PreserveMountedTagsFunction preserveMountedTags_;
|
|
171
183
|
std::shared_ptr<facebook::react::CallInvoker> jsInvoker_;
|
|
184
|
+
SynchronousPropsReader readSynchronousProps_;
|
|
185
|
+
#endif
|
|
186
|
+
#ifdef __APPLE__
|
|
187
|
+
ReadMountedViewPropsFunction readMountedViewProps_;
|
|
172
188
|
#endif
|
|
173
189
|
|
|
174
190
|
private:
|
|
@@ -134,12 +134,12 @@ void LayoutAnimationsProxy_Legacy::reconcileContradictedRemovals(
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
137
|
+
// With Android's push model, structural mutations from the JS thread may still be waiting to mount when a UI-thread
|
|
138
|
+
// pull runs, so dead nodes must be cleaned up on the JS thread. The pull model mounts transactions on the UI thread,
|
|
139
|
+
// where cleanup is safe. A dropped surface can also be cleaned up immediately.
|
|
140
140
|
bool LayoutAnimationsProxy_Legacy::shouldFlushDeadNodes([[maybe_unused]] const bool surfaceDropped) const {
|
|
141
141
|
#ifdef ANDROID
|
|
142
|
-
return surfaceDropped || !worklets::isOnUIThread(uiScheduler_);
|
|
142
|
+
return surfaceDropped || isMountingCoordinatorPullModelEnabled() || !worklets::isOnUIThread(uiScheduler_);
|
|
143
143
|
#else
|
|
144
144
|
return true;
|
|
145
145
|
#endif
|
|
@@ -356,8 +356,9 @@ void LayoutAnimationsProxy_Legacy::handleUpdatesAndEnterings(
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
transferConfigFromNativeID(mutation.newChildShadowView.props->nativeId, mutation.newChildShadowView.tag);
|
|
359
|
-
const auto enteringConfig =
|
|
360
|
-
layoutAnimationsManager_->getLayoutAnimationConfig(tag, LayoutAnimationType::ENTERING)
|
|
359
|
+
const auto enteringConfig = isViewKind(mutation.newChildShadowView)
|
|
360
|
+
? layoutAnimationsManager_->getLayoutAnimationConfig(tag, LayoutAnimationType::ENTERING)
|
|
361
|
+
: nullptr;
|
|
361
362
|
if (!enteringConfig) {
|
|
362
363
|
filteredMutations.push_back(mutation);
|
|
363
364
|
continue;
|
|
@@ -470,7 +471,7 @@ void LayoutAnimationsProxy_Legacy::addOngoingAnimations(ShadowViewMutationList &
|
|
|
470
471
|
mutations.push_back(
|
|
471
472
|
ShadowViewMutation::UpdateMutation(layoutAnimation.currentView, newView, layoutAnimation.parentTag));
|
|
472
473
|
layoutAnimation.currentView = newView;
|
|
473
|
-
if (layoutAnimation.opacity &&
|
|
474
|
+
if (layoutAnimation.opacity && getViewProps(newView).opacity == *layoutAnimation.opacity) {
|
|
474
475
|
layoutAnimation.opacity.reset();
|
|
475
476
|
}
|
|
476
477
|
}
|
|
@@ -653,14 +654,13 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(
|
|
|
653
654
|
#ifdef LAYOUT_ANIMATIONS_LOGS
|
|
654
655
|
LOG(INFO) << "start entering animation for tag " << tag << std::endl;
|
|
655
656
|
#endif
|
|
656
|
-
auto &viewProps = static_cast<const ViewProps &>(*mutation.newChildShadowView.props);
|
|
657
657
|
enqueueLayoutAnimation(ManagedLayoutAnimationStart{
|
|
658
658
|
.tag = tag,
|
|
659
659
|
.type = LayoutAnimationType::ENTERING,
|
|
660
660
|
.before = mutation.newChildShadowView,
|
|
661
661
|
.after = mutation.newChildShadowView,
|
|
662
662
|
.parentTag = mutation.parentTag,
|
|
663
|
-
.opacity =
|
|
663
|
+
.opacity = getViewProps(mutation.newChildShadowView).opacity,
|
|
664
664
|
.config = config,
|
|
665
665
|
});
|
|
666
666
|
}
|