- 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
607 lines
20 KiB
Plaintext
607 lines
20 KiB
Plaintext
#import <RNReanimated/READisplayLink.h>
|
|
#import <RNReanimated/REAModule.h>
|
|
#import <RNReanimated/REANodesManager.h>
|
|
#import <RNReanimated/REAUIKit.h>
|
|
#import <React/RCTConvert.h>
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
#import <React/RCTComponentViewRegistry.h>
|
|
#import <React/RCTMountingManager.h>
|
|
#import <React/RCTSurfacePresenter.h>
|
|
#import <react/renderer/core/ShadowNode.h>
|
|
#import <react/renderer/uimanager/UIManager.h>
|
|
#else
|
|
#import <stdatomic.h>
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
#if __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
|
|
#import <RNScreens/RNSScreenStackHeaderConfig.h>
|
|
#endif // __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
using namespace facebook::react;
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
// Interface below has been added in order to use private methods of RCTUIManager,
|
|
// RCTUIManager#UpdateView is a React Method which is exported to JS but in
|
|
// Objective-C it stays private
|
|
// RCTUIManager#setNeedsLayout is a method which updated layout only which
|
|
// in its turn will trigger relayout if no batch has been activated
|
|
|
|
@interface RCTUIManager ()
|
|
|
|
- (void)updateView:(nonnull NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props;
|
|
|
|
- (void)setNeedsLayout;
|
|
|
|
- (bool)isExecutingUpdatesBatch;
|
|
|
|
@end
|
|
|
|
@interface RCTUIManager (SyncUpdates)
|
|
|
|
- (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer;
|
|
|
|
@end
|
|
|
|
@interface ComponentUpdate : NSObject
|
|
|
|
@property (nonnull) NSMutableDictionary *props;
|
|
@property (nonnull) NSNumber *viewTag;
|
|
@property (nonnull) NSString *viewName;
|
|
|
|
@end
|
|
|
|
@implementation ComponentUpdate
|
|
@end
|
|
|
|
@implementation RCTUIManager (SyncUpdates)
|
|
|
|
- (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer
|
|
{
|
|
// before we run uimanager batch complete, we override coordinator observers list
|
|
// to avoid observers from firing. This is done because we only want the uimanager
|
|
// related operations to run and not all other operations (including the ones enqueued
|
|
// by reanimated or native animated modules) from being scheduled. If we were to allow
|
|
// other modules to execute some logic from this sync uimanager run there is a possibility
|
|
// that the commands will execute out of order or that we intercept a batch of commands that
|
|
// those modules may be in a middle of (we verify that batch isn't in progress for uimodule
|
|
// but can't do the same for all remaining modules)
|
|
|
|
// store reference to the observers array
|
|
id oldObservers = [self.observerCoordinator valueForKey:@"_observers"];
|
|
|
|
// temporarily replace observers with a table containing just nodesManager (we need
|
|
// this to capture mounting block)
|
|
NSHashTable<id<RCTUIManagerObserver>> *soleObserver = [NSHashTable new];
|
|
[soleObserver addObject:observer];
|
|
[self.observerCoordinator setValue:soleObserver forKey:@"_observers"];
|
|
|
|
// run batch
|
|
[self batchDidComplete];
|
|
// restore old observers table
|
|
[self.observerCoordinator setValue:oldObservers forKey:@"_observers"];
|
|
}
|
|
|
|
@end
|
|
|
|
#ifndef RCT_NEW_ARCH_ENABLED
|
|
|
|
@interface REASyncUpdateObserver : NSObject <RCTUIManagerObserver>
|
|
@end
|
|
|
|
@implementation REASyncUpdateObserver {
|
|
volatile void (^_mounting)(void);
|
|
volatile BOOL _waitTimedOut;
|
|
dispatch_semaphore_t _semaphore;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_mounting = nil;
|
|
_waitTimedOut = NO;
|
|
_semaphore = dispatch_semaphore_create(0);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
RCTAssert(_mounting == nil, @"Mounting block was set but never executed. This may lead to UI inconsistencies");
|
|
}
|
|
|
|
- (void)unblockUIThread
|
|
{
|
|
RCTAssertUIManagerQueue();
|
|
dispatch_semaphore_signal(_semaphore);
|
|
}
|
|
|
|
- (void)waitAndMountWithTimeout:(NSTimeInterval)timeout
|
|
{
|
|
RCTAssertMainQueue();
|
|
long result = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC));
|
|
if (result != 0) {
|
|
@synchronized(self) {
|
|
_waitTimedOut = YES;
|
|
}
|
|
}
|
|
if (_mounting) {
|
|
_mounting();
|
|
_mounting = nil;
|
|
}
|
|
}
|
|
|
|
- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block
|
|
{
|
|
RCTAssertUIManagerQueue();
|
|
@synchronized(self) {
|
|
if (_waitTimedOut) {
|
|
return NO;
|
|
} else {
|
|
_mounting = block;
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
@implementation REANodesManager {
|
|
READisplayLink *_displayLink;
|
|
BOOL _wantRunUpdates;
|
|
NSMutableArray<REAOnAnimationCallback> *_onAnimationCallbacks;
|
|
BOOL _tryRunBatchUpdatesSynchronously;
|
|
REAEventHandler _eventHandler;
|
|
NSMutableDictionary<NSNumber *, ComponentUpdate *> *_componentUpdateBuffer;
|
|
NSMutableDictionary<NSNumber *, REAUIView *> *_viewRegistry;
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
__weak RCTBridge *_bridge;
|
|
REAPerformOperations _performOperations;
|
|
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
|
|
NSMutableDictionary<NSNumber *, NSMutableDictionary *> *_operationsInBatch;
|
|
#else
|
|
NSMutableArray<REANativeAnimationOp> *_operationsInBatch;
|
|
volatile atomic_bool _shouldFlushUpdateBuffer;
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
}
|
|
|
|
- (READisplayLink *)getDisplayLink
|
|
{
|
|
RCTAssertMainQueue();
|
|
|
|
if (!_displayLink) {
|
|
_displayLink = [READisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)];
|
|
#if !TARGET_OS_OSX
|
|
_displayLink.preferredFramesPerSecond = 120; // will fallback to 60 fps for devices without Pro Motion display
|
|
#endif // TARGET_OS_OSX
|
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
}
|
|
return _displayLink;
|
|
}
|
|
|
|
- (void)useDisplayLinkOnMainQueue:(CADisplayLinkOperation)displayLinkOperation
|
|
{
|
|
__weak __typeof__(self) weakSelf = self;
|
|
RCTExecuteOnMainQueue(^{
|
|
__typeof__(self) strongSelf = weakSelf;
|
|
if (strongSelf == nil) {
|
|
return;
|
|
}
|
|
displayLinkOperation([strongSelf getDisplayLink]);
|
|
});
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
- (nonnull instancetype)initWithModule:(REAModule *)reanimatedModule
|
|
bridge:(RCTBridge *)bridge
|
|
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
|
|
{
|
|
if ((self = [super init])) {
|
|
_bridge = bridge;
|
|
_surfacePresenter = surfacePresenter;
|
|
_reanimatedModule = reanimatedModule;
|
|
_wantRunUpdates = NO;
|
|
_onAnimationCallbacks = [NSMutableArray new];
|
|
_operationsInBatch = [NSMutableDictionary new];
|
|
_componentUpdateBuffer = [NSMutableDictionary new];
|
|
_viewRegistry = [_uiManager valueForKey:@"_viewRegistry"];
|
|
_eventHandler = ^(id<RCTEvent> event) {
|
|
// no-op
|
|
};
|
|
}
|
|
#else
|
|
- (instancetype)initWithModule:(REAModule *)reanimatedModule uiManager:(RCTUIManager *)uiManager
|
|
{
|
|
if ((self = [super init])) {
|
|
_reanimatedModule = reanimatedModule;
|
|
_uiManager = uiManager;
|
|
_wantRunUpdates = NO;
|
|
_onAnimationCallbacks = [NSMutableArray new];
|
|
_operationsInBatch = [NSMutableArray new];
|
|
_componentUpdateBuffer = [NSMutableDictionary new];
|
|
_viewRegistry = [_uiManager valueForKey:@"_viewRegistry"];
|
|
_shouldFlushUpdateBuffer = false;
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
[self useDisplayLinkOnMainQueue:^(READisplayLink *displayLink) {
|
|
[displayLink setPaused:YES];
|
|
}];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
_eventHandler = nil;
|
|
[self useDisplayLinkOnMainQueue:^(READisplayLink *displayLink) {
|
|
[displayLink invalidate];
|
|
}];
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
|
|
{
|
|
_surfacePresenter = surfacePresenter;
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
- (void)operationsBatchDidComplete
|
|
{
|
|
if (![[self getDisplayLink] isPaused]) {
|
|
// if display link is set it means some of the operations that have run as a part of the batch
|
|
// requested updates. We want updates to be run in the same frame as in which operations have
|
|
// been scheduled as it may mean the new view has just been mounted and expects its initial
|
|
// props to be calculated.
|
|
// Unfortunately if the operation has just scheduled animation callback it won't run until the
|
|
// next frame, so it's being triggered manually.
|
|
_wantRunUpdates = YES;
|
|
[self performOperations];
|
|
}
|
|
}
|
|
|
|
- (void)postOnAnimation:(REAOnAnimationCallback)clb
|
|
{
|
|
[_onAnimationCallbacks addObject:clb];
|
|
[self startUpdatingOnAnimationFrame];
|
|
}
|
|
|
|
- (void)registerEventHandler:(REAEventHandler)eventHandler
|
|
{
|
|
_eventHandler = eventHandler;
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
- (void)registerPerformOperations:(REAPerformOperations)performOperations
|
|
{
|
|
_performOperations = performOperations;
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
- (void)startUpdatingOnAnimationFrame
|
|
{
|
|
[[self getDisplayLink] setPaused:NO];
|
|
}
|
|
|
|
- (void)stopUpdatingOnAnimationFrame
|
|
{
|
|
[[self getDisplayLink] setPaused:YES];
|
|
}
|
|
|
|
- (void)onAnimationFrame:(READisplayLink *)displayLink
|
|
{
|
|
NSArray<REAOnAnimationCallback> *callbacks = _onAnimationCallbacks;
|
|
_onAnimationCallbacks = [NSMutableArray new];
|
|
|
|
// When one of the callbacks would postOnAnimation callback we don't want
|
|
// to process it until the next frame. This is why we cpy the array before
|
|
// we iterate over it
|
|
for (REAOnAnimationCallback block in callbacks) {
|
|
block(displayLink);
|
|
}
|
|
|
|
[self performOperations];
|
|
|
|
if (_onAnimationCallbacks.count == 0) {
|
|
[self stopUpdatingOnAnimationFrame];
|
|
}
|
|
}
|
|
|
|
- (void)performOperations
|
|
{
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
_performOperations(); // calls NativeReanimatedModule::performOperations
|
|
_wantRunUpdates = NO;
|
|
#else
|
|
if (_operationsInBatch.count != 0) {
|
|
NSMutableArray<REANativeAnimationOp> *copiedOperationsQueue = _operationsInBatch;
|
|
_operationsInBatch = [NSMutableArray new];
|
|
|
|
BOOL trySynchronously = _tryRunBatchUpdatesSynchronously;
|
|
_tryRunBatchUpdatesSynchronously = NO;
|
|
|
|
__weak __typeof__(self) weakSelf = self;
|
|
REASyncUpdateObserver *syncUpdateObserver = [REASyncUpdateObserver new];
|
|
RCTExecuteOnUIManagerQueue(^{
|
|
__typeof__(self) strongSelf = weakSelf;
|
|
if (strongSelf == nil) {
|
|
return;
|
|
}
|
|
// It is safe to call `isExecutingUpdatesBatch` in this context (Shadow thread) because both
|
|
// the Shadow thread and UI Thread are locked at this point. The UI Thread is specifically
|
|
// locked by the lock inside `REASyncUpdateObserver`, ensuring a safe reading operation.
|
|
BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager isExecutingUpdatesBatch];
|
|
|
|
if (!canUpdateSynchronously) {
|
|
[syncUpdateObserver unblockUIThread];
|
|
}
|
|
|
|
for (int i = 0; i < copiedOperationsQueue.count; i++) {
|
|
copiedOperationsQueue[i](strongSelf.uiManager);
|
|
}
|
|
|
|
if (canUpdateSynchronously) {
|
|
[strongSelf.uiManager runSyncUIUpdatesWithObserver:syncUpdateObserver];
|
|
[syncUpdateObserver unblockUIThread];
|
|
}
|
|
// In case canUpdateSynchronously=true we still have to send uiManagerWillPerformMounting event
|
|
// to observers because some components (e.g. TextInput) update their UIViews only on that event.
|
|
[strongSelf.uiManager setNeedsLayout];
|
|
});
|
|
if (trySynchronously) {
|
|
// The 16ms timeout here aims to match the frame duration. It may make sense to read that parameter
|
|
// from CADisplayLink but it is easier to hardcode it for the time being.
|
|
// The reason why we use frame duration here is that if takes longer than one frame to complete layout tasks
|
|
// there is no point of synchronizing layout with the UI interaction as we get that one frame delay anyways.
|
|
[syncUpdateObserver waitAndMountWithTimeout:0.016];
|
|
}
|
|
}
|
|
_wantRunUpdates = NO;
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
// nothing
|
|
#else
|
|
- (void)enqueueUpdateViewOnNativeThread:(nonnull NSNumber *)reactTag
|
|
viewName:(NSString *)viewName
|
|
nativeProps:(NSMutableDictionary *)nativeProps
|
|
trySynchronously:(BOOL)trySync
|
|
{
|
|
if (trySync) {
|
|
_tryRunBatchUpdatesSynchronously = YES;
|
|
}
|
|
[_operationsInBatch addObject:^(RCTUIManager *uiManager) {
|
|
[uiManager updateView:reactTag viewName:viewName props:nativeProps];
|
|
}];
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
- (void)dispatchEvent:(id<RCTEvent>)event
|
|
{
|
|
__weak REAEventHandler eventHandler = _eventHandler;
|
|
__weak __typeof__(self) weakSelf = self;
|
|
RCTExecuteOnMainQueue(^void() {
|
|
__typeof__(self) strongSelf = weakSelf;
|
|
if (strongSelf == nil) {
|
|
return;
|
|
}
|
|
if (eventHandler == nil) {
|
|
return;
|
|
}
|
|
eventHandler(event);
|
|
[strongSelf performOperations];
|
|
});
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
// nothing
|
|
#else
|
|
- (void)configureUiProps:(nonnull NSSet<NSString *> *)uiPropsSet
|
|
andNativeProps:(nonnull NSSet<NSString *> *)nativePropsSet
|
|
{
|
|
_uiProps = uiPropsSet;
|
|
_nativeProps = nativePropsSet;
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
- (BOOL)isNativeViewMounted:(NSNumber *)viewTag
|
|
{
|
|
REAUIView *view = [_uiManager viewForReactTag:(NSNumber *)viewTag];
|
|
if (view.superview != nil) {
|
|
return YES;
|
|
}
|
|
#if __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
|
|
if ([view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
|
return ((RNSScreenStackHeaderConfig *)view).screenView != nil;
|
|
}
|
|
#endif // __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
|
|
return NO;
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
|
|
- (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(nonnull NSDictionary *)uiProps
|
|
{
|
|
// adapted from RCTPropsAnimatedNode.m
|
|
RCTSurfacePresenter *surfacePresenter = _bridge.surfacePresenter ?: _surfacePresenter;
|
|
RCTComponentViewRegistry *componentViewRegistry = surfacePresenter.mountingManager.componentViewRegistry;
|
|
REAUIView<RCTComponentViewProtocol> *componentView =
|
|
[componentViewRegistry findComponentViewWithTag:[viewTag integerValue]];
|
|
|
|
NSSet<NSString *> *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN];
|
|
[surfacePresenter synchronouslyUpdateViewOnUIThread:viewTag props:uiProps];
|
|
[componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated];
|
|
|
|
// `synchronouslyUpdateViewOnUIThread` does not flush props like `backgroundColor` etc.
|
|
// so that's why we need to call `finalizeUpdates` here.
|
|
[componentView finalizeUpdates:RNComponentViewUpdateMask{}];
|
|
}
|
|
|
|
#else
|
|
|
|
- (void)updateProps:(nonnull NSDictionary *)props
|
|
ofViewWithTag:(nonnull NSNumber *)viewTag
|
|
withName:(nonnull NSString *)viewName
|
|
{
|
|
ComponentUpdate *lastSnapshot = _componentUpdateBuffer[viewTag];
|
|
BOOL isNativeViewMounted = [self isNativeViewMounted:viewTag];
|
|
|
|
if (lastSnapshot != nil) {
|
|
NSMutableDictionary *lastProps = lastSnapshot.props;
|
|
for (NSString *key in props) {
|
|
[lastProps setValue:props[key] forKey:key];
|
|
}
|
|
}
|
|
|
|
// If the component isn't mounted, we will bail early with a scheduled update
|
|
if (!isNativeViewMounted) {
|
|
if (lastSnapshot == nil) {
|
|
ComponentUpdate *propsSnapshot = [ComponentUpdate new];
|
|
propsSnapshot.props = [props mutableCopy];
|
|
propsSnapshot.viewTag = viewTag;
|
|
propsSnapshot.viewName = viewName;
|
|
_componentUpdateBuffer[viewTag] = propsSnapshot;
|
|
atomic_store(&_shouldFlushUpdateBuffer, true);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// The component may have been mounted with a pending snapshot (due to a race condition),
|
|
// so we should attempt run the update. Otherwise, the next call to -maybeFlushUpdateBuffer
|
|
// will only arrive when a new component is mounted (which might be never!)
|
|
//
|
|
// If there are 0 remaining items in the buffer, we can skip the run in -maybeFlushUpdateBuffer.
|
|
if (lastSnapshot != nil && isNativeViewMounted) {
|
|
props = lastSnapshot.props;
|
|
viewTag = lastSnapshot.viewTag;
|
|
viewName = lastSnapshot.viewName;
|
|
|
|
[_componentUpdateBuffer removeObjectForKey:viewTag];
|
|
|
|
if (_componentUpdateBuffer.count == 0) {
|
|
atomic_store(&_shouldFlushUpdateBuffer, false);
|
|
}
|
|
}
|
|
|
|
// TODO: refactor PropsNode to also use this function
|
|
NSMutableDictionary *uiProps = [NSMutableDictionary new];
|
|
NSMutableDictionary *nativeProps = [NSMutableDictionary new];
|
|
NSMutableDictionary *jsProps = [NSMutableDictionary new];
|
|
|
|
void (^addBlock)(NSString *key, id obj, BOOL *stop) = ^(NSString *key, id obj, BOOL *stop) {
|
|
if ([self.uiProps containsObject:key]) {
|
|
uiProps[key] = obj;
|
|
} else if ([self.nativeProps containsObject:key]) {
|
|
nativeProps[key] = obj;
|
|
} else {
|
|
jsProps[key] = obj;
|
|
}
|
|
};
|
|
|
|
[props enumerateKeysAndObjectsUsingBlock:addBlock];
|
|
|
|
if (uiProps.count > 0) {
|
|
[self.uiManager synchronouslyUpdateViewOnUIThread:viewTag viewName:viewName props:uiProps];
|
|
}
|
|
if (nativeProps.count > 0) {
|
|
[self enqueueUpdateViewOnNativeThread:viewTag viewName:viewName nativeProps:nativeProps trySynchronously:YES];
|
|
}
|
|
if (jsProps.count > 0) {
|
|
[self.reanimatedModule sendEventWithName:@"onReanimatedPropsChange"
|
|
body:@{@"viewTag" : viewTag, @"props" : jsProps}];
|
|
}
|
|
}
|
|
|
|
- (NSString *)obtainProp:(nonnull NSNumber *)viewTag propName:(nonnull NSString *)propName
|
|
{
|
|
REAUIView *view = [self.uiManager viewForReactTag:viewTag];
|
|
|
|
if ([propName isEqualToString:@"opacity"]) {
|
|
#if !TARGET_OS_OSX
|
|
CGFloat alpha = view.alpha;
|
|
#else
|
|
CGFloat alpha = view.alphaValue;
|
|
#endif // TARGET_OS_OSX
|
|
return [@(alpha) stringValue];
|
|
} else if ([propName isEqualToString:@"zIndex"]) {
|
|
NSInteger zIndex = view.reactZIndex;
|
|
return [@(zIndex) stringValue];
|
|
} else if ([propName isEqualToString:@"width"]) {
|
|
return [@(view.frame.size.width) stringValue];
|
|
} else if ([propName isEqualToString:@"height"]) {
|
|
return [@(view.frame.size.height) stringValue];
|
|
} else if ([propName isEqualToString:@"top"]) {
|
|
return [@(view.frame.origin.y) stringValue];
|
|
} else if ([propName isEqualToString:@"left"]) {
|
|
return [@(view.frame.origin.x) stringValue];
|
|
} else if ([propName isEqualToString:@"backgroundColor"]) {
|
|
#if !TARGET_OS_OSX
|
|
UIColor *color = view.backgroundColor;
|
|
#else
|
|
NSColor *color = view.backgroundColor;
|
|
#endif
|
|
if (color == nil) {
|
|
return @"nil";
|
|
}
|
|
const size_t totalComponents = CGColorGetNumberOfComponents(color.CGColor);
|
|
const CGFloat *components = CGColorGetComponents(color.CGColor);
|
|
int r = 255 * components[MIN(0, totalComponents - 1)];
|
|
int g = 255 * components[MIN(1, totalComponents - 1)];
|
|
int b = 255 * components[MIN(2, totalComponents - 1)];
|
|
int alpha = 255 * components[MIN(3, totalComponents - 1)];
|
|
return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, alpha];
|
|
}
|
|
|
|
return [NSString
|
|
stringWithFormat:
|
|
@"error: unknown propName %@, currently supported: opacity, zIndex, width, height, top, left, backgroundColor",
|
|
propName];
|
|
}
|
|
|
|
- (void)maybeFlushUpdateBuffer
|
|
{
|
|
RCTAssertUIManagerQueue();
|
|
bool shouldFlushUpdateBuffer = atomic_load(&_shouldFlushUpdateBuffer);
|
|
if (!shouldFlushUpdateBuffer) {
|
|
return;
|
|
}
|
|
|
|
__weak __typeof__(self) weakSelf = self;
|
|
[_uiManager
|
|
addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
|
|
__typeof__(self) strongSelf = weakSelf;
|
|
if (strongSelf == nil) {
|
|
return;
|
|
}
|
|
atomic_store(&strongSelf->_shouldFlushUpdateBuffer, false);
|
|
NSMutableDictionary *componentUpdateBuffer = [strongSelf->_componentUpdateBuffer copy];
|
|
strongSelf->_componentUpdateBuffer = [NSMutableDictionary new];
|
|
for (NSNumber *tag in componentUpdateBuffer) {
|
|
ComponentUpdate *componentUpdate = componentUpdateBuffer[tag];
|
|
if (componentUpdate == Nil) {
|
|
continue;
|
|
}
|
|
[strongSelf updateProps:componentUpdate.props
|
|
ofViewWithTag:componentUpdate.viewTag
|
|
withName:componentUpdate.viewName];
|
|
}
|
|
[strongSelf performOperations];
|
|
}];
|
|
}
|
|
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
- (void)maybeFlushUIUpdatesQueue
|
|
{
|
|
if ([[self getDisplayLink] isPaused]) {
|
|
[self performOperations];
|
|
}
|
|
}
|
|
|
|
@end
|