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 @@
export type ContentsJsonImageIdiom = 'iphone' | 'ipad' | 'watchos' | 'ios' | 'ios-marketing' | 'universal';
export type ContentsJsonImageAppearance = {
appearance: 'luminosity';
value: 'dark';
};
export type ContentsJsonImageScale = '1x' | '2x' | '3x';
export interface ContentsJsonImage {
appearances?: ContentsJsonImageAppearance[];
idiom: ContentsJsonImageIdiom;
size?: string;
scale?: ContentsJsonImageScale;
filename?: string;
platform?: ContentsJsonImageIdiom;
}
export interface ContentsJson {
images: ContentsJsonImage[];
info: {
version: number;
author: string;
};
}
export declare function createContentsJsonItem(item: ContentsJsonImage): ContentsJsonImage;
/**
* Writes the Config.json which is used to assign images to their respective platform, dpi, and idiom.
*
* @param directory path to add the Contents.json to.
* @param contents image json data
*/
export declare function writeContentsJsonAsync(directory: string, { images }: Pick<ContentsJson, 'images'>): Promise<void>;

View File

@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createContentsJsonItem = createContentsJsonItem;
exports.writeContentsJsonAsync = writeContentsJsonAsync;
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function createContentsJsonItem(item) {
return item;
}
/**
* Writes the Config.json which is used to assign images to their respective platform, dpi, and idiom.
*
* @param directory path to add the Contents.json to.
* @param contents image json data
*/
async function writeContentsJsonAsync(directory, {
images
}) {
await _fsExtra().default.ensureDir(directory);
await _fsExtra().default.writeFile((0, _path().join)(directory, 'Contents.json'), JSON.stringify({
images,
info: {
version: 1,
// common practice is for the tool that generated the icons to be the "author"
author: 'expo'
}
}, null, 2));
}
//# sourceMappingURL=AssetContents.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetContents.js","names":["_fsExtra","data","_interopRequireDefault","require","_path","obj","__esModule","default","createContentsJsonItem","item","writeContentsJsonAsync","directory","images","fs","ensureDir","writeFile","join","JSON","stringify","info","version","author"],"sources":["../../../src/plugins/icons/AssetContents.ts"],"sourcesContent":["import fs from 'fs-extra';\nimport { join } from 'path';\n\nexport type ContentsJsonImageIdiom =\n | 'iphone'\n | 'ipad'\n | 'watchos'\n | 'ios'\n | 'ios-marketing'\n | 'universal';\n\nexport type ContentsJsonImageAppearance = {\n appearance: 'luminosity';\n value: 'dark';\n};\n\nexport type ContentsJsonImageScale = '1x' | '2x' | '3x';\n\nexport interface ContentsJsonImage {\n appearances?: ContentsJsonImageAppearance[];\n idiom: ContentsJsonImageIdiom;\n size?: string;\n scale?: ContentsJsonImageScale;\n filename?: string;\n platform?: ContentsJsonImageIdiom;\n}\n\nexport interface ContentsJson {\n images: ContentsJsonImage[];\n info: {\n version: number;\n author: string;\n };\n}\n\nexport function createContentsJsonItem(item: ContentsJsonImage): ContentsJsonImage {\n return item;\n}\n\n/**\n * Writes the Config.json which is used to assign images to their respective platform, dpi, and idiom.\n *\n * @param directory path to add the Contents.json to.\n * @param contents image json data\n */\nexport async function writeContentsJsonAsync(\n directory: string,\n { images }: Pick<ContentsJson, 'images'>\n): Promise<void> {\n await fs.ensureDir(directory);\n\n await fs.writeFile(\n join(directory, 'Contents.json'),\n JSON.stringify(\n {\n images,\n info: {\n version: 1,\n // common practice is for the tool that generated the icons to be the \"author\"\n author: 'expo',\n },\n },\n null,\n 2\n )\n );\n}\n"],"mappings":";;;;;;;AAAA,SAAAA,SAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,QAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,MAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,KAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAA4B,SAAAC,uBAAAG,GAAA,WAAAA,GAAA,IAAAA,GAAA,CAAAC,UAAA,GAAAD,GAAA,KAAAE,OAAA,EAAAF,GAAA;AAkCrB,SAASG,sBAAsBA,CAACC,IAAuB,EAAqB;EACjF,OAAOA,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeC,sBAAsBA,CAC1CC,SAAiB,EACjB;EAAEC;AAAqC,CAAC,EACzB;EACf,MAAMC,kBAAE,CAACC,SAAS,CAACH,SAAS,CAAC;EAE7B,MAAME,kBAAE,CAACE,SAAS,CAChB,IAAAC,YAAI,EAACL,SAAS,EAAE,eAAe,CAAC,EAChCM,IAAI,CAACC,SAAS,CACZ;IACEN,MAAM;IACNO,IAAI,EAAE;MACJC,OAAO,EAAE,CAAC;MACV;MACAC,MAAM,EAAE;IACV;EACF,CAAC,EACD,IAAI,EACJ,CACF,CACF,CAAC;AACH","ignoreList":[]}

