1"use strict"; 2 3Object.defineProperty(exports, "__esModule", { 4 value: true 5}); 6exports.ANDROID_RES_PATH = void 0; 7exports.configureAdaptiveIconAsync = configureAdaptiveIconAsync; 8exports.dpiValues = exports.createAdaptiveIconXmlString = void 0; 9exports.getAdaptiveIcon = getAdaptiveIcon; 10exports.getIcon = getIcon; 11exports.setIconAsync = setIconAsync; 12exports.setRoundIconManifest = setRoundIconManifest; 13exports.withAndroidIcons = void 0; 14function _configPlugins() { 15 const data = require("@expo/config-plugins"); 16 _configPlugins = function () { 17 return data; 18 }; 19 return data; 20} 21function _imageUtils() { 22 const data = require("@expo/image-utils"); 23 _imageUtils = function () { 24 return data; 25 }; 26 return data; 27} 28function _fsExtra() { 29 const data = _interopRequireDefault(require("fs-extra")); 30 _fsExtra = function () { 31 return data; 32 }; 33 return data; 34} 35function _path() { 36 const data = _interopRequireDefault(require("path")); 37 _path = function () { 38 return data; 39 }; 40 return data; 41} 42function _withAndroidManifestIcons() { 43 const data = require("./withAndroidManifestIcons"); 44 _withAndroidManifestIcons = function () { 45 return data; 46 }; 47 return data; 48} 49function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 50const { 51 Colors 52} = _configPlugins().AndroidConfig; 53const dpiValues = { 54 mdpi: { 55 folderName: 'mipmap-mdpi', 56 scale: 1 57 }, 58 hdpi: { 59 folderName: 'mipmap-hdpi', 60 scale: 1.5 61 }, 62 xhdpi: { 63 folderName: 'mipmap-xhdpi', 64 scale: 2 65 }, 66 xxhdpi: { 67 folderName: 'mipmap-xxhdpi', 68 scale: 3 69 }, 70 xxxhdpi: { 71 folderName: 'mipmap-xxxhdpi', 72 scale: 4 73 } 74}; 75exports.dpiValues = dpiValues; 76const BASELINE_PIXEL_SIZE = 108; 77const ANDROID_RES_PATH = 'android/app/src/main/res/'; 78exports.ANDROID_RES_PATH = ANDROID_RES_PATH; 79const MIPMAP_ANYDPI_V26 = 'mipmap-anydpi-v26'; 80const ICON_BACKGROUND = 'iconBackground'; 81const IC_LAUNCHER_PNG = 'ic_launcher.png'; 82const IC_LAUNCHER_ROUND_PNG = 'ic_launcher_round.png'; 83const IC_LAUNCHER_BACKGROUND_PNG = 'ic_launcher_background.png'; 84const IC_LAUNCHER_FOREGROUND_PNG = 'ic_launcher_foreground.png'; 85const IC_LAUNCHER_MONOCHROME_PNG = 'ic_launcher_monochrome.png'; 86const IC_LAUNCHER_XML = 'ic_launcher.xml'; 87const IC_LAUNCHER_ROUND_XML = 'ic_launcher_round.xml'; 88const withAndroidIcons = config => { 89 const { 90 foregroundImage, 91 backgroundColor, 92 backgroundImage, 93 monochromeImage 94 } = getAdaptiveIcon(config); 95 const icon = foregroundImage !== null && foregroundImage !== void 0 ? foregroundImage : getIcon(config); 96 if (!icon) { 97 return config; 98 } 99 config = (0, _withAndroidManifestIcons().withAndroidManifestIcons)(config); 100 // Apply colors.xml changes 101 config = withAndroidAdaptiveIconColors(config, backgroundColor); 102 return (0, _configPlugins().withDangerousMod)(config, ['android', async config => { 103 var _config$android; 104 await setIconAsync(config.modRequest.projectRoot, { 105 icon, 106 backgroundColor, 107 backgroundImage, 108 monochromeImage, 109 isAdaptive: !!((_config$android = config.android) !== null && _config$android !== void 0 && _config$android.adaptiveIcon) 110 }); 111 return config; 112 }]); 113}; 114exports.withAndroidIcons = withAndroidIcons; 115function setRoundIconManifest(config, manifest) { 116 var _config$android2; 117 const isAdaptive = !!((_config$android2 = config.android) !== null && _config$android2 !== void 0 && _config$android2.adaptiveIcon); 118 const application = _configPlugins().AndroidConfig.Manifest.getMainApplicationOrThrow(manifest); 119 if (isAdaptive) { 120 application.$['android:roundIcon'] = '@mipmap/ic_launcher_round'; 121 } else { 122 delete application.$['android:roundIcon']; 123 } 124 return manifest; 125} 126const withAndroidAdaptiveIconColors = (config, backgroundColor) => { 127 return (0, _configPlugins().withAndroidColors)(config, config => { 128 config.modResults = setBackgroundColor(backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : '#FFFFFF', config.modResults); 129 return config; 130 }); 131}; 132function getIcon(config) { 133 var _config$android3; 134 return ((_config$android3 = config.android) === null || _config$android3 === void 0 ? void 0 : _config$android3.icon) || config.icon || null; 135} 136function getAdaptiveIcon(config) { 137 var _config$android$adapt, _config$android4, _config$android4$adap, _config$android$adapt2, _config$android5, _config$android5$adap, _config$android$adapt3, _config$android6, _config$android6$adap, _config$android$adapt4, _config$android7, _config$android7$adap; 138 return { 139 foregroundImage: (_config$android$adapt = (_config$android4 = config.android) === null || _config$android4 === void 0 ? void 0 : (_config$android4$adap = _config$android4.adaptiveIcon) === null || _config$android4$adap === void 0 ? void 0 : _config$android4$adap.foregroundImage) !== null && _config$android$adapt !== void 0 ? _config$android$adapt : null, 140 backgroundColor: (_config$android$adapt2 = (_config$android5 = config.android) === null || _config$android5 === void 0 ? void 0 : (_config$android5$adap = _config$android5.adaptiveIcon) === null || _config$android5$adap === void 0 ? void 0 : _config$android5$adap.backgroundColor) !== null && _config$android$adapt2 !== void 0 ? _config$android$adapt2 : null, 141 backgroundImage: (_config$android$adapt3 = (_config$android6 = config.android) === null || _config$android6 === void 0 ? void 0 : (_config$android6$adap = _config$android6.adaptiveIcon) === null || _config$android6$adap === void 0 ? void 0 : _config$android6$adap.backgroundImage) !== null && _config$android$adapt3 !== void 0 ? _config$android$adapt3 : null, 142 monochromeImage: (_config$android$adapt4 = (_config$android7 = config.android) === null || _config$android7 === void 0 ? void 0 : (_config$android7$adap = _config$android7.adaptiveIcon) === null || _config$android7$adap === void 0 ? void 0 : _config$android7$adap.monochromeImage) !== null && _config$android$adapt4 !== void 0 ? _config$android$adapt4 : null 143 }; 144} 145 146/** 147 * Resizes the user-provided icon to create a set of legacy icon files in 148 * their respective "mipmap" directories for <= Android 7, and creates a set of adaptive 149 * icon files for > Android 7 from the adaptive icon files (if provided). 150 */ 151async function setIconAsync(projectRoot, { 152 icon, 153 backgroundColor, 154 backgroundImage, 155 monochromeImage, 156 isAdaptive 157}) { 158 if (!icon) { 159 return null; 160 } 161 await configureLegacyIconAsync(projectRoot, icon, backgroundImage, backgroundColor); 162 if (isAdaptive) { 163 await generateRoundIconAsync(projectRoot, icon, backgroundImage, backgroundColor); 164 } else { 165 await deleteIconNamedAsync(projectRoot, IC_LAUNCHER_ROUND_PNG); 166 } 167 await configureAdaptiveIconAsync(projectRoot, icon, backgroundImage, monochromeImage, isAdaptive); 168 return true; 169} 170 171/** 172 * Configures legacy icon files to be used on Android 7 and earlier. If adaptive icon configuration 173 * was provided, we create a pseudo-adaptive icon by layering the provided files (or background 174 * color if no backgroundImage is provided. If no backgroundImage and no backgroundColor are provided, 175 * the background is set to transparent.) 176 */ 177async function configureLegacyIconAsync(projectRoot, icon, backgroundImage, backgroundColor) { 178 return generateMultiLayerImageAsync(projectRoot, { 179 icon, 180 backgroundImage, 181 backgroundColor, 182 outputImageFileName: IC_LAUNCHER_PNG, 183 imageCacheFolder: 'android-standard-square', 184 backgroundImageCacheFolder: 'android-standard-square-background' 185 }); 186} 187async function generateRoundIconAsync(projectRoot, icon, backgroundImage, backgroundColor) { 188 return generateMultiLayerImageAsync(projectRoot, { 189 icon, 190 borderRadiusRatio: 0.5, 191 outputImageFileName: IC_LAUNCHER_ROUND_PNG, 192 backgroundImage, 193 backgroundColor, 194 imageCacheFolder: 'android-standard-circle', 195 backgroundImageCacheFolder: 'android-standard-round-background' 196 }); 197} 198 199/** 200 * Configures adaptive icon files to be used on Android 8 and up. A foreground image must be provided, 201 * and will have a transparent background unless: 202 * - A backgroundImage is provided, or 203 * - A backgroundColor was specified 204 */ 205async function configureAdaptiveIconAsync(projectRoot, foregroundImage, backgroundImage, monochromeImage, isAdaptive) { 206 if (monochromeImage) { 207 await generateMonochromeImageAsync(projectRoot, { 208 icon: monochromeImage, 209 imageCacheFolder: 'android-adaptive-monochrome', 210 outputImageFileName: IC_LAUNCHER_MONOCHROME_PNG 211 }); 212 } 213 await generateMultiLayerImageAsync(projectRoot, { 214 backgroundColor: 'transparent', 215 backgroundImage, 216 backgroundImageCacheFolder: 'android-adaptive-background', 217 outputImageFileName: IC_LAUNCHER_FOREGROUND_PNG, 218 icon: foregroundImage, 219 imageCacheFolder: 'android-adaptive-foreground', 220 backgroundImageFileName: IC_LAUNCHER_BACKGROUND_PNG 221 }); 222 223 // create ic_launcher.xml and ic_launcher_round.xml 224 const icLauncherXmlString = createAdaptiveIconXmlString(backgroundImage, monochromeImage); 225 await createAdaptiveIconXmlFiles(projectRoot, icLauncherXmlString, 226 // If the user only defined icon and not android.adaptiveIcon, then skip enabling the layering system 227 // this will scale the image down and present it uncropped. 228 isAdaptive); 229} 230function setBackgroundColor(backgroundColor, colors) { 231 return Colors.assignColorValue(colors, { 232 value: backgroundColor, 233 name: ICON_BACKGROUND 234 }); 235} 236const createAdaptiveIconXmlString = (backgroundImage, monochromeImage) => { 237 const background = backgroundImage ? `@mipmap/ic_launcher_background` : `@color/iconBackground`; 238 const iconElements = [`<background android:drawable="${background}"/>`, '<foreground android:drawable="@mipmap/ic_launcher_foreground"/>']; 239 if (monochromeImage) { 240 iconElements.push('<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>'); 241 } 242 return `<?xml version="1.0" encoding="utf-8"?> 243<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> 244 ${iconElements.join('\n ')} 245</adaptive-icon>`; 246}; 247exports.createAdaptiveIconXmlString = createAdaptiveIconXmlString; 248async function createAdaptiveIconXmlFiles(projectRoot, icLauncherXmlString, add) { 249 const anyDpiV26Directory = _path().default.resolve(projectRoot, ANDROID_RES_PATH, MIPMAP_ANYDPI_V26); 250 await _fsExtra().default.ensureDir(anyDpiV26Directory); 251 const launcherPath = _path().default.resolve(anyDpiV26Directory, IC_LAUNCHER_XML); 252 const launcherRoundPath = _path().default.resolve(anyDpiV26Directory, IC_LAUNCHER_ROUND_XML); 253 if (add) { 254 await Promise.all([_fsExtra().default.writeFile(launcherPath, icLauncherXmlString), _fsExtra().default.writeFile(launcherRoundPath, icLauncherXmlString)]); 255 } else { 256 // Remove the xml if the icon switches from adaptive to standard. 257 await Promise.all([launcherPath, launcherRoundPath].map(async path => { 258 if (_fsExtra().default.existsSync(path)) { 259 return _fsExtra().default.remove(path); 260 } 261 })); 262 } 263} 264async function generateMultiLayerImageAsync(projectRoot, { 265 icon, 266 backgroundColor, 267 backgroundImage, 268 imageCacheFolder, 269 backgroundImageCacheFolder, 270 borderRadiusRatio, 271 outputImageFileName, 272 backgroundImageFileName 273}) { 274 await iterateDpiValues(projectRoot, async ({ 275 dpiFolder, 276 scale 277 }) => { 278 let iconLayer = await generateIconAsync(projectRoot, { 279 cacheType: imageCacheFolder, 280 src: icon, 281 scale, 282 // backgroundImage overrides backgroundColor 283 backgroundColor: backgroundImage ? 'transparent' : backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : 'transparent', 284 borderRadiusRatio 285 }); 286 if (backgroundImage) { 287 const backgroundLayer = await generateIconAsync(projectRoot, { 288 cacheType: backgroundImageCacheFolder, 289 src: backgroundImage, 290 scale, 291 backgroundColor: 'transparent', 292 borderRadiusRatio 293 }); 294 if (backgroundImageFileName) { 295 await _fsExtra().default.writeFile(_path().default.resolve(dpiFolder, backgroundImageFileName), backgroundLayer); 296 } else { 297 iconLayer = await (0, _imageUtils().compositeImagesAsync)({ 298 foreground: iconLayer, 299 background: backgroundLayer 300 }); 301 } 302 } else if (backgroundImageFileName) { 303 // Remove any instances of ic_launcher_background.png that are there from previous icons 304 await deleteIconNamedAsync(projectRoot, backgroundImageFileName); 305 } 306 await _fsExtra().default.ensureDir(dpiFolder); 307 await _fsExtra().default.writeFile(_path().default.resolve(dpiFolder, outputImageFileName), iconLayer); 308 }); 309} 310async function generateMonochromeImageAsync(projectRoot, { 311 icon, 312 imageCacheFolder, 313 outputImageFileName 314}) { 315 await iterateDpiValues(projectRoot, async ({ 316 dpiFolder, 317 scale 318 }) => { 319 const monochromeIcon = await generateIconAsync(projectRoot, { 320 cacheType: imageCacheFolder, 321 src: icon, 322 scale, 323 backgroundColor: 'transparent' 324 }); 325 await _fsExtra().default.ensureDir(dpiFolder); 326 await _fsExtra().default.writeFile(_path().default.resolve(dpiFolder, outputImageFileName), monochromeIcon); 327 }); 328} 329function iterateDpiValues(projectRoot, callback) { 330 return Promise.all(Object.values(dpiValues).map(value => callback({ 331 dpiFolder: _path().default.resolve(projectRoot, ANDROID_RES_PATH, value.folderName), 332 ...value 333 }))); 334} 335async function deleteIconNamedAsync(projectRoot, name) { 336 return iterateDpiValues(projectRoot, ({ 337 dpiFolder 338 }) => { 339 return _fsExtra().default.remove(_path().default.resolve(dpiFolder, name)); 340 }); 341} 342async function generateIconAsync(projectRoot, { 343 cacheType, 344 src, 345 scale, 346 backgroundColor, 347 borderRadiusRatio 348}) { 349 const iconSizePx = BASELINE_PIXEL_SIZE * scale; 350 return (await (0, _imageUtils().generateImageAsync)({ 351 projectRoot, 352 cacheType 353 }, { 354 src, 355 width: iconSizePx, 356 height: iconSizePx, 357 resizeMode: 'cover', 358 backgroundColor, 359 borderRadius: borderRadiusRatio ? iconSizePx * borderRadiusRatio : undefined 360 })).source; 361} 362//# sourceMappingURL=withAndroidIcons.js.map