UNPKG

@expo/prebuild-config

Version:
372 lines (367 loc) 12.6 kB
"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 _fs() { const data = _interopRequireDefault(require("fs")); _fs = 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(e) { return e && e.__esModule ? e : { default: e }; } 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 LEGACY_BASELINE_PIXEL_SIZE = 48; const ADAPTIVE_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_WEBP = 'ic_launcher.webp'; const IC_LAUNCHER_ROUND_WEBP = 'ic_launcher_round.webp'; const IC_LAUNCHER_BACKGROUND_WEBP = 'ic_launcher_background.webp'; const IC_LAUNCHER_FOREGROUND_WEBP = 'ic_launcher_foreground.webp'; const IC_LAUNCHER_MONOCHROME_WEBP = 'ic_launcher_monochrome.webp'; 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_WEBP); } 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_WEBP, 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_WEBP, backgroundImage, backgroundColor, imageCacheFolder: 'android-standard-circle', backgroundImageCacheFolder: 'android-standard-round-background', isAdaptive: false }); } /** * 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_WEBP }); } await generateMultiLayerImageAsync(projectRoot, { backgroundColor: 'transparent', backgroundImage, backgroundImageCacheFolder: 'android-adaptive-background', outputImageFileName: IC_LAUNCHER_FOREGROUND_WEBP, icon: foregroundImage, imageCacheFolder: 'android-adaptive-foreground', backgroundImageFileName: IC_LAUNCHER_BACKGROUND_WEBP, isAdaptive: true }); // 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 _fs().default.promises.mkdir(anyDpiV26Directory, { recursive: true }); const launcherPath = _path().default.resolve(anyDpiV26Directory, IC_LAUNCHER_XML); const launcherRoundPath = _path().default.resolve(anyDpiV26Directory, IC_LAUNCHER_ROUND_XML); if (add) { await Promise.all([_fs().default.promises.writeFile(launcherPath, icLauncherXmlString, 'utf8'), _fs().default.promises.writeFile(launcherRoundPath, icLauncherXmlString, 'utf8')]); } else { // Remove the xml if the icon switches from adaptive to standard. await Promise.all([launcherPath, launcherRoundPath].map(async path => { return _fs().default.promises.rm(path, { force: true }); })); } } async function generateMultiLayerImageAsync(projectRoot, { icon, backgroundColor, backgroundImage, imageCacheFolder, backgroundImageCacheFolder, borderRadiusRatio, outputImageFileName, backgroundImageFileName, isAdaptive }) { 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, isAdaptive }); if (backgroundImage) { const backgroundLayer = await generateIconAsync(projectRoot, { cacheType: backgroundImageCacheFolder, src: backgroundImage, scale, backgroundColor: 'transparent', borderRadiusRatio, isAdaptive }); if (backgroundImageFileName) { await _fs().default.promises.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 _fs().default.promises.mkdir(dpiFolder, { recursive: true }); await _fs().default.promises.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', isAdaptive: true }); await _fs().default.promises.mkdir(dpiFolder, { recursive: true }); await _fs().default.promises.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 _fs().default.promises.rm(_path().default.resolve(dpiFolder, name), { force: true }); }); } async function generateIconAsync(projectRoot, { cacheType, src, scale, backgroundColor, borderRadiusRatio, isAdaptive }) { const iconSizePx = (isAdaptive ? ADAPTIVE_BASELINE_PIXEL_SIZE : LEGACY_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