Files
smart-city-digital-twin-mar…/smart-app-city/frontend/node_modules/react-native-reanimated/apple/LayoutReanimation/REASwizzledUIManager.mm
Eric FELIXINE e30ae8ed09 feat(smart-app): implement complete mobile app MVP
- 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
2026-06-01 18:00:35 -04:00

383 lines
14 KiB
Plaintext

#import <RNReanimated/FeaturesConfig.h>
#import <RNReanimated/REASwizzledUIManager.h>
#import <RNReanimated/REAUIKit.h>
#import <RNReanimated/REAUtils.h>
#import <React/RCTLayoutAnimation.h>
#import <React/RCTLayoutAnimationGroup.h>
#import <React/RCTRootShadowView.h>
#import <React/RCTRootViewInternal.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <objc/runtime.h>
@interface RCTUIManager (Reanimated)
@property REAAnimationsManager *animationsManager;
- (NSArray<id<RCTComponent>> *)_childrenToRemoveFromContainer:(id<RCTComponent>)container
atIndices:(NSArray<NSNumber *> *)atIndices;
@end
@implementation RCTUIManager (Reanimated)
@dynamic animationsManager;
- (void)setAnimationsManager:(REAAnimationsManager *)animationsManager
{
objc_setAssociatedObject(self, @selector(animationsManager), animationsManager, OBJC_ASSOCIATION_RETAIN);
}
- (id)animationsManager
{
return objc_getAssociatedObject(self, @selector(animationsManager));
}
@end
@implementation REASwizzledUIManager
std::atomic<uint> isFlushingBlocks;
std::atomic<bool> hasPendingBlocks;
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager
withAnimationManager:(REAAnimationsManager *)animationsManager
{
if (self = [super init]) {
isFlushingBlocks = 0;
hasPendingBlocks = false;
[uiManager setAnimationsManager:animationsManager];
[self swizzleMethods];
IMP isExecutingUpdatesBatchImpl = imp_implementationWithBlock(^() {
return hasPendingBlocks || isFlushingBlocks > 0;
});
class_addMethod([RCTUIManager class], @selector(isExecutingUpdatesBatch), isExecutingUpdatesBatchImpl, "");
}
return self;
}
- (void)swizzleMethods
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[REAUtils swizzleMethod:@selector(uiBlockWithLayoutUpdateForRootView:)
forClass:[RCTUIManager class]
with:@selector(reanimated_uiBlockWithLayoutUpdateForRootView:)
fromClass:[self class]];
SEL manageChildrenOriginal = @selector
(_manageChildren:moveFromIndices:moveToIndices:addChildReactTags:addAtIndices:removeAtIndices:registry:);
SEL manageChildrenReanimated =
@selector(reanimated_manageChildren:
moveFromIndices:moveToIndices:addChildReactTags:addAtIndices:removeAtIndices:registry:);
[REAUtils swizzleMethod:manageChildrenOriginal
forClass:[RCTUIManager class]
with:manageChildrenReanimated
fromClass:[self class]];
[REAUtils swizzleMethod:@selector(addUIBlock:)
forClass:[RCTUIManager class]
with:@selector(reanimated_addUIBlock:)
fromClass:[self class]];
[REAUtils swizzleMethod:@selector(prependUIBlock:)
forClass:[RCTUIManager class]
with:@selector(reanimated_prependUIBlock:)
fromClass:[self class]];
[REAUtils swizzleMethod:@selector(flushUIBlocksWithCompletion:)
forClass:[RCTUIManager class]
with:@selector(reanimated_flushUIBlocksWithCompletion:)
fromClass:[self class]];
});
}
- (void)reanimated_manageChildren:(NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
{
bool isLayoutAnimationEnabled = reanimated::FeaturesConfig::isLayoutAnimationEnabled();
id<RCTComponent> container;
NSArray<id<RCTComponent>> *permanentlyRemovedChildren;
BOOL containerIsRootOfViewController = NO;
RCTUIManager *originalSelf = (RCTUIManager *)self;
if (isLayoutAnimationEnabled) {
container = registry[containerTag];
permanentlyRemovedChildren = [originalSelf _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
if ([container isKindOfClass:[REAUIView class]]) {
UIViewController *controller = ((REAUIView *)container).reactViewController;
UIViewController *parentController = ((REAUIView *)container).superview.reactViewController;
containerIsRootOfViewController = controller != parentController;
}
// we check if the container we`re removing from is a root view
// of some view controller. In that case, we skip running exiting animations
// in its children, to prevent issues with RN Screens.
if (containerIsRootOfViewController) {
NSArray<id<RCTComponent>> *permanentlyRemovedChildren =
[originalSelf _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
for (REAUIView *view in permanentlyRemovedChildren) {
[originalSelf.animationsManager endAnimationsRecursive:view];
[originalSelf.animationsManager removeAnimationsFromSubtree:view];
}
[originalSelf.animationsManager onScreenRemoval:(REAUIView *)permanentlyRemovedChildren[0]
stack:(REAUIView *)container];
}
}
// call original method
[self reanimated_manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
addAtIndices:addAtIndices
removeAtIndices:removeAtIndices
registry:registry];
if (!isLayoutAnimationEnabled) {
return;
}
if (containerIsRootOfViewController) {
return;
}
// we sort the (index, view) pairs to make sure we insert views back in order
NSMutableArray<NSArray<id> *> *removedViewsWithIndices = [NSMutableArray new];
for (int i = 0; i < removeAtIndices.count; i++) {
removedViewsWithIndices[i] = @[ removeAtIndices[i], permanentlyRemovedChildren[i] ];
}
[removedViewsWithIndices
sortUsingComparator:^NSComparisonResult(NSArray<id> *_Nonnull obj1, NSArray<id> *_Nonnull obj2) {
return [(NSNumber *)obj1[0] compare:(NSNumber *)obj2[0]];
}];
[originalSelf.animationsManager reattachAnimatedChildren:permanentlyRemovedChildren
toContainer:container
atIndices:removeAtIndices];
}
- (RCTViewManagerUIBlock)reanimated_uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
{
if (!reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
return [self reanimated_uiBlockWithLayoutUpdateForRootView:rootShadowView];
}
RCTUIManager *originalSelf = (RCTUIManager *)self;
#if REACT_NATIVE_MINOR_VERSION >= 73
NSPointerArray *affectedShadowViews = [NSPointerArray weakObjectsPointerArray];
#else
NSHashTable<RCTShadowView *> *affectedShadowViews = [NSHashTable weakObjectsHashTable];
#endif
[rootShadowView layoutWithAffectedShadowViews:affectedShadowViews];
if (!affectedShadowViews.count) {
// no frame change results in no UI update block
return nil;
}
typedef struct {
CGRect frame;
UIUserInterfaceLayoutDirection layoutDirection;
BOOL isNew;
BOOL parentIsNew;
RCTDisplayType displayType;
} RCTFrameData;
// Construct arrays then hand off to main thread
NSUInteger count = affectedShadowViews.count;
NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
{
NSUInteger index = 0;
RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
for (RCTShadowView *shadowView in affectedShadowViews) {
reactTags[index] = shadowView.reactTag;
RCTLayoutMetrics layoutMetrics = shadowView.layoutMetrics;
frameDataArray[index++] = (RCTFrameData){
layoutMetrics.frame,
layoutMetrics.layoutDirection,
shadowView.isNewView,
shadowView.superview.isNewView,
layoutMetrics.displayType};
}
}
for (RCTShadowView *shadowView in affectedShadowViews) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
NSNumber *reactTag = shadowView.reactTag;
if (shadowView.onLayout) {
CGRect frame = shadowView.layoutMetrics.frame;
shadowView.onLayout(@{
@"layout" : @{
@"x" : @(frame.origin.x),
@"y" : @(frame.origin.y),
@"width" : @(frame.size.width),
@"height" : @(frame.size.height),
},
});
}
if (RCTIsReactRootView(reactTag) && [shadowView isKindOfClass:[RCTRootShadowView class]]) {
CGSize contentSize = shadowView.layoutMetrics.frame.size;
RCTExecuteOnMainQueue(^{
REAUIView *view = [originalSelf viewForReactTag:(NSNumber *)reactTag];
RCTAssert(view != nil, @"view (for ID %@) not found", reactTag);
RCTRootView *rootView = (RCTRootView *)[view superview];
if ([rootView isKindOfClass:[RCTRootView class]]) {
rootView.intrinsicContentSize = contentSize;
}
});
}
}
// Perform layout (possibly animated)
return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes;
RCTLayoutAnimationGroup *layoutAnimationGroup = [uiManager valueForKey:@"_layoutAnimationGroup"];
__block NSUInteger completionsCalled = 0;
NSMutableDictionary<NSNumber *, REASnapshot *> *snapshotsBefore = [NSMutableDictionary dictionary];
NSInteger index = 0;
for (NSNumber *reactTag in reactTags) {
RCTFrameData frameData = frameDataArray[index++];
REAUIView *view = [originalSelf viewForReactTag:(NSNumber *)reactTag];
CGRect frame = frameData.frame;
UIUserInterfaceLayoutDirection layoutDirection = frameData.layoutDirection;
BOOL isNew = frameData.isNew;
RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation;
BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew;
RCTLayoutAnimation *creatingLayoutAnimation =
shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil;
BOOL isHidden = frameData.displayType == RCTDisplayTypeNone;
void (^completion)(BOOL) = ^(BOOL finished) {
completionsCalled++;
if (layoutAnimationGroup.callback && completionsCalled == count) {
layoutAnimationGroup.callback(@[ @(finished) ]);
// It's unsafe to call this callback more than once, so we nil it out here
// to make sure that doesn't happen.
layoutAnimationGroup.callback = nil;
}
};
if (view.reactLayoutDirection != layoutDirection) {
view.reactLayoutDirection = layoutDirection;
}
if (view.isHidden != isHidden) {
view.hidden = isHidden;
}
// Reanimated changes /start
REASnapshot *snapshotBefore =
isNew ? nil : [originalSelf.animationsManager prepareSnapshotBeforeMountForView:view];
snapshotsBefore[reactTag] = snapshotBefore;
// Reanimated changes /end
if (creatingLayoutAnimation) {
// Animate view creation
[view reactSetFrame:frame];
CATransform3D finalTransform = view.layer.transform;
CGFloat finalOpacity = view.layer.opacity;
NSString *property = creatingLayoutAnimation.property;
if ([property isEqualToString:@"scaleXY"]) {
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
} else if ([property isEqualToString:@"scaleX"]) {
view.layer.transform = CATransform3DMakeScale(0, 1, 0);
} else if ([property isEqualToString:@"scaleY"]) {
view.layer.transform = CATransform3DMakeScale(1, 0, 0);
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = 0.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@", creatingLayoutAnimation.property);
}
[creatingLayoutAnimation
performAnimations:^{
if ([property isEqualToString:@"scaleX"] || [property isEqualToString:@"scaleY"] ||
[property isEqualToString:@"scaleXY"]) {
view.layer.transform = finalTransform;
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = finalOpacity;
}
}
withCompletionBlock:completion];
} else if (updatingLayoutAnimation) {
// Animate view update
[updatingLayoutAnimation
performAnimations:^{
[view reactSetFrame:frame];
}
withCompletionBlock:completion];
} else {
// Update without animation
[view reactSetFrame:frame];
completion(YES);
}
}
// Reanimated changes /start
index = 0;
for (NSNumber *reactTag in reactTags) {
RCTFrameData frameData = frameDataArray[index++];
REAUIView *view = [originalSelf viewForReactTag:(NSNumber *)reactTag];
BOOL isNew = frameData.isNew;
CGRect frame = frameData.frame;
REASnapshot *snapshotBefore = snapshotsBefore[reactTag];
if (isNew || snapshotBefore != nil) {
[originalSelf.animationsManager viewDidMount:view withBeforeSnapshot:snapshotBefore withNewFrame:frame];
}
}
// Clean up
// below line serves as this one uiManager->_layoutAnimationGroup = nil;, because we don't have access to the
// private field
[uiManager setValue:nil forKey:@"_layoutAnimationGroup"];
[originalSelf.animationsManager viewsDidLayout];
// Reanimated changes /end
};
}
- (void)reanimated_addUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssertUIManagerQueue();
hasPendingBlocks = true;
[self reanimated_addUIBlock:block];
}
- (void)reanimated_prependUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssertUIManagerQueue();
hasPendingBlocks = true;
[self reanimated_prependUIBlock:block];
}
- (void)reanimated_flushUIBlocksWithCompletion:(void (^)(void))completion
{
RCTAssertUIManagerQueue();
if (hasPendingBlocks) {
++isFlushingBlocks;
hasPendingBlocks = false;
[self reanimated_addUIBlock:^(
__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
--isFlushingBlocks;
}];
}
[self reanimated_flushUIBlocksWithCompletion:completion];
}
@end