View File

@@ -0,0 +1,39 @@
import { AndroidConfig, ConfigPlugin } from '@expo/config-plugins';
import { ExpoConfig } from '@expo/config-types';
type DPIString = 'mdpi' | 'hdpi' | 'xhdpi' | 'xxhdpi' | 'xxxhdpi';
type dpiMap = Record<DPIString, {
folderName: string;
scale: number;
}>;
export declare const dpiValues: dpiMap;
export declare const ANDROID_RES_PATH = "android/app/src/main/res/";
export declare const withAndroidIcons: ConfigPlugin;
export declare function setRoundIconManifest(config: Pick<ExpoConfig, 'android'>, manifest: AndroidConfig.Manifest.AndroidManifest): AndroidConfig.Manifest.AndroidManifest;
export declare function getIcon(config: ExpoConfig): string | null;
export declare function getAdaptiveIcon(config: ExpoConfig): {
foregroundImage: string | null;
backgroundColor: string | null;
backgroundImage: string | null;
monochromeImage: string | null;
};
/**
* Resizes the user-provided icon to create a set of legacy icon files in
* their respective "mipmap" directories for <= Android 7, and creates a set of adaptive
* icon files for > Android 7 from the adaptive icon files (if provided).
*/
export declare function setIconAsync(projectRoot: string, { icon, backgroundColor, backgroundImage, monochromeImage, isAdaptive, }: {
icon: string | null;
backgroundColor: string | null;
backgroundImage: string | null;
monochromeImage: string | null;
isAdaptive: boolean;
}): Promise<true | null>;
/**
* Configures adaptive icon files to be used on Android 8 and up. A foreground image must be provided,
* and will have a transparent background unless:
* - A backgroundImage is provided, or
* - A backgroundColor was specified
*/
export declare function configureAdaptiveIconAsync(projectRoot: string, foregroundImage: string, backgroundImage: string | null, monochromeImage: string | null, isAdaptive: boolean): Promise<void>;
export declare const createAdaptiveIconXmlString: (backgroundImage: string | null, monochromeImage: string | null) => string;
export {};

View File

