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,133 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <algorithm>
#include <random>
namespace facebook::react {
/*
* The source of pseudo-random numbers and some problem-oriented tools built on
* top of that. We need this class to maintain a reproducible stream of random
* numbers and abstract away complex math of and C++ STL API behind that.
*/
class Entropy final {
public:
using Generator = std::mt19937;
/*
* Creates an instance seeded with a real, not pseudo-random, number.
*/
Entropy() {
std::random_device device;
seed_ = device();
generator_ = std::mt19937(seed_);
}
/*
* Creates an instance seeded with a given number.
*/
Entropy(uint_fast32_t seed) {
seed_ = seed;
generator_ = std::mt19937(seed_);
}
uint_fast32_t getSeed() const {
return seed_;
}
/*
* Family of methods that return uniformly distributed instances of a type
* within a specified range.
*/
template <typename T>
bool random() const {
T result;
generateRandomValue(generator_, result);
return result;
}
template <typename T, typename Arg1>
T random(Arg1 arg1) const {
T result;
generateRandomValue(generator_, result, arg1);
return result;
}
template <typename T, typename Arg1, typename Arg2>
T random(Arg1 arg1, Arg2 arg2) const {
T result;
generateRandomValue(generator_, result, arg1, arg2);
return result;
}
void generateRandomValue(
Generator& generator,
bool& result,
double ratio = 0.5) const {
result = generator() % 10000 < 10000 * ratio;
}
void generateRandomValue(Generator& generator, int& result) const {
result = generator();
}
void generateRandomValue(Generator& generator, int& result, int min, int max)
const {
std::uniform_int_distribution<int> distribution(min, max);
result = distribution(generator);
}
/*
* Shuffles `std::vector` in place.
*/
template <typename T>
void shuffle(T array) const {
std::shuffle(array.begin(), array.end(), generator_);
}
/*
* Distribute items from a given array into buckets using a normal
* distribution and given `deviation`.
*/
template <typename T>
std::vector<std::vector<T>> distribute(std::vector<T> items, double deviation)
const {
std::normal_distribution<> distribution{0, deviation};
auto deviationLimit = int(deviation * 10);
auto spreadResult = std::vector<std::vector<T>>(deviationLimit * 2);
std::fill(spreadResult.begin(), spreadResult.end(), std::vector<T>{});
for (const auto& item : items) {
auto position = int(distribution(generator_) + deviationLimit);
position = std::max(0, std::min(position, deviationLimit * 2));
if (position < spreadResult.size()) {
spreadResult[position].push_back(item);
}
}
auto result = std::vector<std::vector<T>>{};
for (const auto& chunk : spreadResult) {
if (chunk.size() == 0) {
continue;
}
result.push_back(chunk);
}
return result;
}
private:
mutable std::mt19937 generator_;
uint_fast32_t seed_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <chrono>
class MockClock {
public:
typedef std::chrono::
time_point<std::chrono::steady_clock, std::chrono::nanoseconds>
time_point;
static time_point now() noexcept {
return time_;
}
template <typename TDuration>
static void advance_by(const TDuration duration) {
time_ += duration;
}
private:
static time_point time_;
};

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <gmock/gmock.h>
#include <react/renderer/scheduler/SurfaceHandler.h>
namespace facebook::react {
class MockSurfaceHandler : public SurfaceHandler {
public:
MockSurfaceHandler() : SurfaceHandler("moduleName", 0){};
MOCK_METHOD(void, setDisplayMode, (DisplayMode), (const, noexcept));
MOCK_METHOD(SurfaceId, getSurfaceId, (), (const, noexcept));
};
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
RCT_EXTERN_C_BEGIN
int RCTGetRetainCount(id _Nullable object);
void RCTAutoReleasePoolPush(void);
void RCTAutoReleasePoolPop(void);
RCT_EXTERN_C_END

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTMemoryUtils.h"
int RCTGetRetainCount(id _Nullable object)
{
return object != nil ? CFGetRetainCount((__bridge CFTypeRef)object) - 1 : 0;
}
OBJC_EXPORT
void *objc_autoreleasePoolPush(void) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void objc_autoreleasePoolPop(void *context) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
static NSString *const kAutoreleasePoolContextStackKey = @"autorelease_pool_context_stack";
void RCTAutoReleasePoolPush(void)
{
assert([NSThread isMainThread]);
NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary];
void *context = objc_autoreleasePoolPush();
NSMutableArray<NSValue *> *contextStack = dictionary[kAutoreleasePoolContextStackKey];
if (!contextStack) {
contextStack = [NSMutableArray array];
dictionary[kAutoreleasePoolContextStackKey] = contextStack;
}
[contextStack addObject:[NSValue valueWithPointer:context]];
}
void RCTAutoReleasePoolPop(void)
{
assert([NSThread isMainThread]);
NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary];
NSMutableArray<NSValue *> *contextStack = dictionary[kAutoreleasePoolContextStackKey];
assert(contextStack.count > 0);
NSValue *lastContext = contextStack.lastObject;
[contextStack removeLastObject];
objc_autoreleasePoolPop(lastContext.pointerValue);
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
RCT_EXTERN_C_BEGIN
void RCTSwizzleInstanceSelector(
Class targetClass,
Class swizzleClass,
SEL selector);
RCT_EXTERN_C_END

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSwizzleHelpers.h"
#import <objc/runtime.h>
void RCTSwizzleInstanceSelector(Class targetClass, Class swizzleClass, SEL selector)
{
Method originalMethod = class_getInstanceMethod(targetClass, selector);
Method swizzleMethod = class_getInstanceMethod(swizzleClass, selector);
method_exchangeImplementations(originalMethod, swizzleMethod);
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface ShimRCTInstance : NSObject
@property int initCount;
@property int invalidateCount;
@property NSDictionary *launchOptions;
@property NSString *jsModuleName;
@property NSString *method;
@property NSArray *args;
- (void)reset;
@end

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "ShimRCTInstance.h"
#import <ReactCommon/RCTInstance.h>
#import "RCTSwizzleHelpers.h"
static __weak ShimRCTInstance *weakShim = nil;
@implementation ShimRCTInstance
- (instancetype)init
{
if (self = [super init]) {
_initCount = 0;
RCTSwizzleInstanceSelector(
[RCTInstance class],
[ShimRCTInstance class],
@selector(initWithDelegate:
jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:onInitialBundleLoad:moduleRegistry
:parentInspectorTarget:launchOptions:));
RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate));
RCTSwizzleInstanceSelector(
[RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:));
weakShim = self;
}
return self;
}
- (void)reset
{
RCTSwizzleInstanceSelector(
[RCTInstance class],
[ShimRCTInstance class],
@selector(initWithDelegate:
jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:onInitialBundleLoad:moduleRegistry
:parentInspectorTarget:launchOptions:));
RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate));
RCTSwizzleInstanceSelector(
[RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:));
_initCount = 0;
_invalidateCount = 0;
}
- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
jsRuntimeFactory:(std::shared_ptr<facebook::react::JSRuntimeFactory>)jsRuntimeFactory
bundleManager:(RCTBundleManager *)bundleManager
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)tmmDelegate
onInitialBundleLoad:(RCTInstanceInitialBundleLoadCompletionBlock)onInitialBundleLoad
moduleRegistry:(RCTModuleRegistry *)moduleRegistry
parentInspectorTarget:(facebook::react::jsinspector_modern::PageTarget *)parentInspectorTarget
launchOptions:(NSDictionary *)launchOptions
{
weakShim.initCount++;
return self;
}
- (void)invalidate
{
weakShim.invalidateCount++;
}
- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args
{
weakShim.jsModuleName = moduleName;
weakShim.method = method;
weakShim.args = [args copy];
}
@end

