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,28 @@
import type { ExtraDependencies, ModuleDescriptorAndroid, PackageRevision } from '../types';
/**
* Generates Java file that contains all autolinked packages.
*/
export declare function generatePackageListAsync(modules: ModuleDescriptorAndroid[], targetPath: string, namespace: string): Promise<void>;
export declare function resolveModuleAsync(packageName: string, revision: PackageRevision): Promise<ModuleDescriptorAndroid | null>;
export declare function resolveExtraBuildDependenciesAsync(projectNativeRoot: string): Promise<ExtraDependencies | null>;
/**
* Converts the package name and gradle file path to Android's project name.
* `$` to indicate subprojects
* `/` path will transform as `-`
*
* Example: `@expo/example` + `android/build.gradle` → `expo-example`
*
* Example: multiple projects
* - `expo-test` + `android/build.gradle` → `react-native-third-party`
* - `expo-test` + `subproject/build.gradle` → `react-native-third-party$subproject`
*/
export declare function convertPackageNameToProjectName(packageName: string, buildGradleFile: string): string;
/**
* Given the contents of a `gradle.properties` file,
* searches for a property with the given name.
*
* This function will return the first property found with the given name.
* The implementation follows config-plugins and
* tries to align the behavior with the `withGradleProperties` plugin.
*/
export declare function searchGradlePropertyFirst(contents: string, propertyName: string): string | null;

View File

