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,324 @@
#include "Shareables.h"
using namespace facebook;
namespace reanimated {
jsi::Function getValueUnpacker(jsi::Runtime &rt) {
auto valueUnpacker = rt.global().getProperty(rt, "__valueUnpacker");
assert(valueUnpacker.isObject() && "valueUnpacker not found");
return valueUnpacker.asObject(rt).asFunction(rt);
}
#ifndef NDEBUG
static const auto callGuardLambda = [](facebook::jsi::Runtime &rt,
const facebook::jsi::Value &thisVal,
const facebook::jsi::Value *args,
size_t count) {
return args[0].asObject(rt).asFunction(rt).call(rt, args + 1, count - 1);
};
jsi::Function getCallGuard(jsi::Runtime &rt) {
auto callGuard = rt.global().getProperty(rt, "__callGuardDEV");
if (callGuard.isObject()) {
// Use JS implementation if `__callGuardDEV` has already been installed.
// This is the desired behavior.
return callGuard.asObject(rt).asFunction(rt);
}
// Otherwise, fallback to C++ JSI implementation. This is necessary so that we
// can install `__callGuardDEV` itself and should happen only once. Note that
// the C++ implementation doesn't intercept errors and simply throws them as
// C++ exceptions which crashes the app. We assume that installing the guard
// doesn't throw any errors.
return jsi::Function::createFromHostFunction(
rt, jsi::PropNameID::forAscii(rt, "callGuard"), 1, callGuardLambda);
}
#endif // NDEBUG
jsi::Value makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource) {
std::shared_ptr<Shareable> shareable;
if (value.isObject()) {
auto object = value.asObject(rt);
if (!object.getProperty(rt, "__workletHash").isUndefined()) {
shareable = std::make_shared<ShareableWorklet>(rt, object);
} else if (!object.getProperty(rt, "__init").isUndefined()) {
shareable = std::make_shared<ShareableHandle>(rt, object);
} else if (object.isFunction(rt)) {
auto function = object.asFunction(rt);
if (function.isHostFunction(rt)) {
shareable =
std::make_shared<ShareableHostFunction>(rt, std::move(function));
} else {
shareable =
std::make_shared<ShareableRemoteFunction>(rt, std::move(function));
}
} else if (object.isArray(rt)) {
if (shouldRetainRemote.isBool() && shouldRetainRemote.getBool()) {
shareable = std::make_shared<RetainingShareable<ShareableArray>>(
rt, object.asArray(rt));
} else {
shareable = std::make_shared<ShareableArray>(rt, object.asArray(rt));
}
} else if (object.isArrayBuffer(rt)) {
shareable =
std::make_shared<ShareableArrayBuffer>(rt, object.getArrayBuffer(rt));
} else if (object.isHostObject(rt)) {
if (object.isHostObject<ShareableJSRef>(rt)) {
return object;
}
shareable =
std::make_shared<ShareableHostObject>(rt, object.getHostObject(rt));
} else {
if (shouldRetainRemote.isBool() && shouldRetainRemote.getBool()) {
shareable = std::make_shared<RetainingShareable<ShareableObject>>(
rt, object, nativeStateSource);
} else {
shareable =
std::make_shared<ShareableObject>(rt, object, nativeStateSource);
}
}
} else if (value.isString()) {
shareable = std::make_shared<ShareableString>(value.asString(rt).utf8(rt));
} else if (value.isUndefined()) {
shareable = std::make_shared<ShareableScalar>();
} else if (value.isNull()) {
shareable = std::make_shared<ShareableScalar>(nullptr);
} else if (value.isBool()) {
shareable = std::make_shared<ShareableScalar>(value.getBool());
} else if (value.isNumber()) {
shareable = std::make_shared<ShareableScalar>(value.getNumber());
#if REACT_NATIVE_MINOR_VERSION >= 71
} else if (value.isBigInt()) {
shareable = std::make_shared<ShareableBigInt>(rt, value.getBigInt(rt));
#endif
} else if (value.isSymbol()) {
// TODO: this is only a placeholder implementation, here we replace symbols
// with strings in order to make certain objects to be captured. There isn't
// yet any usecase for using symbols on the UI runtime so it is fine to keep
// it like this for now.
shareable =
std::make_shared<ShareableString>(value.getSymbol(rt).toString(rt));
} else {
throw std::runtime_error(
"[Reanimated] Attempted to convert an unsupported value type.");
}
return ShareableJSRef::newHostObject(rt, shareable);
}
std::shared_ptr<Shareable> extractShareableOrThrow(
jsi::Runtime &rt,
const jsi::Value &maybeShareableValue,
const std::string &errorMessage) {
if (maybeShareableValue.isObject()) {
auto object = maybeShareableValue.asObject(rt);
if (object.isHostObject<ShareableJSRef>(rt)) {
return object.getHostObject<ShareableJSRef>(rt)->value();
}
throw std::runtime_error(
"[Reanimated] Attempted to extract from a HostObject that wasn't converted to a Shareable.");
} else if (maybeShareableValue.isUndefined()) {
return Shareable::undefined();
}
throw std::runtime_error(errorMessage);
}
Shareable::~Shareable() {}
std::shared_ptr<Shareable> Shareable::undefined() {
static auto undefined = std::make_shared<ShareableScalar>();
return undefined;
}
template <typename BaseClass>
jsi::Value RetainingShareable<BaseClass>::getJSValue(jsi::Runtime &rt) {
if (&rt == primaryRuntime_) {
// TODO: it is suboptimal to generate new object every time getJS is
// called on host runtime the objects we are generating already exists
// and we should possibly just grab a hold of such object and use it here
// instead of creating a new JS representation. As far as I understand the
// only case where it can be realistically called this way is when a
// shared value is created and then accessed on the same runtime
return BaseClass::toJSValue(rt);
}
if (secondaryValue_ == nullptr) {
auto value = BaseClass::toJSValue(rt);
secondaryValue_ = std::make_unique<jsi::Value>(rt, value);
secondaryRuntime_ = &rt;
return value;
}
if (&rt == secondaryRuntime_) {
return jsi::Value(rt, *secondaryValue_);
}
return BaseClass::toJSValue(rt);
}
ShareableJSRef::~ShareableJSRef() {}
ShareableArray::ShareableArray(jsi::Runtime &rt, const jsi::Array &array)
: Shareable(ArrayType) {
auto size = array.size(rt);
data_.reserve(size);
for (size_t i = 0; i < size; i++) {
data_.push_back(extractShareableOrThrow(rt, array.getValueAtIndex(rt, i)));
}
}
jsi::Value ShareableArray::toJSValue(jsi::Runtime &rt) {
auto size = data_.size();
auto ary = jsi::Array(rt, size);
for (size_t i = 0; i < size; i++) {
ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt));
}
return ary;
}
jsi::Value ShareableArrayBuffer::toJSValue(jsi::Runtime &rt) {
auto size = static_cast<int>(data_.size());
auto arrayBuffer = rt.global()
.getPropertyAsFunction(rt, "ArrayBuffer")
.callAsConstructor(rt, size)
.getObject(rt)
.getArrayBuffer(rt);
memcpy(arrayBuffer.data(rt), data_.data(), size);
return arrayBuffer;
}
ShareableObject::ShareableObject(jsi::Runtime &rt, const jsi::Object &object)
: Shareable(ObjectType) {
auto propertyNames = object.getPropertyNames(rt);
auto size = propertyNames.size(rt);
data_.reserve(size);
for (size_t i = 0; i < size; i++) {
auto key = propertyNames.getValueAtIndex(rt, i).asString(rt);
auto value = extractShareableOrThrow(rt, object.getProperty(rt, key));
data_.emplace_back(key.utf8(rt), value);
}
#if REACT_NATIVE_MINOR_VERSION >= 71
if (object.hasNativeState(rt)) {
nativeState_ = object.getNativeState(rt);
}
#endif
}
ShareableObject::ShareableObject(
jsi::Runtime &rt,
const jsi::Object &object,
const jsi::Value &nativeStateSource)
: ShareableObject(rt, object) {
#if REACT_NATIVE_MINOR_VERSION >= 71
if (nativeStateSource.isObject() &&
nativeStateSource.asObject(rt).hasNativeState(rt)) {
nativeState_ = nativeStateSource.asObject(rt).getNativeState(rt);
}
#endif
}
jsi::Value ShareableObject::toJSValue(jsi::Runtime &rt) {
auto obj = jsi::Object(rt);
for (size_t i = 0, size = data_.size(); i < size; i++) {
obj.setProperty(
rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt));
}
#if REACT_NATIVE_MINOR_VERSION >= 71
if (nativeState_ != nullptr) {
obj.setNativeState(rt, nativeState_);
}
#endif
return obj;
}
jsi::Value ShareableHostObject::toJSValue(jsi::Runtime &rt) {
return jsi::Object::createFromHostObject(rt, hostObject_);
}
jsi::Value ShareableHostFunction::toJSValue(jsi::Runtime &rt) {
return jsi::Function::createFromHostFunction(
rt, jsi::PropNameID::forUtf8(rt, name_), paramCount_, hostFunction_);
}
jsi::Value ShareableWorklet::toJSValue(jsi::Runtime &rt) {
assert(
std::any_of(
data_.cbegin(),
data_.cend(),
[](const auto &item) { return item.first == "__workletHash"; }) &&
"ShareableWorklet doesn't have `__workletHash` property");
jsi::Value obj = ShareableObject::toJSValue(rt);
return getValueUnpacker(rt).call(
rt, obj, jsi::String::createFromAscii(rt, "Worklet"));
}
jsi::Value ShareableRemoteFunction::toJSValue(jsi::Runtime &rt) {
if (&rt == runtime_) {
return jsi::Value(rt, *function_);
} else {
#ifndef NDEBUG
return getValueUnpacker(rt).call(
rt,
ShareableJSRef::newHostObject(rt, shared_from_this()),
jsi::String::createFromAscii(rt, "RemoteFunction"),
jsi::String::createFromUtf8(rt, name_));
#else
return ShareableJSRef::newHostObject(rt, shared_from_this());
#endif
}
}
jsi::Value ShareableHandle::toJSValue(jsi::Runtime &rt) {
if (remoteValue_ == nullptr) {
auto initObj = initializer_->getJSValue(rt);
auto value = std::make_unique<jsi::Value>(getValueUnpacker(rt).call(
rt, initObj, jsi::String::createFromAscii(rt, "Handle")));
// We are locking the initialization here since the thread that is
// initalizing can be pre-empted on runtime lock. E.g.
// UI thread can be pre-empted on initialization of a shared value and then
// JS thread can try to access the shared value, locking the whole runtime.
// If we put the lock on `getValueUnpacker` part (basically any part that
// requires runtime) we would get a deadlock since UI thread would never
// release it.
std::unique_lock<std::mutex> lock(initializationMutex_);
if (remoteValue_ == nullptr) {
remoteValue_ = std::move(value);
remoteRuntime_ = &rt;
}
}
return jsi::Value(rt, *remoteValue_);
}
jsi::Value ShareableString::toJSValue(jsi::Runtime &rt) {
return jsi::String::createFromUtf8(rt, data_);
}
#if REACT_NATIVE_MINOR_VERSION >= 71
jsi::Value ShareableBigInt::toJSValue(jsi::Runtime &rt) {
return rt.global()
.getPropertyAsFunction(rt, "BigInt")
.call(rt, jsi::String::createFromUtf8(rt, string_));
}
#endif
jsi::Value ShareableScalar::toJSValue(jsi::Runtime &) {
switch (valueType_) {
case Shareable::UndefinedType:
return jsi::Value();
case Shareable::NullType:
return jsi::Value(nullptr);
case Shareable::BooleanType:
return jsi::Value(data_.boolean);
case Shareable::NumberType:
return jsi::Value(data_.number);
default:
throw std::runtime_error(
"[Reanimated] Attempted to convert object that's not of a scalar type.");
}
}
} /* namespace reanimated */

