- App.tsx: full navigation (Auth stack + Main tabs with 5 screens) - Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen - HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors - MapScreen: interactive map with layer toggles (6 layers) - MarketplaceScreen: categories (6), products (5), search - ChatScreen: AI chat with quick prompts (4), bot responses - ProfileScreen: user info, stats, menu (9 items), logout - AlertsScreen: alert list with severity, acknowledge - SensorsScreen: sensor list with type filters (6 types), search - ZonesScreen: zone cards with stats - SettingsScreen: language picker (FR/EN/ES/DE), privacy, about - Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n - Hooks: useSensors, useAlerts, useNotifications, useLocation - Components: Card, Button, LoadingSpinner, ErrorBoundary, Header - Services: iotService, notificationService (with axios API client) - Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN) - Theme: colors.ts with full design system (Blue Ocean palette) - Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
639 lines
22 KiB
Objective-C
639 lines
22 KiB
Objective-C
#import <RNReanimated/REAAnimationsManager.h>
|
|
#import <RNReanimated/REASharedElement.h>
|
|
#import <RNReanimated/REASharedTransitionManager.h>
|
|
#import <RNReanimated/REASwizzledUIManager.h>
|
|
#import <React/RCTComponentData.h>
|
|
#import <React/RCTTextView.h>
|
|
#import <React/UIView+Private.h>
|
|
#import <React/UIView+React.h>
|
|
|
|
typedef NS_ENUM(NSInteger, FrameConfigType) { EnteringFrame, ExitingFrame };
|
|
|
|
BOOL REANodeFind(id<RCTComponent> view, int (^block)(id<RCTComponent>))
|
|
{
|
|
if (!view.reactTag) {
|
|
return NO;
|
|
}
|
|
|
|
if (block(view)) {
|
|
return YES;
|
|
}
|
|
|
|
for (id<RCTComponent> subview in view.reactSubviews) {
|
|
if (REANodeFind(subview, block)) {
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
@implementation REAAnimationsManager {
|
|
RCTUIManager *_uiManager;
|
|
REASwizzledUIManager *_reaSwizzledUIManager;
|
|
NSMutableSet<NSNumber *> *_enteringViews;
|
|
NSMutableDictionary<NSNumber *, REASnapshot *> *_enteringViewTargetValues;
|
|
NSMutableDictionary<NSNumber *, REAUIView *> *_exitingViews;
|
|
NSMutableDictionary<NSNumber *, NSNumber *> *_exitingSubviewsCountMap;
|
|
NSMutableDictionary<NSNumber *, NSNumber *> *_exitingParentTags;
|
|
NSMutableSet<NSNumber *> *_ancestorsToRemove;
|
|
NSMutableArray<NSString *> *_targetKeys;
|
|
NSMutableArray<NSString *> *_currentKeys;
|
|
REAAnimationStartingBlock _startAnimationForTag;
|
|
REAHasAnimationBlock _hasAnimationForTag;
|
|
REAShouldAnimateExitingBlock _shouldAnimateExiting;
|
|
REAAnimationRemovingBlock _clearAnimationConfigForTag;
|
|
REASharedTransitionRemovingBlock _clearSharedTransitionConfigForTag;
|
|
REASharedTransitionManager *_sharedTransitionManager;
|
|
#ifndef NDEBUG
|
|
REACheckDuplicateSharedTagBlock _checkDuplicateSharedTag;
|
|
#endif
|
|
}
|
|
|
|
+ (NSArray *)layoutKeys
|
|
{
|
|
static NSArray *_array;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
_array = @[ @"originX", @"originY", @"width", @"height" ];
|
|
});
|
|
return _array;
|
|
}
|
|
|
|
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager
|
|
{
|
|
if (self = [super init]) {
|
|
_uiManager = uiManager;
|
|
_exitingViews = [NSMutableDictionary new];
|
|
_exitingSubviewsCountMap = [NSMutableDictionary new];
|
|
_ancestorsToRemove = [NSMutableSet new];
|
|
_exitingParentTags = [NSMutableDictionary new];
|
|
_enteringViews = [NSMutableSet new];
|
|
_enteringViewTargetValues = [NSMutableDictionary new];
|
|
|
|
_targetKeys = [NSMutableArray new];
|
|
_currentKeys = [NSMutableArray new];
|
|
for (NSString *key in [[self class] layoutKeys]) {
|
|
[_targetKeys addObject:[NSString stringWithFormat:@"target%@", [key capitalizedString]]];
|
|
[_currentKeys addObject:[NSString stringWithFormat:@"current%@", [key capitalizedString]]];
|
|
}
|
|
_sharedTransitionManager = [[REASharedTransitionManager alloc] initWithAnimationsManager:self];
|
|
_reaSwizzledUIManager = [[REASwizzledUIManager alloc] initWithUIManager:uiManager withAnimationManager:self];
|
|
|
|
_startAnimationForTag = ^(NSNumber *tag, LayoutAnimationType type, NSDictionary *yogaValues) {
|
|
// default implementation, this block will be replaced by a setter
|
|
};
|
|
_hasAnimationForTag = ^(NSNumber *tag, LayoutAnimationType type) {
|
|
// default implementation, this block will be replaced by a setter
|
|
return NO;
|
|
};
|
|
_shouldAnimateExiting = ^(NSNumber *tag, BOOL shouldAnimate) {
|
|
// default implementation, this block will be replaced by a setter
|
|
return YES;
|
|
};
|
|
_clearAnimationConfigForTag = ^(NSNumber *tag) {
|
|
// default implementation, this block will be replaced by a setter
|
|
};
|
|
_clearSharedTransitionConfigForTag = ^(NSNumber *tag) {
|
|
// default implementation, this block will be replaced by a setter
|
|
};
|
|
#ifndef NDEBUG
|
|
_checkDuplicateSharedTag = ^(REAUIView *view, NSNumber *viewTag) {
|
|
// default implementation, this block will be replaced by a setter
|
|
};
|
|
#endif
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
_startAnimationForTag = nil;
|
|
_hasAnimationForTag = nil;
|
|
_uiManager = nil;
|
|
_exitingViews = nil;
|
|
_targetKeys = nil;
|
|
_currentKeys = nil;
|
|
}
|
|
|
|
- (void)setAnimationStartingBlock:(REAAnimationStartingBlock)startAnimation
|
|
{
|
|
_startAnimationForTag = startAnimation;
|
|
}
|
|
|
|
- (void)setHasAnimationBlock:(REAHasAnimationBlock)hasAnimation
|
|
{
|
|
_hasAnimationForTag = hasAnimation;
|
|
}
|
|
|
|
- (void)setShouldAnimateExitingBlock:(REAShouldAnimateExitingBlock)shouldAnimateExiting
|
|
{
|
|
_shouldAnimateExiting = shouldAnimateExiting;
|
|
}
|
|
|
|
- (void)setAnimationRemovingBlock:(REAAnimationRemovingBlock)clearAnimation
|
|
{
|
|
_clearAnimationConfigForTag = clearAnimation;
|
|
}
|
|
|
|
- (void)setSharedTransitionRemovingBlock:(REASharedTransitionRemovingBlock)clearSharedTransition
|
|
{
|
|
_clearSharedTransitionConfigForTag = clearSharedTransition;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
- (void)setCheckDuplicateSharedTagBlock:(REACheckDuplicateSharedTagBlock)checkDuplicateSharedTag
|
|
{
|
|
_checkDuplicateSharedTag = checkDuplicateSharedTag;
|
|
}
|
|
#endif
|
|
|
|
- (REAUIView *)viewForTag:(NSNumber *)tag
|
|
{
|
|
REAUIView *view;
|
|
(view = [_uiManager viewForReactTag:tag]) || (view = [_exitingViews objectForKey:tag]) ||
|
|
(view = [_sharedTransitionManager getTransitioningView:tag]);
|
|
return view;
|
|
}
|
|
|
|
- (void)endLayoutAnimationForTag:(NSNumber *)tag removeView:(BOOL)removeView
|
|
{
|
|
REAUIView *view = [self viewForTag:tag];
|
|
|
|
if (view == nil) {
|
|
return;
|
|
}
|
|
if ([_enteringViews containsObject:tag] && !removeView) {
|
|
REASnapshot *target = _enteringViewTargetValues[tag];
|
|
if (target != nil) {
|
|
[self setNewProps:target.values forView:view];
|
|
}
|
|
}
|
|
[_enteringViews removeObject:tag];
|
|
[_enteringViewTargetValues removeObjectForKey:tag];
|
|
|
|
if (removeView) {
|
|
[self endAnimationsRecursive:view];
|
|
[view removeFromSuperview];
|
|
}
|
|
[_sharedTransitionManager finishSharedAnimation:[self viewForTag:tag] removeView:removeView];
|
|
}
|
|
|
|
- (void)endAnimationsRecursive:(REAUIView *)view
|
|
{
|
|
NSNumber *tag = [view reactTag];
|
|
|
|
if (tag == nil) {
|
|
return;
|
|
}
|
|
|
|
// we'll remove this view anyway when exiting from recursion,
|
|
// no need to remove it in `maybeDropAncestors`
|
|
[_ancestorsToRemove removeObject:tag];
|
|
|
|
for (REAUIView *child in [[view subviews] copy]) {
|
|
[self endAnimationsRecursive:child];
|
|
}
|
|
|
|
if ([_exitingViews objectForKey:tag]) {
|
|
[_exitingViews removeObjectForKey:tag];
|
|
[self maybeDropAncestors:view];
|
|
}
|
|
}
|
|
|
|
- (void)progressLayoutAnimationWithStyle:(NSDictionary *)newStyle
|
|
forTag:(NSNumber *)tag
|
|
isSharedTransition:(BOOL)isSharedTransition
|
|
{
|
|
[self setNewProps:[newStyle mutableCopy] forView:[self viewForTag:tag] convertFromAbsolute:isSharedTransition];
|
|
}
|
|
|
|
- (double)getDoubleOrZero:(NSNumber *)number
|
|
{
|
|
double doubleValue = [number doubleValue];
|
|
if (doubleValue != doubleValue) { // NaN != NaN
|
|
return 0;
|
|
}
|
|
return doubleValue;
|
|
}
|
|
|
|
- (void)setNewProps:(NSMutableDictionary *)newProps forView:(REAUIView *)view
|
|
{
|
|
[self setNewProps:newProps forView:view convertFromAbsolute:NO];
|
|
}
|
|
|
|
- (void)setNewProps:(NSMutableDictionary *)newProps
|
|
forView:(REAUIView *)view
|
|
convertFromAbsolute:(BOOL)convertFromAbsolute
|
|
{
|
|
if (newProps[@"height"]) {
|
|
double height = [self getDoubleOrZero:newProps[@"height"]];
|
|
double oldHeight = view.bounds.size.height;
|
|
view.bounds = CGRectMake(0, 0, view.bounds.size.width, height);
|
|
view.center = CGPointMake(view.center.x, view.center.y - oldHeight / 2.0 + view.bounds.size.height / 2.0);
|
|
[newProps removeObjectForKey:@"height"];
|
|
}
|
|
if (newProps[@"width"]) {
|
|
double width = [self getDoubleOrZero:newProps[@"width"]];
|
|
double oldWidth = view.bounds.size.width;
|
|
view.bounds = CGRectMake(0, 0, width, view.bounds.size.height);
|
|
view.center = CGPointMake(view.center.x + view.bounds.size.width / 2.0 - oldWidth / 2.0, view.center.y);
|
|
[newProps removeObjectForKey:@"width"];
|
|
}
|
|
|
|
bool needsViewPositionUpdate = false;
|
|
double centerX = view.center.x;
|
|
double centerY = view.center.y;
|
|
if (newProps[@"originX"]) {
|
|
needsViewPositionUpdate = true;
|
|
double originX = [self getDoubleOrZero:newProps[@"originX"]];
|
|
[newProps removeObjectForKey:@"originX"];
|
|
centerX = originX + view.bounds.size.width / 2.0;
|
|
}
|
|
if (newProps[@"originY"]) {
|
|
needsViewPositionUpdate = true;
|
|
double originY = [self getDoubleOrZero:newProps[@"originY"]];
|
|
[newProps removeObjectForKey:@"originY"];
|
|
centerY = originY + view.bounds.size.height / 2.0;
|
|
}
|
|
if (needsViewPositionUpdate) {
|
|
CGPoint newCenter = CGPointMake(centerX, centerY);
|
|
if (convertFromAbsolute) {
|
|
#if TARGET_OS_OSX
|
|
REAUIView *window = UIApplication.sharedApplication.keyWindow;
|
|
#else
|
|
REAUIView *window = RCTKeyWindow();
|
|
#endif
|
|
CGPoint convertedCenter = [window convertPoint:newCenter toView:view.superview];
|
|
view.center = convertedCenter;
|
|
} else {
|
|
view.center = newCenter;
|
|
}
|
|
}
|
|
|
|
if (newProps[@"transformMatrix"]) {
|
|
NSArray *matrix = newProps[@"transformMatrix"];
|
|
CGFloat a = [matrix[0] floatValue];
|
|
CGFloat b = [matrix[1] floatValue];
|
|
CGFloat c = [matrix[3] floatValue];
|
|
CGFloat d = [matrix[4] floatValue];
|
|
CGFloat tx = [matrix[6] floatValue];
|
|
CGFloat ty = [matrix[7] floatValue];
|
|
view.transform = CGAffineTransformMake(a, b, c, d, tx, ty);
|
|
[newProps removeObjectForKey:@"transformMatrix"];
|
|
}
|
|
|
|
NSMutableDictionary *componentDataByName = [_uiManager valueForKey:@"_componentDataByName"];
|
|
RCTComponentData *componentData = componentDataByName[@"RCTView"];
|
|
[componentData setProps:newProps forView:view];
|
|
}
|
|
|
|
- (NSDictionary *)prepareDataForAnimatingWorklet:(NSMutableDictionary *)values frameConfig:(FrameConfigType)frameConfig
|
|
{
|
|
if (frameConfig == EnteringFrame) {
|
|
NSDictionary *preparedData = @{
|
|
@"targetWidth" : values[@"width"],
|
|
@"targetHeight" : values[@"height"],
|
|
@"targetOriginX" : values[@"originX"],
|
|
@"targetOriginY" : values[@"originY"],
|
|
@"targetGlobalOriginX" : values[@"globalOriginX"],
|
|
@"targetGlobalOriginY" : values[@"globalOriginY"],
|
|
@"windowWidth" : values[@"windowWidth"],
|
|
@"windowHeight" : values[@"windowHeight"]
|
|
};
|
|
return preparedData;
|
|
} else {
|
|
NSDictionary *preparedData = @{
|
|
@"currentWidth" : values[@"width"],
|
|
@"currentHeight" : values[@"height"],
|
|
@"currentOriginX" : values[@"originX"],
|
|
@"currentOriginY" : values[@"originY"],
|
|
@"currentGlobalOriginX" : values[@"globalOriginX"],
|
|
@"currentGlobalOriginY" : values[@"globalOriginY"],
|
|
@"windowWidth" : values[@"windowWidth"],
|
|
@"windowHeight" : values[@"windowHeight"]
|
|
};
|
|
return preparedData;
|
|
}
|
|
}
|
|
|
|
- (NSMutableDictionary *)prepareDataForLayoutAnimatingWorklet:(NSMutableDictionary *)currentValues
|
|
targetValues:(NSMutableDictionary *)targetValues
|
|
{
|
|
NSMutableDictionary *preparedData = [NSMutableDictionary new];
|
|
preparedData[@"currentWidth"] = currentValues[@"width"];
|
|
preparedData[@"currentHeight"] = currentValues[@"height"];
|
|
preparedData[@"currentOriginX"] = currentValues[@"originX"];
|
|
preparedData[@"currentOriginY"] = currentValues[@"originY"];
|
|
preparedData[@"currentGlobalOriginX"] = currentValues[@"globalOriginX"];
|
|
preparedData[@"currentGlobalOriginY"] = currentValues[@"globalOriginY"];
|
|
preparedData[@"targetWidth"] = targetValues[@"width"];
|
|
preparedData[@"targetHeight"] = targetValues[@"height"];
|
|
preparedData[@"targetOriginX"] = targetValues[@"originX"];
|
|
preparedData[@"targetOriginY"] = targetValues[@"originY"];
|
|
preparedData[@"targetGlobalOriginX"] = targetValues[@"globalOriginX"];
|
|
preparedData[@"targetGlobalOriginY"] = targetValues[@"globalOriginY"];
|
|
preparedData[@"windowWidth"] = currentValues[@"windowWidth"];
|
|
preparedData[@"windowHeight"] = currentValues[@"windowHeight"];
|
|
return preparedData;
|
|
}
|
|
|
|
- (void)registerExitingAncestors:(REAUIView *)child
|
|
{
|
|
[self registerExitingAncestors:child exitingSubviewsCount:1];
|
|
}
|
|
|
|
- (void)registerExitingAncestors:(REAUIView *)child exitingSubviewsCount:(int)exitingSubviewsCount
|
|
{
|
|
NSNumber *childTag = child.reactTag;
|
|
REAUIView *parent = child.superview;
|
|
|
|
UIViewController *childController = child.reactViewController;
|
|
|
|
// only register ancestors whose `reactViewController` is the same as `child`'s.
|
|
// The idea is that, if a whole ViewController is unmounted, we won't want to run
|
|
// the exiting animation since all the views will disappear immediately anyway
|
|
while (parent != nil && parent.reactViewController == childController &&
|
|
![parent isKindOfClass:[RCTRootView class]]) {
|
|
NSNumber *parentTag = parent.reactTag;
|
|
if (parentTag != nil) {
|
|
_exitingSubviewsCountMap[parent.reactTag] =
|
|
@([_exitingSubviewsCountMap[parent.reactTag] intValue] + exitingSubviewsCount);
|
|
_exitingParentTags[childTag] = parentTag;
|
|
childTag = parentTag;
|
|
}
|
|
parent = parent.superview;
|
|
}
|
|
}
|
|
|
|
- (void)maybeDropAncestors:(REAUIView *)child
|
|
{
|
|
REAUIView *parent = child.superview;
|
|
NSNumber *parentTag = _exitingParentTags[child.reactTag];
|
|
[_exitingParentTags removeObjectForKey:child.reactTag];
|
|
|
|
while ((parent != nil || parentTag != nil) && ![parent isKindOfClass:[RCTRootView class]]) {
|
|
REAUIView *view = parent;
|
|
NSNumber *viewTag = parentTag;
|
|
parentTag = _exitingParentTags[viewTag];
|
|
REAUIView *viewByTag = [self viewForTag:viewTag];
|
|
parent = view.superview;
|
|
|
|
if (view == nil) {
|
|
if (viewByTag == nil) {
|
|
// the view was already removed from both native and RN hierarchies
|
|
// we can safely forget that it had any animated children
|
|
[_ancestorsToRemove removeObject:viewTag];
|
|
[_exitingSubviewsCountMap removeObjectForKey:viewTag];
|
|
[_exitingParentTags removeObjectForKey:viewTag];
|
|
continue;
|
|
}
|
|
// the child was dettached from view, but view is still
|
|
// in the native and RN hierarchy
|
|
view = viewByTag;
|
|
}
|
|
|
|
if (view.reactTag == nil) {
|
|
// we skip over views with no tag when registering parent tags,
|
|
// so we shouldn't go to the parent of viewTag yet
|
|
parentTag = viewTag;
|
|
continue;
|
|
}
|
|
|
|
int trackingCount = [_exitingSubviewsCountMap[view.reactTag] intValue] - 1;
|
|
if (trackingCount <= 0) {
|
|
if ([_ancestorsToRemove containsObject:view.reactTag]) {
|
|
[_ancestorsToRemove removeObject:view.reactTag];
|
|
if (![_exitingViews objectForKey:view.reactTag]) {
|
|
[view removeFromSuperview];
|
|
}
|
|
}
|
|
[_exitingSubviewsCountMap removeObjectForKey:view.reactTag];
|
|
[_exitingParentTags removeObjectForKey:view.reactTag];
|
|
} else {
|
|
_exitingSubviewsCountMap[view.reactTag] = @(trackingCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)startAnimationsRecursive:(REAUIView *)view
|
|
shouldRemoveSubviewsWithoutAnimations:(BOOL)shouldRemoveSubviewsWithoutAnimations
|
|
shouldAnimate:(BOOL)shouldAnimate;
|
|
{
|
|
if (!view.reactTag) {
|
|
return NO;
|
|
}
|
|
|
|
UIViewController *viewController = view.reactViewController;
|
|
|
|
// `startAnimationsRecursive:shouldRemoveSubviewsWithoutAnimations:`
|
|
// is called on a detached view tree, so the `viewController` should be `nil`.
|
|
// If it's not, we're descending into another `UIViewController`.
|
|
// We don't want to run animations inside it (since it causes issues with RNScreens),
|
|
// so instead clean up the subtree and return `NO`.
|
|
if (viewController != nil) {
|
|
[self removeAnimationsFromSubtree:view];
|
|
return NO;
|
|
}
|
|
|
|
shouldAnimate = [self shouldAnimateExiting:view.reactTag shouldAnimate:shouldAnimate];
|
|
|
|
BOOL hasExitAnimation = shouldAnimate &&
|
|
([self hasAnimationForTag:view.reactTag type:EXITING] || [_exitingViews objectForKey:view.reactTag]);
|
|
BOOL hasAnimatedChildren = NO;
|
|
shouldRemoveSubviewsWithoutAnimations = shouldRemoveSubviewsWithoutAnimations && !hasExitAnimation;
|
|
NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init];
|
|
|
|
for (REAUIView *subview in [view.reactSubviews copy]) {
|
|
if ([self startAnimationsRecursive:subview
|
|
shouldRemoveSubviewsWithoutAnimations:shouldRemoveSubviewsWithoutAnimations
|
|
shouldAnimate:shouldAnimate]) {
|
|
hasAnimatedChildren = YES;
|
|
} else if (shouldRemoveSubviewsWithoutAnimations) {
|
|
[toBeRemoved addObject:subview];
|
|
}
|
|
}
|
|
|
|
BOOL wantAnimateExit = hasExitAnimation || hasAnimatedChildren;
|
|
|
|
REASnapshot *before;
|
|
if (hasExitAnimation) {
|
|
before = [[REASnapshot alloc] init:view];
|
|
}
|
|
|
|
// start exit animation
|
|
if (hasExitAnimation && ![_exitingViews objectForKey:view.reactTag]) {
|
|
NSDictionary *preparedValues = [self prepareDataForAnimatingWorklet:before.values frameConfig:ExitingFrame];
|
|
[_exitingViews setObject:view forKey:view.reactTag];
|
|
[self registerExitingAncestors:view];
|
|
_startAnimationForTag(view.reactTag, EXITING, preparedValues);
|
|
}
|
|
|
|
// NOTE: even though this view is still visible,
|
|
// since it's removed from the React tree, we won't
|
|
// start new animations for it, and might as well remove
|
|
// the layout animation config now
|
|
_clearAnimationConfigForTag(view.reactTag);
|
|
|
|
if (!wantAnimateExit) {
|
|
return NO;
|
|
}
|
|
|
|
if (hasAnimatedChildren) {
|
|
[_ancestorsToRemove addObject:view.reactTag];
|
|
}
|
|
|
|
for (REAUIView *child in toBeRemoved) {
|
|
[view removeReactSubview:child];
|
|
}
|
|
|
|
// we don't want user interaction on exiting views
|
|
view.userInteractionEnabled = NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)reattachAnimatedChildren:(NSArray<id<RCTComponent>> *)children
|
|
toContainer:(id<RCTComponent>)container
|
|
atIndices:(NSArray<NSNumber *> *)indices
|
|
{
|
|
if (![container isKindOfClass:[REAUIView class]]) {
|
|
return;
|
|
}
|
|
|
|
// since we reattach only some of the views,
|
|
// we count the views we DIDN'T reattach
|
|
// and shift later views' indices by that number
|
|
// to make sure they appear at correct relative posisitons
|
|
// in the `subviews` array
|
|
int skippedViewsCount = 0;
|
|
|
|
for (int i = 0; i < children.count; i++) {
|
|
id<RCTComponent> child = children[i];
|
|
if (![child isKindOfClass:[REAUIView class]]) {
|
|
skippedViewsCount++;
|
|
continue;
|
|
}
|
|
REAUIView *childView = (REAUIView *)child;
|
|
NSNumber *originalIndex = indices[i];
|
|
if ([self startAnimationsRecursive:childView shouldRemoveSubviewsWithoutAnimations:YES shouldAnimate:YES]) {
|
|
[(REAUIView *)container insertSubview:childView atIndex:[originalIndex intValue] - skippedViewsCount];
|
|
int exitingSubviewsCount = [_exitingSubviewsCountMap[childView.reactTag] intValue];
|
|
if ([_exitingViews objectForKey:childView.reactTag] != nil) {
|
|
exitingSubviewsCount++;
|
|
}
|
|
[self registerExitingAncestors:childView exitingSubviewsCount:exitingSubviewsCount];
|
|
} else {
|
|
skippedViewsCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)onViewCreate:(REAUIView *)view after:(REASnapshot *)after
|
|
{
|
|
NSMutableDictionary *targetValues = after.values;
|
|
NSDictionary *preparedValues = [self prepareDataForAnimatingWorklet:targetValues frameConfig:EnteringFrame];
|
|
[_enteringViews addObject:view.reactTag];
|
|
_startAnimationForTag(view.reactTag, ENTERING, preparedValues);
|
|
}
|
|
|
|
- (void)onViewUpdate:(REAUIView *)view before:(REASnapshot *)before after:(REASnapshot *)after
|
|
{
|
|
NSMutableDictionary *targetValues = after.values;
|
|
NSMutableDictionary *currentValues = before.values;
|
|
|
|
NSDictionary *preparedValues = [self prepareDataForLayoutAnimatingWorklet:currentValues targetValues:targetValues];
|
|
_startAnimationForTag(view.reactTag, LAYOUT, preparedValues);
|
|
}
|
|
|
|
- (REASnapshot *)prepareSnapshotBeforeMountForView:(REAUIView *)view
|
|
{
|
|
return [[REASnapshot alloc] init:view];
|
|
}
|
|
|
|
- (void)removeAnimationsFromSubtree:(REAUIView *)view
|
|
{
|
|
REANodeFind(view, ^int(id<RCTComponent> view) {
|
|
if (!self->_hasAnimationForTag(view.reactTag, SHARED_ELEMENT_TRANSITION)) {
|
|
self->_clearAnimationConfigForTag(view.reactTag);
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
- (void)viewDidMount:(REAUIView *)view withBeforeSnapshot:(nonnull REASnapshot *)before withNewFrame:(CGRect)frame
|
|
{
|
|
LayoutAnimationType type = before == nil ? ENTERING : LAYOUT;
|
|
NSNumber *viewTag = view.reactTag;
|
|
if (_hasAnimationForTag(viewTag, type)) {
|
|
REASnapshot *after = [[REASnapshot alloc] init:view];
|
|
if (before == nil) {
|
|
[self onViewCreate:view after:after];
|
|
} else {
|
|
[self onViewUpdate:view before:before after:after];
|
|
}
|
|
} else if (type == LAYOUT && [_enteringViews containsObject:[view reactTag]]) {
|
|
_enteringViewTargetValues[[view reactTag]] = [[REASnapshot alloc] init:view];
|
|
[self setNewProps:before.values forView:view];
|
|
}
|
|
|
|
if (_hasAnimationForTag(viewTag, SHARED_ELEMENT_TRANSITION)) {
|
|
if (type == ENTERING) {
|
|
[_sharedTransitionManager notifyAboutNewView:view];
|
|
#ifndef NDEBUG
|
|
_checkDuplicateSharedTag(view, viewTag);
|
|
#endif
|
|
} else {
|
|
[_sharedTransitionManager notifyAboutViewLayout:view withViewFrame:frame];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)viewsDidLayout
|
|
{
|
|
[_sharedTransitionManager viewsDidLayout];
|
|
}
|
|
|
|
- (void)setFindPrecedingViewTagForTransitionBlock:
|
|
(REAFindPrecedingViewTagForTransitionBlock)findPrecedingViewTagForTransition
|
|
{
|
|
[_sharedTransitionManager setFindPrecedingViewTagForTransitionBlock:findPrecedingViewTagForTransition];
|
|
}
|
|
|
|
- (void)setCancelAnimationBlock:(REACancelAnimationBlock)animationCancellingBlock
|
|
{
|
|
[_sharedTransitionManager setCancelAnimationBlock:animationCancellingBlock];
|
|
}
|
|
|
|
- (BOOL)hasAnimationForTag:(NSNumber *)tag type:(LayoutAnimationType)type
|
|
{
|
|
return _hasAnimationForTag(tag, type);
|
|
}
|
|
|
|
- (BOOL)shouldAnimateExiting:(NSNumber *)tag shouldAnimate:(BOOL)shouldAnimate
|
|
{
|
|
return _shouldAnimateExiting(tag, shouldAnimate);
|
|
}
|
|
|
|
- (void)clearAnimationConfigForTag:(NSNumber *)tag
|
|
{
|
|
_clearAnimationConfigForTag(tag);
|
|
}
|
|
|
|
- (void)clearSharedTransitionConfigForTag:(NSNumber *)tag
|
|
{
|
|
_clearSharedTransitionConfigForTag(tag);
|
|
}
|
|
|
|
- (void)startAnimationForTag:(NSNumber *)tag type:(LayoutAnimationType)type yogaValues:(NSDictionary *)yogaValues
|
|
{
|
|
_startAnimationForTag(tag, type, yogaValues);
|
|
}
|
|
|
|
- (void)onScreenRemoval:(REAUIView *)screen stack:(REAUIView *)stack
|
|
{
|
|
[_sharedTransitionManager onScreenRemoval:screen stack:stack];
|
|
}
|
|
|
|
@end
|