View File

@@ -0,0 +1,323 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include <random>
#include <react/config/ReactNativeConfig.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/stubs.h>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include "Entropy.h"
namespace facebook::react {
static Tag generateReactTag() {
static Tag tag = 1000;
return tag++;
}
class ShadowTreeEdge final {
public:
ShadowNode::Shared shadowNode{nullptr};
ShadowNode::Shared parentShadowNode{nullptr};
int index{0};
};
static bool traverseShadowTree(
const ShadowNode::Shared& parentShadowNode,
const std::function<void(ShadowTreeEdge const& edge, bool& stop)>&
callback) {
auto index = int{0};
for (const auto& childNode : parentShadowNode->getChildren()) {
auto stop = bool{false};
callback(ShadowTreeEdge{childNode, parentShadowNode, index}, stop);
if (stop) {
return true;
}
if (traverseShadowTree(childNode, callback)) {
return true;
}
index++;
}
return false;
}
static int countShadowNodes(const ShadowNode::Shared& rootShadowNode) {
auto counter = int{0};
traverseShadowTree(
rootShadowNode,
[&](const ShadowTreeEdge& edge, bool& stop) { counter++; });
return counter;
}
static ShadowTreeEdge findShadowNodeWithIndex(
const ShadowNode::Shared& rootNode,
int index) {
auto counter = int{0};
auto result = ShadowTreeEdge{};
traverseShadowTree(rootNode, [&](const ShadowTreeEdge& edge, bool& stop) {
if (index == counter) {
result = edge;
}
counter++;
});
return result;
}
static ShadowTreeEdge findRandomShadowNode(
const Entropy& entropy,
const ShadowNode::Shared& rootShadowNode) {
auto count = countShadowNodes(rootShadowNode);
return findShadowNodeWithIndex(
rootShadowNode,
entropy.random<int>(1 /* Excluding a root node */, count - 1));
}
static ShadowNode::ListOfShared cloneSharedShadowNodeList(
const ShadowNode::ListOfShared& list) {
auto result = ShadowNode::ListOfShared{};
result.reserve(list.size());
for (const auto& shadowNode : list) {
result.push_back(shadowNode->clone({}));
}
return result;
}
static inline ShadowNode::Unshared messWithChildren(
const Entropy& entropy,
const ShadowNode& shadowNode) {
auto children = shadowNode.getChildren();
children = cloneSharedShadowNodeList(children);
entropy.shuffle(children);
return shadowNode.clone(
{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<ShadowNode::ListOfShared const>(children)});
}
static inline ShadowNode::Unshared messWithLayoutableOnlyFlag(
const Entropy& entropy,
const ShadowNode& shadowNode) {
auto oldProps = shadowNode.getProps();
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
parserContext, oldProps, RawProps(folly::dynamic::object()));
auto& viewProps =
const_cast<ViewProps&>(static_cast<const ViewProps&>(*newProps));
if (entropy.random<bool>(0.1)) {
viewProps.nativeId = entropy.random<bool>() ? "42" : "";
}
if (entropy.random<bool>(0.1)) {
viewProps.backgroundColor =
entropy.random<bool>() ? SharedColor() : whiteColor();
}
if (entropy.random<bool>(0.1)) {
viewProps.shadowColor =
entropy.random<bool>() ? SharedColor() : blackColor();
}
if (entropy.random<bool>(0.1)) {
viewProps.accessible = entropy.random<bool>();
}
if (entropy.random<bool>(0.1)) {
viewProps.zIndex = entropy.random<int>();
}
if (entropy.random<bool>(0.1)) {
viewProps.pointerEvents = entropy.random<bool>() ? PointerEventsMode::Auto
: PointerEventsMode::None;
}
if (entropy.random<bool>(0.1)) {
viewProps.transform = entropy.random<bool>() ? Transform::Identity()
: Transform::Perspective(42);
}
#ifdef ANDROID
if (entropy.random<bool>(0.1)) {
viewProps.elevation = entropy.random<bool>() ? 1 : 0;
}
#endif
return shadowNode.clone({newProps});
}
// Similar to `messWithLayoutableOnlyFlag` but has a 50/50 chance of flattening
// (or unflattening) a node's children.
static inline ShadowNode::Unshared messWithNodeFlattenednessFlags(
const Entropy& entropy,
const ShadowNode& shadowNode) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
auto oldProps = shadowNode.getProps();
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
parserContext, oldProps, RawProps(folly::dynamic::object()));
auto& viewProps =
const_cast<ViewProps&>(static_cast<const ViewProps&>(*newProps));
if (entropy.random<bool>(0.5)) {
viewProps.nativeId = "";
viewProps.collapsable = true;
viewProps.backgroundColor = SharedColor();
viewProps.shadowColor = SharedColor();
viewProps.accessible = false;
viewProps.zIndex = {};
viewProps.pointerEvents = PointerEventsMode::Auto;
viewProps.transform = Transform::Identity();
#ifdef ANDROID
viewProps.elevation = 0;
#endif
} else {
viewProps.nativeId = "42";
viewProps.backgroundColor = whiteColor();
viewProps.shadowColor = blackColor();
viewProps.accessible = true;
viewProps.zIndex = {entropy.random<int>()};
viewProps.pointerEvents = PointerEventsMode::None;
viewProps.transform = Transform::Perspective(entropy.random<int>());
#ifdef ANDROID
viewProps.elevation = entropy.random<int>();
#endif
}
return shadowNode.clone({newProps});
}
static inline ShadowNode::Unshared messWithYogaStyles(
const Entropy& entropy,
const ShadowNode& shadowNode) {
folly::dynamic dynamic = folly::dynamic::object();
if (entropy.random<bool>()) {
dynamic["flexDirection"] = entropy.random<bool>() ? "row" : "column";
}
std::vector<std::string> properties = {
"flex", "flexGrow", "flexShrink", "flexBasis",
"left", "top", "marginLeft", "marginTop",
"marginRight", "marginBottom", "paddingLeft", "paddingTop",
"paddingRight", "paddingBottom", "width", "height",
"maxWidth", "maxHeight", "minWidth", "minHeight",
};
// It is not safe to add new Yoga properties to this list. Unit tests
// validate specific seeds, and what they test may change and cause unrelated
// failures if the size of properties also changes.
EXPECT_EQ(properties.size(), 20);
for (const auto& property : properties) {
if (entropy.random<bool>(0.1)) {
dynamic[property] = entropy.random<int>(0, 1024);
}
}
ContextContainer contextContainer;
contextContainer.insert(
"ReactNativeConfig", std::make_shared<EmptyReactNativeConfig>());
PropsParserContext parserContext{-1, contextContainer};
auto oldProps = shadowNode.getProps();
auto newProps = shadowNode.getComponentDescriptor().cloneProps(
parserContext, oldProps, RawProps(dynamic));
return shadowNode.clone({newProps});
}
using ShadowNodeAlteration = std::function<
ShadowNode::Unshared(const Entropy& entropy, const ShadowNode& shadowNode)>;
static inline void alterShadowTree(
const Entropy& entropy,
RootShadowNode::Shared& rootShadowNode,
ShadowNodeAlteration alteration) {
auto edge = findRandomShadowNode(entropy, rootShadowNode);
rootShadowNode =
std::static_pointer_cast<RootShadowNode>(rootShadowNode->cloneTree(
edge.shadowNode->getFamily(), [&](const ShadowNode& oldShadowNode) {
return alteration(entropy, oldShadowNode);
}));
}
static inline void alterShadowTree(
const Entropy& entropy,
RootShadowNode::Shared& rootShadowNode,
std::vector<ShadowNodeAlteration> alterations) {
auto i = entropy.random<int>(0, alterations.size() - 1);
alterShadowTree(entropy, rootShadowNode, alterations[i]);
}
static SharedViewProps generateDefaultProps(
const ComponentDescriptor& componentDescriptor) {
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
return std::static_pointer_cast<const ViewProps>(
componentDescriptor.cloneProps(parserContext, nullptr, RawProps{}));
}
static inline ShadowNode::Shared generateShadowNodeTree(
const Entropy& entropy,
const ComponentDescriptor& componentDescriptor,
int size,
int deviation = 3) {
if (size <= 1) {
auto family = componentDescriptor.createFamily(
{generateReactTag(), SurfaceId(1), nullptr});
return componentDescriptor.createShadowNode(
ShadowNodeFragment{generateDefaultProps(componentDescriptor)}, family);
}
auto items = std::vector<int>(size);
std::fill(items.begin(), items.end(), 1);
auto chunks = entropy.distribute(items, deviation);
auto children = ShadowNode::ListOfShared{};
for (const auto& chunk : chunks) {
children.push_back(
generateShadowNodeTree(entropy, componentDescriptor, chunk.size()));
}
auto family = componentDescriptor.createFamily(
{generateReactTag(), SurfaceId(1), nullptr});
return componentDescriptor.createShadowNode(
ShadowNodeFragment{
generateDefaultProps(componentDescriptor),
std::make_shared<ShadowNode::ListOfShared>(children)},
family);
}
} // namespace facebook::react