View File

@@ -0,0 +1,361 @@
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "WorkletRuntimeRegistry.h"
using namespace facebook;
namespace reanimated {
jsi::Function getValueUnpacker(jsi::Runtime &rt);
#ifndef NDEBUG
jsi::Function getCallGuard(jsi::Runtime &rt);
#endif // NDEBUG
// If possible, please use `WorkletRuntime::runGuarded` instead.
template <typename... Args>
inline jsi::Value runOnRuntimeGuarded(
jsi::Runtime &rt,
const jsi::Value &function,
Args &&...args) {
// We only use callGuard in debug mode, otherwise we call the provided
// function directly. CallGuard provides a way of capturing exceptions in
// JavaScript and propagating them to the main React Native thread such that
// they can be presented using RN's LogBox.
#ifndef NDEBUG
return getCallGuard(rt).call(rt, function, args...);
#else
return function.asObject(rt).asFunction(rt).call(rt, args...);
#endif
}
inline void cleanupIfRuntimeExists(
jsi::Runtime *rt,
std::unique_ptr<jsi::Value> &value) {
if (rt != nullptr && !WorkletRuntimeRegistry::isRuntimeAlive(rt)) {
// The below use of unique_ptr.release prevents the smart pointer from
// calling the destructor of the kept object. This effectively results in
// leaking some memory. We do this on purpose, as sometimes we would keep
// references to JSI objects past the lifetime of its runtime (e.g.,
// shared values references from the RN VM holds reference to JSI objects
// on the UI runtime). When the UI runtime is terminated, the orphaned JSI
// objects would crash the app when their destructors are called, because
// they call into a memory that's managed by the terminated runtime. We
// accept the tradeoff of leaking memory here, as it has a limited impact.
// This scenario can only occur when the React instance is torn down which
// happens in development mode during app reloads, or in production when
// the app is being shut down gracefully by the system. An alternative
// solution would require us to keep track of all JSI values that are in
// use which would require additional data structure and compute spent on
// bookkeeping that only for the sake of destroying the values in time
// before the runtime is terminated. Note that the underlying memory that
// jsi::Value refers to is managed by the VM and gets freed along with the
// runtime.
value.release();
}
}
class Shareable {
protected:
virtual jsi::Value toJSValue(jsi::Runtime &rt) = 0;
public:
virtual ~Shareable();
enum ValueType {
UndefinedType,
NullType,
BooleanType,
NumberType,
// SymbolType, TODO
BigIntType,
StringType,
ObjectType,
ArrayType,
WorkletType,
RemoteFunctionType,
HandleType,
HostObjectType,
HostFunctionType,
ArrayBufferType,
};
explicit Shareable(ValueType valueType) : valueType_(valueType) {}
virtual jsi::Value getJSValue(jsi::Runtime &rt) {
return toJSValue(rt);
}
inline ValueType valueType() const {
return valueType_;
}
static std::shared_ptr<Shareable> undefined();
protected:
ValueType valueType_;
};
template <typename BaseClass>
class RetainingShareable : virtual public BaseClass {
private:
jsi::Runtime *primaryRuntime_;
jsi::Runtime *secondaryRuntime_;
std::unique_ptr<jsi::Value> secondaryValue_;
public:
template <typename... Args>
explicit RetainingShareable(jsi::Runtime &rt, Args &&...args)
: BaseClass(rt, std::forward<Args>(args)...), primaryRuntime_(&rt) {}
jsi::Value getJSValue(jsi::Runtime &rt);
~RetainingShareable() {
cleanupIfRuntimeExists(secondaryRuntime_, secondaryValue_);
}
};
class ShareableJSRef : public jsi::HostObject {
private:
const std::shared_ptr<Shareable> value_;
public:
explicit ShareableJSRef(const std::shared_ptr<Shareable> &value)
: value_(value) {}
virtual ~ShareableJSRef();
std::shared_ptr<Shareable> value() const {
return value_;
}
static jsi::Object newHostObject(
jsi::Runtime &rt,
const std::shared_ptr<Shareable> &value) {
return jsi::Object::createFromHostObject(
rt, std::make_shared<ShareableJSRef>(value));
}
};
jsi::Value makeShareableClone(
jsi::Runtime &rt,
const jsi::Value &value,
const jsi::Value &shouldRetainRemote,
const jsi::Value &nativeStateSource);
std::shared_ptr<Shareable> extractShareableOrThrow(
jsi::Runtime &rt,
const jsi::Value &maybeShareableValue,
const std::string &errorMessage =
"[Reanimated] Expecting the object to be of type ShareableJSRef.");
template <typename T>
std::shared_ptr<T> extractShareableOrThrow(
jsi::Runtime &rt,
const jsi::Value &shareableRef,
const std::string &errorMessage =
"[Reanimated] Provided shareable object is of an incompatible type.") {
auto res = std::dynamic_pointer_cast<T>(
extractShareableOrThrow(rt, shareableRef, errorMessage));
if (!res) {
throw std::runtime_error(errorMessage);
}
return res;
}
class ShareableArray : public Shareable {
public:
ShareableArray(jsi::Runtime &rt, const jsi::Array &array);
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
std::vector<std::shared_ptr<Shareable>> data_;
};
class ShareableObject : public Shareable {
public:
ShareableObject(jsi::Runtime &rt, const jsi::Object &object);
ShareableObject(
jsi::Runtime &rt,
const jsi::Object &object,
const jsi::Value &nativeStateSource);
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
std::vector<std::pair<std::string, std::shared_ptr<Shareable>>> data_;
#if REACT_NATIVE_MINOR_VERSION >= 71
std::shared_ptr<jsi::NativeState> nativeState_;
#endif
};
class ShareableHostObject : public Shareable {
public:
ShareableHostObject(
jsi::Runtime &,
const std::shared_ptr<jsi::HostObject> &hostObject)
: Shareable(HostObjectType), hostObject_(hostObject) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::shared_ptr<jsi::HostObject> hostObject_;
};
class ShareableHostFunction : public Shareable {
public:
ShareableHostFunction(jsi::Runtime &rt, jsi::Function function)
: Shareable(HostFunctionType),
hostFunction_(
(assert(function.isHostFunction(rt)),
function.getHostFunction(rt))),
name_(function.getProperty(rt, "name").asString(rt).utf8(rt)),
paramCount_(function.getProperty(rt, "length").asNumber()) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const jsi::HostFunctionType hostFunction_;
const std::string name_;
const unsigned int paramCount_;
};
class ShareableArrayBuffer : public Shareable {
public:
ShareableArrayBuffer(
jsi::Runtime &rt,
#if REACT_NATIVE_MINOR_VERSION >= 72
const jsi::ArrayBuffer &arrayBuffer
#else
jsi::ArrayBuffer arrayBuffer
#endif
)
: Shareable(ArrayBufferType),
data_(
arrayBuffer.data(rt),
arrayBuffer.data(rt) + arrayBuffer.size(rt)) {
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::vector<uint8_t> data_;
};
class ShareableWorklet : public ShareableObject {
public:
ShareableWorklet(jsi::Runtime &rt, const jsi::Object &worklet)
: ShareableObject(rt, worklet) {
valueType_ = WorkletType;
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
};
class ShareableRemoteFunction
: public Shareable,
public std::enable_shared_from_this<ShareableRemoteFunction> {
private:
jsi::Runtime *runtime_;
#ifndef NDEBUG
const std::string name_;
#endif
std::unique_ptr<jsi::Value> function_;
public:
ShareableRemoteFunction(jsi::Runtime &rt, jsi::Function &&function)
: Shareable(RemoteFunctionType),
runtime_(&rt),
#ifndef NDEBUG
name_(function.getProperty(rt, "name").asString(rt).utf8(rt)),
#endif
function_(std::make_unique<jsi::Value>(rt, std::move(function))) {
}
~ShareableRemoteFunction() {
cleanupIfRuntimeExists(runtime_, function_);
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
};
class ShareableHandle : public Shareable {
private:
// We don't release the initializer since the handle can get
// initialized in parallel on multiple threads. However this is not a problem,
// since the final value is taken from a cache on the runtime which guarantees
// sequential access.
std::unique_ptr<ShareableObject> initializer_;
std::unique_ptr<jsi::Value> remoteValue_;
mutable std::mutex initializationMutex_;
jsi::Runtime *remoteRuntime_;
public:
ShareableHandle(jsi::Runtime &rt, const jsi::Object &initializerObject)
: Shareable(HandleType),
initializer_(std::make_unique<ShareableObject>(rt, initializerObject)) {
}
~ShareableHandle() {
cleanupIfRuntimeExists(remoteRuntime_, remoteValue_);
}
jsi::Value toJSValue(jsi::Runtime &rt) override;
};
class ShareableString : public Shareable {
public:
explicit ShareableString(const std::string &string)
: Shareable(StringType), data_(string) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::string data_;
};
#if REACT_NATIVE_MINOR_VERSION >= 71
class ShareableBigInt : public Shareable {
public:
explicit ShareableBigInt(jsi::Runtime &rt, const jsi::BigInt &bigint)
: Shareable(BigIntType), string_(bigint.toString(rt).utf8(rt)) {}
jsi::Value toJSValue(jsi::Runtime &rt) override;
protected:
const std::string string_;
};
#endif
class ShareableScalar : public Shareable {
public:
explicit ShareableScalar(double number) : Shareable(NumberType) {
data_.number = number;
}
explicit ShareableScalar(bool boolean) : Shareable(BooleanType) {
data_.boolean = boolean;
}
ShareableScalar() : Shareable(UndefinedType) {}
explicit ShareableScalar(std::nullptr_t) : Shareable(NullType) {}
jsi::Value toJSValue(jsi::Runtime &);
protected:
union Data {
bool boolean;
double number;
};
private:
Data data_;
};
} // namespace reanimated