@@ -0,0 +1,356 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ANDROID_RES_PATH = void 0;
exports.configureAdaptiveIconAsync = configureAdaptiveIconAsync;
exports.dpiValues = exports.createAdaptiveIconXmlString = void 0;
exports.getAdaptiveIcon = getAdaptiveIcon;
exports.getIcon = getIcon;
exports.setIconAsync = setIconAsync;
exports.setRoundIconManifest = setRoundIconManifest;
exports.withAndroidIcons = void 0;
function _configPlugins() {
const data = require("@expo/config-plugins");
_configPlugins = function () {
return data;
};
return data;
}
function _imageUtils() {
const data = require("@expo/image-utils");
_imageUtils = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _withAndroidManifestIcons() {
const data = require("./withAndroidManifestIcons");
_withAndroidManifestIcons = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const {
Colors
} = _configPlugins().AndroidConfig;
const dpiValues = exports.dpiValues = {
mdpi: {
folderName: 'mipmap-mdpi',
scale: 1
},
hdpi: {
folderName: 'mipmap-hdpi',
scale: 1.5
},
xhdpi: {
folderName: 'mipmap-xhdpi',
scale: 2
},
xxhdpi: {
folderName: 'mipmap-xxhdpi',
scale: 3
},
xxxhdpi: {
folderName: 'mipmap-xxxhdpi',
scale: 4
}
};
const BASELINE_PIXEL_SIZE = 108;
const ANDROID_RES_PATH = exports.ANDROID_RES_PATH = 'android/app/src/main/res/';
const MIPMAP_ANYDPI_V26 = 'mipmap-anydpi-v26';
const ICON_BACKGROUND = 'iconBackground';
const IC_LAUNCHER_PNG = 'ic_launcher.png';
const IC_LAUNCHER_ROUND_PNG = 'ic_launcher_round.png';
const IC_LAUNCHER_BACKGROUND_PNG = 'ic_launcher_background.png';
const IC_LAUNCHER_FOREGROUND_PNG = 'ic_launcher_foreground.png';
const IC_LAUNCHER_MONOCHROME_PNG = 'ic_launcher_monochrome.png';
const IC_LAUNCHER_XML = 'ic_launcher.xml';
const IC_LAUNCHER_ROUND_XML = 'ic_launcher_round.xml';
const withAndroidIcons = config => {
const {
foregroundImage,
backgroundColor,
backgroundImage,
monochromeImage
} = getAdaptiveIcon(config);
const icon = foregroundImage ?? getIcon(config);
if (!icon) {
return config;
}
config = (0, _withAndroidManifestIcons().withAndroidManifestIcons)(config);
// Apply colors.xml changes
config = withAndroidAdaptiveIconColors(config, backgroundColor);
return (0, _configPlugins().withDangerousMod)(config, ['android', async config => {
await setIconAsync(config.modRequest.projectRoot, {
icon,
backgroundColor,
backgroundImage,
monochromeImage,
isAdaptive: !!config.android?.adaptiveIcon
});
return config;
}]);
};
exports.withAndroidIcons = withAndroidIcons;
function setRoundIconManifest(config, manifest) {
const isAdaptive = !!config.android?.adaptiveIcon;
const application = _configPlugins().AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);
if (isAdaptive) {
application.$['android:roundIcon'] = '@mipmap/ic_launcher_round';
} else {
delete application.$['android:roundIcon'];
}
return manifest;
}
const withAndroidAdaptiveIconColors = (config, backgroundColor) => {
return (0, _configPlugins().withAndroidColors)(config, config => {
config.modResults = setBackgroundColor(backgroundColor ?? '#FFFFFF', config.modResults);
return config;
});
};
function getIcon(config) {
return config.android?.icon || config.icon || null;
}
function getAdaptiveIcon(config) {
return {
foregroundImage: config.android?.adaptiveIcon?.foregroundImage ?? null,
backgroundColor: config.android?.adaptiveIcon?.backgroundColor ?? null,
backgroundImage: config.android?.adaptiveIcon?.backgroundImage ?? null,
monochromeImage: config.android?.adaptiveIcon?.monochromeImage ?? null
};
}
/**
* Resizes the user-provided icon to create a set of legacy icon files in
* their respective "mipmap" directories for <= Android 7, and creates a set of adaptive
* icon files for > Android 7 from the adaptive icon files (if provided).
*/
async function setIconAsync(projectRoot, {
icon,
backgroundColor,
backgroundImage,
monochromeImage,
isAdaptive
}) {
if (!icon) {
return null;
}
await configureLegacyIconAsync(projectRoot, icon, backgroundImage, backgroundColor);
if (isAdaptive) {
await generateRoundIconAsync(projectRoot, icon, backgroundImage, backgroundColor);
} else {
await deleteIconNamedAsync(projectRoot, IC_LAUNCHER_ROUND_PNG);
}
await configureAdaptiveIconAsync(projectRoot, icon, backgroundImage, monochromeImage, isAdaptive);
return true;
}
/**
* Configures legacy icon files to be used on Android 7 and earlier. If adaptive icon configuration
* was provided, we create a pseudo-adaptive icon by layering the provided files (or background
* color if no backgroundImage is provided. If no backgroundImage and no backgroundColor are provided,
* the background is set to transparent.)
*/
async function configureLegacyIconAsync(projectRoot, icon, backgroundImage, backgroundColor) {
return generateMultiLayerImageAsync(projectRoot, {
icon,
backgroundImage,
backgroundColor,
outputImageFileName: IC_LAUNCHER_PNG,
imageCacheFolder: 'android-standard-square',
backgroundImageCacheFolder: 'android-standard-square-background'
});
}
async function generateRoundIconAsync(projectRoot, icon, backgroundImage, backgroundColor) {
return generateMultiLayerImageAsync(projectRoot, {
icon,
borderRadiusRatio: 0.5,
outputImageFileName: IC_LAUNCHER_ROUND_PNG,
backgroundImage,
backgroundColor,
imageCacheFolder: 'android-standard-circle',
backgroundImageCacheFolder: 'android-standard-round-background'
});
}
/**
* Configures adaptive icon files to be used on Android 8 and up. A foreground image must be provided,
* and will have a transparent background unless:
* - A backgroundImage is provided, or
* - A backgroundColor was specified
*/
async function configureAdaptiveIconAsync(projectRoot, foregroundImage, backgroundImage, monochromeImage, isAdaptive) {
if (monochromeImage) {
await generateMonochromeImageAsync(projectRoot, {
icon: monochromeImage,
imageCacheFolder: 'android-adaptive-monochrome',
outputImageFileName: IC_LAUNCHER_MONOCHROME_PNG
});
}
await generateMultiLayerImageAsync(projectRoot, {
backgroundColor: 'transparent',
backgroundImage,
backgroundImageCacheFolder: 'android-adaptive-background',
outputImageFileName: IC_LAUNCHER_FOREGROUND_PNG,
icon: foregroundImage,
imageCacheFolder: 'android-adaptive-foreground',
backgroundImageFileName: IC_LAUNCHER_BACKGROUND_PNG
});
// create ic_launcher.xml and ic_launcher_round.xml
const icLauncherXmlString = createAdaptiveIconXmlString(backgroundImage, monochromeImage);
await createAdaptiveIconXmlFiles(projectRoot, icLauncherXmlString,
// If the user only defined icon and not android.adaptiveIcon, then skip enabling the layering system
// this will scale the image down and present it uncropped.
isAdaptive);
}
function setBackgroundColor(backgroundColor, colors) {
return Colors.assignColorValue(colors, {
value: backgroundColor,
name: ICON_BACKGROUND
});
}
const createAdaptiveIconXmlString = (backgroundImage, monochromeImage) => {
const background = backgroundImage ? `@mipmap/ic_launcher_background` : `@color/iconBackground`;
const iconElements = [`<background android:drawable="${background}"/>`, '<foreground android:drawable="@mipmap/ic_launcher_foreground"/>'];
if (monochromeImage) {
iconElements.push('<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>');
}
return `<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
${iconElements.join('\n ')}
</adaptive-icon>`;
};
exports.createAdaptiveIconXmlString = createAdaptiveIconXmlString;
async function createAdaptiveIconXmlFiles(projectRoot, icLauncherXmlString, add) {
const anyDpiV26Directory = _path().default.resolve(projectRoot, ANDROID_RES_PATH, MIPMAP_ANYDPI_V26);
await _fsExtra().default.ensureDir(anyDpiV26Directory);
const launcherPath = _path().default.resolve(anyDpiV26Directory, IC_LAUNCHER_XML);
const launcherRoundPath = _path().default.resolve(anyDpiV26Directory, IC_LAUNCHER_ROUND_XML);
if (add) {
await Promise.all([_fsExtra().default.writeFile(launcherPath, icLauncherXmlString), _fsExtra().default.writeFile(launcherRoundPath, icLauncherXmlString)]);
} else {
// Remove the xml if the icon switches from adaptive to standard.
await Promise.all([launcherPath, launcherRoundPath].map(async path => {
if (_fsExtra().default.existsSync(path)) {
return _fsExtra().default.remove(path);
}
}));
}
}
async function generateMultiLayerImageAsync(projectRoot, {
icon,
backgroundColor,
backgroundImage,
imageCacheFolder,
backgroundImageCacheFolder,
borderRadiusRatio,
outputImageFileName,
backgroundImageFileName
}) {
await iterateDpiValues(projectRoot, async ({
dpiFolder,
scale
}) => {
let iconLayer = await generateIconAsync(projectRoot, {
cacheType: imageCacheFolder,
src: icon,
scale,
// backgroundImage overrides backgroundColor
backgroundColor: backgroundImage ? 'transparent' : backgroundColor ?? 'transparent',
borderRadiusRatio
});
if (backgroundImage) {
const backgroundLayer = await generateIconAsync(projectRoot, {
cacheType: backgroundImageCacheFolder,
src: backgroundImage,
scale,
backgroundColor: 'transparent',
borderRadiusRatio
});
if (backgroundImageFileName) {
await _fsExtra().default.writeFile(_path().default.resolve(dpiFolder, backgroundImageFileName), backgroundLayer);
} else {
iconLayer = await (0, _imageUtils().compositeImagesAsync)({
foreground: iconLayer,
background: backgroundLayer
});
}
} else if (backgroundImageFileName) {
// Remove any instances of ic_launcher_background.png that are there from previous icons
await deleteIconNamedAsync(projectRoot, backgroundImageFileName);
}
await _fsExtra().default.ensureDir(dpiFolder);
await _fsExtra().default.writeFile(_path().default.resolve(dpiFolder, outputImageFileName), iconLayer);
});
}
async function generateMonochromeImageAsync(projectRoot, {
icon,
imageCacheFolder,
outputImageFileName
}) {
await iterateDpiValues(projectRoot, async ({
dpiFolder,
scale
}) => {
const monochromeIcon = await generateIconAsync(projectRoot, {
cacheType: imageCacheFolder,
src: icon,
scale,
backgroundColor: 'transparent'
});
await _fsExtra().default.ensureDir(dpiFolder);
await _fsExtra().default.writeFile(_path().default.resolve(dpiFolder, outputImageFileName), monochromeIcon);
});
}
function iterateDpiValues(projectRoot, callback) {
return Promise.all(Object.values(dpiValues).map(value => callback({
dpiFolder: _path().default.resolve(projectRoot, ANDROID_RES_PATH, value.folderName),
...value
})));
}
async function deleteIconNamedAsync(projectRoot, name) {
return iterateDpiValues(projectRoot, ({
dpiFolder
}) => {
return _fsExtra().default.remove(_path().default.resolve(dpiFolder, name));
});
}
async function generateIconAsync(projectRoot, {
cacheType,
src,
scale,
backgroundColor,
borderRadiusRatio
}) {
const iconSizePx = BASELINE_PIXEL_SIZE * scale;
return (await (0, _imageUtils().generateImageAsync)({
projectRoot,
cacheType
}, {
src,
width: iconSizePx,
height: iconSizePx,
resizeMode: 'cover',
backgroundColor,
borderRadius: borderRadiusRatio ? iconSizePx * borderRadiusRatio : undefined
})).source;
}
//# sourceMappingURL=withAndroidIcons.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
import { AndroidConfig, ConfigPlugin } from '@expo/config-plugins';
import { ExpoConfig } from '@expo/config-types';
export declare const withAndroidManifestIcons: ConfigPlugin;
export declare function setRoundIconManifest(config: Pick<ExpoConfig, 'android'>, manifest: AndroidConfig.Manifest.AndroidManifest): AndroidConfig.Manifest.AndroidManifest;

View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setRoundIconManifest = setRoundIconManifest;
exports.withAndroidManifestIcons = void 0;
function _configPlugins() {
const data = require("@expo/config-plugins");
_configPlugins = function () {
return data;
};
return data;
}
const withAndroidManifestIcons = config => (0, _configPlugins().withAndroidManifest)(config, config => {
config.modResults = setRoundIconManifest(config, config.modResults);
return config;
});
exports.withAndroidManifestIcons = withAndroidManifestIcons;
function setRoundIconManifest(config, manifest) {
const isAdaptive = !!config.android?.adaptiveIcon;
const application = _configPlugins().AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);
if (isAdaptive) {
application.$['android:roundIcon'] = '@mipmap/ic_launcher_round';
} else {
delete application.$['android:roundIcon'];
}
return manifest;
}
//# sourceMappingURL=withAndroidManifestIcons.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"withAndroidManifestIcons.js","names":["_configPlugins","data","require","withAndroidManifestIcons","config","withAndroidManifest","modResults","setRoundIconManifest","exports","manifest","isAdaptive","android","adaptiveIcon","application","AndroidConfig","Manifest","getMainApplicationOrThrow","$"],"sources":["../../../src/plugins/icons/withAndroidManifestIcons.ts"],"sourcesContent":["import { AndroidConfig, ConfigPlugin, withAndroidManifest } from '@expo/config-plugins';\nimport { ExpoConfig } from '@expo/config-types';\n\nexport const withAndroidManifestIcons: ConfigPlugin = (config) =>\n withAndroidManifest(config, (config) => {\n config.modResults = setRoundIconManifest(config, config.modResults);\n return config;\n });\n\nexport function setRoundIconManifest(\n config: Pick<ExpoConfig, 'android'>,\n manifest: AndroidConfig.Manifest.AndroidManifest\n): AndroidConfig.Manifest.AndroidManifest {\n const isAdaptive = !!config.android?.adaptiveIcon;\n const application = AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);\n\n if (isAdaptive) {\n application.$['android:roundIcon'] = '@mipmap/ic_launcher_round';\n } else {\n delete application.$['android:roundIcon'];\n }\n return manifest;\n}\n"],"mappings":";;;;;;;AAAA,SAAAA,eAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,cAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAGO,MAAME,wBAAsC,GAAIC,MAAM,IAC3D,IAAAC,oCAAmB,EAACD,MAAM,EAAGA,MAAM,IAAK;EACtCA,MAAM,CAACE,UAAU,GAAGC,oBAAoB,CAACH,MAAM,EAAEA,MAAM,CAACE,UAAU,CAAC;EACnE,OAAOF,MAAM;AACf,CAAC,CAAC;AAACI,OAAA,CAAAL,wBAAA,GAAAA,wBAAA;AAEE,SAASI,oBAAoBA,CAClCH,MAAmC,EACnCK,QAAgD,EACR;EACxC,MAAMC,UAAU,GAAG,CAAC,CAACN,MAAM,CAACO,OAAO,EAAEC,YAAY;EACjD,MAAMC,WAAW,GAAGC,8BAAa,CAACC,QAAQ,CAACC,yBAAyB,CAACP,QAAQ,CAAC;EAE9E,IAAIC,UAAU,EAAE;IACdG,WAAW,CAACI,CAAC,CAAC,mBAAmB,CAAC,GAAG,2BAA2B;EAClE,CAAC,MAAM;IACL,OAAOJ,WAAW,CAACI,CAAC,CAAC,mBAAmB,CAAC;EAC3C;EACA,OAAOR,QAAQ;AACjB","ignoreList":[]}