@@ -0,0 +1,194 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchGradlePropertyFirst = exports.convertPackageNameToProjectName = exports.resolveExtraBuildDependenciesAsync = exports.resolveModuleAsync = exports.generatePackageListAsync = void 0;
const fast_glob_1 = __importDefault(require("fast-glob"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const ANDROID_PROPERTIES_FILE = 'gradle.properties';
const ANDROID_EXTRA_BUILD_DEPS_KEY = 'android.extraMavenRepos';
/**
* Generates Java file that contains all autolinked packages.
*/
async function generatePackageListAsync(modules, targetPath, namespace) {
const generatedFileContent = await generatePackageListFileContentAsync(modules, namespace);
await fs_extra_1.default.outputFile(targetPath, generatedFileContent);
}
exports.generatePackageListAsync = generatePackageListAsync;
async function findGradleFilesAsync(revision) {
const configGradlePaths = revision.config?.androidGradlePaths();
if (configGradlePaths && configGradlePaths.length) {
return configGradlePaths;
}
const buildGradleFiles = await (0, fast_glob_1.default)('*/build.gradle', {
cwd: revision.path,
ignore: ['**/node_modules/**'],
});
return buildGradleFiles;
}
async function resolveModuleAsync(packageName, revision) {
// TODO: Relative source dir should be configurable through the module config.
// Don't link itself... :D
if (packageName === '@unimodules/react-native-adapter') {
return null;
}
const buildGradleFiles = await findGradleFilesAsync(revision);
// Just in case where the module doesn't have its own `build.gradle`.
if (!buildGradleFiles.length) {
return null;
}
const projects = buildGradleFiles.map((buildGradleFile) => {
const gradleFilePath = path_1.default.join(revision.path, buildGradleFile);
return {
name: convertPackageNameToProjectName(packageName, path_1.default.relative(revision.path, gradleFilePath)),
sourceDir: path_1.default.dirname(gradleFilePath),
};
});
const plugins = (revision.config?.androidGradlePlugins() ?? []).map(({ id, group, sourceDir }) => ({
id,
group,
sourceDir: path_1.default.join(revision.path, sourceDir),
}));
return {
packageName,
projects,
...(plugins.length > 0 ? { plugins } : {}),
modules: revision.config?.androidModules() ?? [],
};
}
exports.resolveModuleAsync = resolveModuleAsync;
async function resolveExtraBuildDependenciesAsync(projectNativeRoot) {
const propsFile = path_1.default.join(projectNativeRoot, ANDROID_PROPERTIES_FILE);
try {
const contents = await fs_extra_1.default.readFile(propsFile, 'utf8');
const extraMavenReposString = searchGradlePropertyFirst(contents, ANDROID_EXTRA_BUILD_DEPS_KEY);
if (extraMavenReposString) {
const extraMavenRepos = JSON.parse(extraMavenReposString);
return extraMavenRepos;
}
}
catch { }
return null;
}
exports.resolveExtraBuildDependenciesAsync = resolveExtraBuildDependenciesAsync;
/**
* Generates the string to put into the generated package list.
*/
async function generatePackageListFileContentAsync(modules, namespace) {
// TODO: Instead of ignoring `expo` here, make the package class paths configurable from `expo-module.config.json`.
const packagesClasses = await findAndroidPackagesAsync(modules.filter((module) => module.packageName !== 'expo'));
const modulesClasses = await findAndroidModules(modules);
return `package ${namespace};
import java.util.Arrays;
import java.util.List;
import expo.modules.core.interfaces.Package;
import expo.modules.kotlin.modules.Module;
import expo.modules.kotlin.ModulesProvider;
public class ExpoModulesPackageList implements ModulesProvider {
private static class LazyHolder {
static final List<Package> packagesList = Arrays.<Package>asList(
${packagesClasses.map((packageClass) => ` new ${packageClass}()`).join(',\n')}
);
static final List<Class<? extends Module>> modulesList = Arrays.<Class<? extends Module>>asList(
${modulesClasses.map((moduleClass) => ` ${moduleClass}.class`).join(',\n')}
);
}
public static List<Package> getPackageList() {
return LazyHolder.packagesList;
}
@Override
public List<Class<? extends Module>> getModulesList() {
return LazyHolder.modulesList;
}
}
`;
}
function findAndroidModules(modules) {
const modulesToProvide = modules.filter((module) => module.modules.length > 0);
const classNames = [].concat(...modulesToProvide.map((module) => module.modules));
return classNames;
}
async function findAndroidPackagesAsync(modules) {
const classes = [];
const flattenedSourceDirList = [];
for (const module of modules) {
for (const project of module.projects) {
flattenedSourceDirList.push(project.sourceDir);
}
}
await Promise.all(flattenedSourceDirList.map(async (sourceDir) => {
const files = await (0, fast_glob_1.default)('**/*Package.{java,kt}', {
cwd: sourceDir,
});
for (const file of files) {
const fileContent = await fs_extra_1.default.readFile(path_1.default.join(sourceDir, file), 'utf8');
const packageRegex = (() => {
if (process.env.EXPO_SHOULD_USE_LEGACY_PACKAGE_INTERFACE) {
return /\bimport\s+org\.unimodules\.core\.(interfaces\.Package|BasePackage)\b/;
}
else {
return /\bimport\s+expo\.modules\.core\.(interfaces\.Package|BasePackage)\b/;
}
})();
// Very naive check to skip non-expo packages
if (!packageRegex.test(fileContent)) {
continue;
}
const classPathMatches = fileContent.match(/^package ([\w.]+)\b/m);
if (classPathMatches) {
const basename = path_1.default.basename(file, path_1.default.extname(file));
classes.push(`${classPathMatches[1]}.${basename}`);
}
}
}));
return classes.sort();
}
/**
* Converts the package name and gradle file path to Android's project name.
* `$` to indicate subprojects
* `/` path will transform as `-`
*
* Example: `@expo/example` + `android/build.gradle` → `expo-example`
*
* Example: multiple projects
* - `expo-test` + `android/build.gradle` → `react-native-third-party`
* - `expo-test` + `subproject/build.gradle` → `react-native-third-party$subproject`
*/
function convertPackageNameToProjectName(packageName, buildGradleFile) {
const name = packageName.replace(/^@/g, '').replace(/\W+/g, '-');
const baseDir = path_1.default.dirname(buildGradleFile).replace(/\//g, '-');
return baseDir === 'android' ? name : `${name}$${baseDir}`;
}
exports.convertPackageNameToProjectName = convertPackageNameToProjectName;
/**
* Given the contents of a `gradle.properties` file,
* searches for a property with the given name.
*
* This function will return the first property found with the given name.
* The implementation follows config-plugins and
* tries to align the behavior with the `withGradleProperties` plugin.
*/
function searchGradlePropertyFirst(contents, propertyName) {
const lines = contents.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line && !line.startsWith('#')) {
const eok = line.indexOf('=');
const key = line.slice(0, eok);
if (key === propertyName) {
const value = line.slice(eok + 1, line.length);
return value;
}
}
}
return null;
}
exports.searchGradlePropertyFirst = searchGradlePropertyFirst;
//# sourceMappingURL=android.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,15 @@
import type { ExtraDependencies, ModuleDescriptorIos, ModuleIosPodspecInfo, PackageRevision, SearchOptions } from '../types';
export declare function getSwiftModuleNames(pods: ModuleIosPodspecInfo[], swiftModuleNames: string[] | undefined): string[];
/**
* Resolves module search result with additional details required for iOS platform.
*/
export declare function resolveModuleAsync(packageName: string, revision: PackageRevision, options: SearchOptions): Promise<ModuleDescriptorIos | null>;
export declare function resolveExtraBuildDependenciesAsync(projectNativeRoot: string): Promise<ExtraDependencies | null>;
/**
* Generates Swift file that contains all autolinked Swift packages.
*/
export declare function generatePackageListAsync(modules: ModuleDescriptorIos[], targetPath: string): Promise<void>;
/**
* Formats an array of modules to Swift's array containing ReactDelegateHandlers
*/
export declare function formatArrayOfReactDelegateHandler(modules: ModuleDescriptorIos[]): string;

View File

@@ -0,0 +1,186 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatArrayOfReactDelegateHandler = exports.generatePackageListAsync = exports.resolveExtraBuildDependenciesAsync = exports.resolveModuleAsync = exports.getSwiftModuleNames = void 0;
const fast_glob_1 = __importDefault(require("fast-glob"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const APPLE_PROPERTIES_FILE = 'Podfile.properties.json';
const APPLE_EXTRA_BUILD_DEPS_KEY = 'apple.extraPods';
const indent = ' ';
async function findPodspecFiles(revision) {
const configPodspecPaths = revision.config?.applePodspecPaths();
if (configPodspecPaths && configPodspecPaths.length) {
return configPodspecPaths;
}
const podspecFiles = await (0, fast_glob_1.default)('*/*.podspec', {
cwd: revision.path,
ignore: ['**/node_modules/**'],
});
return podspecFiles;
}
function getSwiftModuleNames(pods, swiftModuleNames) {
if (swiftModuleNames && swiftModuleNames.length) {
return swiftModuleNames;
}
// by default, non-alphanumeric characters in the pod name are replaced by _ in the module name
return pods.map((pod) => pod.podName.replace(/[^a-zA-Z0-9]/g, '_'));
}
exports.getSwiftModuleNames = getSwiftModuleNames;
/**
* Resolves module search result with additional details required for iOS platform.
*/
async function resolveModuleAsync(packageName, revision, options) {
const podspecFiles = await findPodspecFiles(revision);
if (!podspecFiles.length) {
return null;
}
const pods = podspecFiles.map((podspecFile) => ({
podName: path_1.default.basename(podspecFile, path_1.default.extname(podspecFile)),
podspecDir: path_1.default.dirname(path_1.default.join(revision.path, podspecFile)),
}));
const swiftModuleNames = getSwiftModuleNames(pods, revision.config?.appleSwiftModuleNames());
return {
packageName,
pods,
swiftModuleNames,
flags: options.flags,
modules: revision.config?.appleModules() ?? [],
appDelegateSubscribers: revision.config?.appleAppDelegateSubscribers() ?? [],
reactDelegateHandlers: revision.config?.appleReactDelegateHandlers() ?? [],
debugOnly: revision.config?.appleDebugOnly() ?? false,
};
}
exports.resolveModuleAsync = resolveModuleAsync;
async function resolveExtraBuildDependenciesAsync(projectNativeRoot) {
const propsFile = path_1.default.join(projectNativeRoot, APPLE_PROPERTIES_FILE);
try {
const contents = await fs_extra_1.default.readFile(propsFile, 'utf8');
const podfileJson = JSON.parse(contents);
if (podfileJson[APPLE_EXTRA_BUILD_DEPS_KEY]) {
// expo-build-properties would serialize the extraPods as JSON string, we should parse it again.
const extraPods = JSON.parse(podfileJson[APPLE_EXTRA_BUILD_DEPS_KEY]);
return extraPods;
}
}
catch { }
return null;
}
exports.resolveExtraBuildDependenciesAsync = resolveExtraBuildDependenciesAsync;
/**
* Generates Swift file that contains all autolinked Swift packages.
*/
async function generatePackageListAsync(modules, targetPath) {
const className = path_1.default.basename(targetPath, path_1.default.extname(targetPath));
const generatedFileContent = await generatePackageListFileContentAsync(modules, className);
await fs_extra_1.default.outputFile(targetPath, generatedFileContent);
}
exports.generatePackageListAsync = generatePackageListAsync;
/**
* Generates the string to put into the generated package list.
*/
async function generatePackageListFileContentAsync(modules, className) {
const iosModules = modules.filter((module) => module.modules.length ||
module.appDelegateSubscribers.length ||
module.reactDelegateHandlers.length);
const modulesToImport = iosModules.filter((module) => !module.debugOnly);
const debugOnlyModules = iosModules.filter((module) => module.debugOnly);
const swiftModules = []
.concat(...modulesToImport.map((module) => module.swiftModuleNames))
.filter(Boolean);
const debugOnlySwiftModules = []
.concat(...debugOnlyModules.map((module) => module.swiftModuleNames))
.filter(Boolean);
const modulesClassNames = []
.concat(...modulesToImport.map((module) => module.modules))
.filter(Boolean);
const debugOnlyModulesClassNames = []
.concat(...debugOnlyModules.map((module) => module.modules))
.filter(Boolean);
const appDelegateSubscribers = [].concat(...modulesToImport.map((module) => module.appDelegateSubscribers));
const debugOnlyAppDelegateSubscribers = [].concat(...debugOnlyModules.map((module) => module.appDelegateSubscribers));
const reactDelegateHandlerModules = modulesToImport.filter((module) => !!module.reactDelegateHandlers.length);
const debugOnlyReactDelegateHandlerModules = debugOnlyModules.filter((module) => !!module.reactDelegateHandlers.length);
return `/**
* Automatically generated by expo-modules-autolinking.
*
* This autogenerated class provides a list of classes of native Expo modules,
* but only these that are written in Swift and use the new API for creating Expo modules.
*/
import ExpoModulesCore
${generateCommonImportList(swiftModules)}
${generateDebugOnlyImportList(debugOnlySwiftModules)}
@objc(${className})
public class ${className}: ModulesProvider {
public override func getModuleClasses() -> [AnyModule.Type] {
${generateModuleClasses(modulesClassNames, debugOnlyModulesClassNames)}
}
public override func getAppDelegateSubscribers() -> [ExpoAppDelegateSubscriber.Type] {
${generateModuleClasses(appDelegateSubscribers, debugOnlyAppDelegateSubscribers)}
}
public override func getReactDelegateHandlers() -> [ExpoReactDelegateHandlerTupleType] {
${generateReactDelegateHandlers(reactDelegateHandlerModules, debugOnlyReactDelegateHandlerModules)}
}
}
`;
}
function generateCommonImportList(swiftModules) {
return swiftModules.map((moduleName) => `import ${moduleName}`).join('\n');
}
function generateDebugOnlyImportList(swiftModules) {
if (!swiftModules.length) {
return '';
}
return (wrapInDebugConfigurationCheck(0, swiftModules.map((moduleName) => `import ${moduleName}`).join('\n')) + '\n');
}
function generateModuleClasses(classNames, debugOnlyClassName) {
const commonClassNames = formatArrayOfClassNames(classNames);
if (debugOnlyClassName.length > 0) {
return wrapInDebugConfigurationCheck(2, `return ${formatArrayOfClassNames(classNames.concat(debugOnlyClassName))}`, `return ${commonClassNames}`);
}
else {
return `${indent.repeat(2)}return ${commonClassNames}`;
}
}
/**
* Formats an array of class names to Swift's array containing these classes.
*/
function formatArrayOfClassNames(classNames) {
return `[${classNames.map((className) => `\n${indent.repeat(3)}${className}.self`).join(',')}
${indent.repeat(2)}]`;
}
function generateReactDelegateHandlers(module, debugOnlyModules) {
const commonModules = formatArrayOfReactDelegateHandler(module);
if (debugOnlyModules.length > 0) {
return wrapInDebugConfigurationCheck(2, `return ${formatArrayOfReactDelegateHandler(module.concat(debugOnlyModules))}`, `return ${commonModules}`);
}
else {
return `${indent.repeat(2)}return ${commonModules}`;
}
}
/**
* Formats an array of modules to Swift's array containing ReactDelegateHandlers
*/
function formatArrayOfReactDelegateHandler(modules) {
const values = [];
for (const module of modules) {
for (const handler of module.reactDelegateHandlers) {
values.push(`(packageName: "${module.packageName}", handler: ${handler}.self)`);
}
}
return `[${values.map((value) => `\n${indent.repeat(3)}${value}`).join(',')}
${indent.repeat(2)}]`;
}
exports.formatArrayOfReactDelegateHandler = formatArrayOfReactDelegateHandler;
function wrapInDebugConfigurationCheck(indentationLevel, debugBlock, releaseBlock = null) {
if (releaseBlock) {
return `${indent.repeat(indentationLevel)}#if EXPO_CONFIGURATION_DEBUG\n${indent.repeat(indentationLevel)}${debugBlock}\n${indent.repeat(indentationLevel)}#else\n${indent.repeat(indentationLevel)}${releaseBlock}\n${indent.repeat(indentationLevel)}#endif`;
}
return `${indent.repeat(indentationLevel)}#if EXPO_CONFIGURATION_DEBUG\n${indent.repeat(indentationLevel)}${debugBlock}\n${indent.repeat(indentationLevel)}#endif`;
}
//# sourceMappingURL=apple.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import type { ExtraDependencies, ModuleDescriptorDevTools, PackageRevision } from '../types';
export declare function resolveModuleAsync(packageName: string, revision: PackageRevision): Promise<ModuleDescriptorDevTools | null>;
export declare function resolveExtraBuildDependenciesAsync(_projectNativeRoot: string): Promise<ExtraDependencies | null>;

View File

@@ -0,0 +1,24 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveExtraBuildDependenciesAsync = exports.resolveModuleAsync = void 0;
const path_1 = __importDefault(require("path"));
async function resolveModuleAsync(packageName, revision) {
const devtoolsConfig = revision.config?.toJSON().devtools;
if (devtoolsConfig == null) {
return null;
}
return {
packageName,
packageRoot: revision.path,
webpageRoot: path_1.default.join(revision.path, devtoolsConfig.webpageRoot),
};
}
exports.resolveModuleAsync = resolveModuleAsync;
async function resolveExtraBuildDependenciesAsync(_projectNativeRoot) {
return null;
}
exports.resolveExtraBuildDependenciesAsync = resolveExtraBuildDependenciesAsync;
//# sourceMappingURL=devtools.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../src/platforms/devtools.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAIjB,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,QAAyB;IAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC;IAC1D,IAAI,cAAc,IAAI,IAAI,EAAE;QAC1B,OAAO,IAAI,CAAC;KACb;IAED,OAAO;QACL,WAAW;QACX,WAAW,EAAE,QAAQ,CAAC,IAAI;QAC1B,WAAW,EAAE,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC;KAClE,CAAC;AACJ,CAAC;AAdD,gDAcC;AAEM,KAAK,UAAU,kCAAkC,CACtD,kBAA0B;IAE1B,OAAO,IAAI,CAAC;AACd,CAAC;AAJD,gFAIC","sourcesContent":["import path from 'path';\n\nimport type { ExtraDependencies, ModuleDescriptorDevTools, PackageRevision } from '../types';\n\nexport async function resolveModuleAsync(\n packageName: string,\n revision: PackageRevision\n): Promise<ModuleDescriptorDevTools | null> {\n const devtoolsConfig = revision.config?.toJSON().devtools;\n if (devtoolsConfig == null) {\n return null;\n }\n\n return {\n packageName,\n packageRoot: revision.path,\n webpageRoot: path.join(revision.path, devtoolsConfig.webpageRoot),\n };\n}\n\nexport async function resolveExtraBuildDependenciesAsync(\n _projectNativeRoot: string\n): Promise<ExtraDependencies | null> {\n return null;\n}\n"]}