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
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,29 @@
#if !TARGET_OS_TV && !TARGET_OS_OSX
#import <CoreMotion/CoreMotion.h>
#endif
#import <RNReanimated/ReanimatedSensorType.h>
@interface ReanimatedSensor : NSObject {
ReanimatedSensorType _sensorType;
double _interval;
double _lastTimestamp;
int _referenceFrame;
#if !TARGET_OS_TV && !TARGET_OS_OSX
CMMotionManager *_motionManager;
#endif
void (^_setter)(double[], int);
}
- (instancetype)init:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter;
- (bool)initialize;
- (bool)initializeGyroscope;
- (bool)initializeAccelerometer;
- (bool)initializeGravity;
- (bool)initializeMagnetometer;
- (bool)initializeOrientation;
- (void)cancel;
@end

View File

@@ -0,0 +1,265 @@
#import <RNReanimated/ReanimatedSensor.h>
#if !TARGET_OS_TV && !TARGET_OS_OSX && !TARGET_OS_VISION
@implementation ReanimatedSensor
- (instancetype)init:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter
{
self = [super init];
_sensorType = sensorType;
if (interval == -1) {
_interval = 1.0 / UIScreen.mainScreen.maximumFramesPerSecond;
} else {
_interval = interval / 1000.0; // in seconds
}
_referenceFrame = iosReferenceFrame;
_setter = setter;
_motionManager = [[CMMotionManager alloc] init];
return self;
}
- (bool)initialize
{
if (_sensorType == ACCELEROMETER) {
return [self initializeAccelerometer];
} else if (_sensorType == GYROSCOPE) {
return [self initializeGyroscope];
} else if (_sensorType == GRAVITY) {
return [self initializeGravity];
} else if (_sensorType == MAGNETIC_FIELD) {
return [self initializeMagnetometer];
} else if (_sensorType == ROTATION_VECTOR) {
return [self initializeOrientation];
}
return false;
}
- (bool)initializeGyroscope
{
if (![_motionManager isGyroAvailable]) {
return false;
}
[_motionManager setGyroUpdateInterval:_interval];
[_motionManager startGyroUpdates];
[_motionManager
startGyroUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMGyroData *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double data[] = {sensorData.rotationRate.x, sensorData.rotationRate.y, sensorData.rotationRate.z};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeAccelerometer
{
if (![_motionManager isAccelerometerAvailable]) {
return false;
}
[_motionManager setAccelerometerUpdateInterval:_interval];
[_motionManager startAccelerometerUpdates];
[_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMAccelerometerData *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double G = 9.81;
// convert G to m/s^2
double data[] = {
sensorData.acceleration.x * G,
sensorData.acceleration.y * G,
sensorData.acceleration.z * G};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeGravity
{
if (![_motionManager isDeviceMotionAvailable]) {
return false;
}
[_motionManager setDeviceMotionUpdateInterval:_interval];
[_motionManager setShowsDeviceMovementDisplay:YES];
[_motionManager
startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double G = 9.81;
// convert G to m/s^2
double data[] = {
sensorData.gravity.x * G, sensorData.gravity.y * G, sensorData.gravity.z * G};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeMagnetometer
{
if (![_motionManager isMagnetometerAvailable]) {
return false;
}
[_motionManager setMagnetometerUpdateInterval:_interval];
[_motionManager startMagnetometerUpdates];
[_motionManager
startMagnetometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMMagnetometerData *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double data[] = {
sensorData.magneticField.x, sensorData.magneticField.y, sensorData.magneticField.z};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeOrientation
{
if (![_motionManager isDeviceMotionAvailable]) {
return false;
}
[_motionManager setDeviceMotionUpdateInterval:_interval];
[_motionManager setShowsDeviceMovementDisplay:YES];
// _referenceFrame = Auto, on devices without magnetometer fall back to `XArbitraryZVertical`,
// `XArbitraryCorrectedZVertical` otherwise
if (_referenceFrame == 4) {
if (![_motionManager isMagnetometerAvailable]) {
_referenceFrame = 0;
} else {
_referenceFrame = 1;
}
}
// the binary shift works here because of the definition of CMAttitudeReferenceFrame
[_motionManager startDeviceMotionUpdatesUsingReferenceFrame:(1 << _referenceFrame)
toQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
CMAttitude *attitude = sensorData.attitude;
double data[] = {
attitude.quaternion.x,
attitude.quaternion.y,
attitude.quaternion.z,
attitude.quaternion.w,
attitude.yaw,
attitude.pitch,
attitude.roll};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (void)cancel
{
if (_sensorType == ACCELEROMETER) {
[_motionManager stopAccelerometerUpdates];
} else if (_sensorType == GYROSCOPE) {
[_motionManager stopGyroUpdates];
} else if (_sensorType == GRAVITY) {
[_motionManager stopDeviceMotionUpdates];
} else if (_sensorType == MAGNETIC_FIELD) {
[_motionManager stopMagnetometerUpdates];
} else if (_sensorType == ROTATION_VECTOR) {
[_motionManager stopDeviceMotionUpdates];
}
}
- (int)getInterfaceOrientation
{
UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
if (@available(iOS 13.0, *)) {
orientation = UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation;
} else {
orientation = UIApplication.sharedApplication.statusBarOrientation;
}
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
return 270;
case UIInterfaceOrientationLandscapeRight:
return 90;
case UIInterfaceOrientationPortraitUpsideDown:
return 180;
default:
return 0;
}
}
@end
#else
@implementation ReanimatedSensor
- (instancetype)init:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter
{
self = [super init];
return self;
}
- (bool)initialize
{
return false;
}
- (bool)initializeGyroscope
{
return false;
}
- (bool)initializeAccelerometer
{
return false;
}
- (bool)initializeGravity
{
return false;
}
- (bool)initializeMagnetometer
{
return false;
}
- (bool)initializeOrientation
{
return false;
}
- (void)cancel
{
}
@end
#endif

View File

@@ -0,0 +1,15 @@
#import <RNReanimated/ReanimatedSensorType.h>
@interface ReanimatedSensorContainer : NSObject {
NSNumber *_nextSensorId;
NSMutableDictionary *_sensors;
}
- (instancetype)init;
- (int)registerSensor:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter;
- (void)unregisterSensor:(int)sensorId;
@end

View File

@@ -0,0 +1,46 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/ReanimatedSensor.h>
#import <RNReanimated/ReanimatedSensorContainer.h>
static NSNumber *_nextSensorId = nil;
@implementation ReanimatedSensorContainer
- (instancetype)init
{
self = [super init];
_sensors = [[NSMutableDictionary alloc] init];
_nextSensorId = @0;
return self;
}
- (int)registerSensor:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter
{
ReanimatedSensor *sensor = [[ReanimatedSensor alloc] init:sensorType
interval:interval
iosReferenceFrame:iosReferenceFrame
setter:setter];
if ([sensor initialize]) {
NSNumber *sensorId = [_nextSensorId copy];
_nextSensorId = [NSNumber numberWithInt:[_nextSensorId intValue] + 1];
[_sensors setObject:sensor forKey:sensorId];
return [sensorId intValue];
} else {
return -1;
}
}
- (void)unregisterSensor:(int)sensorId
{
NSNumber *_sensorId = [NSNumber numberWithInt:sensorId];
if (_sensors[_sensorId] == nil) {
return;
}
[_sensors[_sensorId] cancel];
[_sensors removeObjectForKey:_sensorId];
}
@end

View File

@@ -0,0 +1,7 @@
typedef NS_ENUM(NSUInteger, ReanimatedSensorType) {
ACCELEROMETER = 1,
GYROSCOPE = 2,
GRAVITY = 3,
MAGNETIC_FIELD = 4,
ROTATION_VECTOR = 5,
};