View File

@@ -0,0 +1,12 @@
import { ConfigPlugin } from '@expo/config-plugins';
import { ExpoConfig } from '@expo/config-types';
import { ContentsJson } from './AssetContents';
export declare const withIosIcons: ConfigPlugin;
export declare function getIcons(config: Pick<ExpoConfig, 'icon' | 'ios'>): string | null;
export declare function setIconsAsync(config: ExpoConfig, projectRoot: string): Promise<void>;
export declare function generateUniversalIconAsync(projectRoot: string, { icon, cacheKey, iosNamedProjectRoot, platform, }: {
platform: 'watchos' | 'ios';
icon?: string | null;
iosNamedProjectRoot: string;
cacheKey: string;
}): Promise<ContentsJson['images']>;

View File

@@ -0,0 +1,143 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.generateUniversalIconAsync = generateUniversalIconAsync;
exports.getIcons = getIcons;
exports.setIconsAsync = setIconsAsync;
exports.withIosIcons = void 0;
function _configPlugins() {
const data = require("@expo/config-plugins");
_configPlugins = function () {
return data;
};
return data;
}
function _imageUtils() {
const data = require("@expo/image-utils");
_imageUtils = function () {
return data;
};
return data;
}
function fs() {
const data = _interopRequireWildcard(require("fs-extra"));
fs = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _AssetContents() {
const data = require("./AssetContents");
_AssetContents = function () {
return data;
};
return data;
}
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const {
getProjectName
} = _configPlugins().IOSConfig.XcodeUtils;
const IMAGE_CACHE_NAME = 'icons';
const IMAGESET_PATH = 'Images.xcassets/AppIcon.appiconset';
const withIosIcons = config => {
return (0, _configPlugins().withDangerousMod)(config, ['ios', async config => {
await setIconsAsync(config, config.modRequest.projectRoot);
return config;
}]);
};
exports.withIosIcons = withIosIcons;
function getIcons(config) {
// No support for empty strings.
return config.ios?.icon || config.icon || null;
}
async function setIconsAsync(config, projectRoot) {
const icon = getIcons(config);
if (!icon) {
_configPlugins().WarningAggregator.addWarningIOS('icon', 'No icon is defined in the Expo config.');
}
// Something like projectRoot/ios/MyApp/
const iosNamedProjectRoot = getIosNamedProjectPath(projectRoot);
// Ensure the Images.xcassets/AppIcon.appiconset path exists
await fs().ensureDir((0, _path().join)(iosNamedProjectRoot, IMAGESET_PATH));
// Store the image JSON data for assigning via the Contents.json
const imagesJson = await generateUniversalIconAsync(projectRoot, {
icon,
cacheKey: 'universal-icon',
iosNamedProjectRoot,
platform: 'ios'
});
// Finally, write the Config.json
await (0, _AssetContents().writeContentsJsonAsync)((0, _path().join)(iosNamedProjectRoot, IMAGESET_PATH), {
images: imagesJson
});
}
/**
* Return the project's named iOS path: ios/MyProject/
*
* @param projectRoot Expo project root path.
*/
function getIosNamedProjectPath(projectRoot) {
const projectName = getProjectName(projectRoot);
return (0, _path().join)(projectRoot, 'ios', projectName);
}
function getAppleIconName(size, scale) {
return `App-Icon-${size}x${size}@${scale}x.png`;
}
async function generateUniversalIconAsync(projectRoot, {
icon,
cacheKey,
iosNamedProjectRoot,
platform
}) {
const size = 1024;
const filename = getAppleIconName(size, 1);
let source;
if (icon) {
// Using this method will cache the images in `.expo` based on the properties used to generate them.
// this method also supports remote URLs and using the global sharp instance.
source = (await (0, _imageUtils().generateImageAsync)({
projectRoot,
cacheType: IMAGE_CACHE_NAME + cacheKey
}, {
src: icon,
name: filename,
width: size,
height: size,
removeTransparency: true,
// The icon should be square, but if it's not then it will be cropped.
resizeMode: 'cover',
// Force the background color to solid white to prevent any transparency.
// TODO: Maybe use a more adaptive option based on the icon color?
backgroundColor: '#ffffff'
})).source;
} else {
// Create a white square image if no icon exists to mitigate the chance of a submission failure to the app store.
source = await (0, _imageUtils().createSquareAsync)({
size
});
}
// Write image buffer to the file system.
const assetPath = (0, _path().join)(iosNamedProjectRoot, IMAGESET_PATH, filename);
await fs().writeFile(assetPath, source);
return [{
filename: getAppleIconName(size, 1),
idiom: 'universal',
platform,
size: `${size}x${size}`
}];
}
//# sourceMappingURL=withIosIcons.js.map

File diff suppressed because one or more lines are too long