1"use strict"; 2Object.defineProperty(exports, "__esModule", { value: true }); 3exports.withNotificationsAndroid = exports.setNotificationSounds = exports.setNotificationIconColorAsync = exports.setNotificationIconAsync = exports.getNotificationColor = exports.getNotificationIcon = exports.withNotificationSounds = exports.withNotificationManifest = exports.withNotificationIconColor = exports.withNotificationIcons = exports.NOTIFICATION_ICON_COLOR_RESOURCE = exports.NOTIFICATION_ICON_COLOR = exports.NOTIFICATION_ICON_RESOURCE = exports.NOTIFICATION_ICON = exports.META_DATA_NOTIFICATION_ICON_COLOR = exports.META_DATA_NOTIFICATION_ICON = exports.dpiValues = exports.ANDROID_RES_PATH = void 0; 4const config_plugins_1 = require("@expo/config-plugins"); 5const image_utils_1 = require("@expo/image-utils"); 6const fs_1 = require("fs"); 7const path_1 = require("path"); 8const { buildResourceItem, readResourcesXMLAsync } = config_plugins_1.AndroidConfig.Resources; 9const { writeXMLAsync } = config_plugins_1.XML; 10const { Colors } = config_plugins_1.AndroidConfig; 11exports.ANDROID_RES_PATH = 'android/app/src/main/res/'; 12exports.dpiValues = { 13 mdpi: { folderName: 'mipmap-mdpi', scale: 1 }, 14 hdpi: { folderName: 'mipmap-hdpi', scale: 1.5 }, 15 xhdpi: { folderName: 'mipmap-xhdpi', scale: 2 }, 16 xxhdpi: { folderName: 'mipmap-xxhdpi', scale: 3 }, 17 xxxhdpi: { folderName: 'mipmap-xxxhdpi', scale: 4 }, 18}; 19const { addMetaDataItemToMainApplication, getMainApplicationOrThrow, removeMetaDataItemFromMainApplication, } = config_plugins_1.AndroidConfig.Manifest; 20const BASELINE_PIXEL_SIZE = 24; 21const ERROR_MSG_PREFIX = 'An error occurred while configuring Android notifications. '; 22exports.META_DATA_NOTIFICATION_ICON = 'expo.modules.notifications.default_notification_icon'; 23exports.META_DATA_NOTIFICATION_ICON_COLOR = 'expo.modules.notifications.default_notification_color'; 24exports.NOTIFICATION_ICON = 'notification_icon'; 25exports.NOTIFICATION_ICON_RESOURCE = `@drawable/${exports.NOTIFICATION_ICON}`; 26exports.NOTIFICATION_ICON_COLOR = 'notification_icon_color'; 27exports.NOTIFICATION_ICON_COLOR_RESOURCE = `@color/${exports.NOTIFICATION_ICON_COLOR}`; 28exports.withNotificationIcons = (config, { icon }) => { 29 // If no icon provided in the config plugin props, fallback to value from app.json 30 icon = icon || getNotificationIcon(config); 31 return config_plugins_1.withDangerousMod(config, [ 32 'android', 33 async (config) => { 34 await setNotificationIconAsync(config.modRequest.projectRoot, icon); 35 return config; 36 }, 37 ]); 38}; 39exports.withNotificationIconColor = (config, { color }) => { 40 // If no color provided in the config plugin props, fallback to value from app.json 41 color = color || getNotificationColor(config); 42 return config_plugins_1.withDangerousMod(config, [ 43 'android', 44 async (config) => { 45 await setNotificationIconColorAsync(config.modRequest.projectRoot, color); 46 return config; 47 }, 48 ]); 49}; 50exports.withNotificationManifest = (config, { icon, color }) => { 51 // If no icon or color provided in the config plugin props, fallback to value from app.json 52 icon = icon || getNotificationIcon(config); 53 color = color || getNotificationColor(config); 54 return config_plugins_1.withAndroidManifest(config, config => { 55 config.modResults = setNotificationConfig({ icon, color }, config.modResults); 56 return config; 57 }); 58}; 59exports.withNotificationSounds = (config, { sounds }) => { 60 return config_plugins_1.withDangerousMod(config, [ 61 'android', 62 config => { 63 setNotificationSounds(config.modRequest.projectRoot, sounds); 64 return config; 65 }, 66 ]); 67}; 68function getNotificationIcon(config) { 69 var _a; 70 return ((_a = config.notification) === null || _a === void 0 ? void 0 : _a.icon) || null; 71} 72exports.getNotificationIcon = getNotificationIcon; 73function getNotificationColor(config) { 74 var _a; 75 return ((_a = config.notification) === null || _a === void 0 ? void 0 : _a.color) || null; 76} 77exports.getNotificationColor = getNotificationColor; 78/** 79 * Applies notification icon configuration for expo-notifications 80 */ 81async function setNotificationIconAsync(projectRoot, icon) { 82 if (icon) { 83 await writeNotificationIconImageFilesAsync(icon, projectRoot); 84 } 85 else { 86 removeNotificationIconImageFiles(projectRoot); 87 } 88} 89exports.setNotificationIconAsync = setNotificationIconAsync; 90function setNotificationConfig(props, manifest) { 91 const mainApplication = getMainApplicationOrThrow(manifest); 92 if (props.icon) { 93 addMetaDataItemToMainApplication(mainApplication, exports.META_DATA_NOTIFICATION_ICON, exports.NOTIFICATION_ICON_RESOURCE, 'resource'); 94 } 95 else { 96 removeMetaDataItemFromMainApplication(mainApplication, exports.META_DATA_NOTIFICATION_ICON); 97 } 98 if (props.color) { 99 addMetaDataItemToMainApplication(mainApplication, exports.META_DATA_NOTIFICATION_ICON_COLOR, exports.NOTIFICATION_ICON_COLOR_RESOURCE, 'resource'); 100 } 101 else { 102 removeMetaDataItemFromMainApplication(mainApplication, exports.META_DATA_NOTIFICATION_ICON_COLOR); 103 } 104 return manifest; 105} 106async function setNotificationIconColorAsync(projectRoot, color) { 107 const colorsXmlPath = await Colors.getProjectColorsXMLPathAsync(projectRoot); 108 let colorsJson = await readResourcesXMLAsync({ path: colorsXmlPath }); 109 if (color) { 110 const colorItemToAdd = buildResourceItem({ name: exports.NOTIFICATION_ICON_COLOR, value: color }); 111 colorsJson = Colors.setColorItem(colorItemToAdd, colorsJson); 112 } 113 else { 114 colorsJson = Colors.removeColorItem(exports.NOTIFICATION_ICON_COLOR, colorsJson); 115 } 116 await writeXMLAsync({ path: colorsXmlPath, xml: colorsJson }); 117} 118exports.setNotificationIconColorAsync = setNotificationIconColorAsync; 119async function writeNotificationIconImageFilesAsync(icon, projectRoot) { 120 await Promise.all(Object.values(exports.dpiValues).map(async ({ folderName, scale }) => { 121 const drawableFolderName = folderName.replace('mipmap', 'drawable'); 122 const dpiFolderPath = path_1.resolve(projectRoot, exports.ANDROID_RES_PATH, drawableFolderName); 123 if (!fs_1.existsSync(dpiFolderPath)) { 124 fs_1.mkdirSync(dpiFolderPath, { recursive: true }); 125 } 126 const iconSizePx = BASELINE_PIXEL_SIZE * scale; 127 try { 128 const resizedIcon = (await image_utils_1.generateImageAsync({ projectRoot, cacheType: 'android-notification' }, { 129 src: icon, 130 width: iconSizePx, 131 height: iconSizePx, 132 resizeMode: 'cover', 133 backgroundColor: 'transparent', 134 })).source; 135 fs_1.writeFileSync(path_1.resolve(dpiFolderPath, exports.NOTIFICATION_ICON + '.png'), resizedIcon); 136 } 137 catch (e) { 138 throw new Error(ERROR_MSG_PREFIX + 'Encountered an issue resizing Android notification icon: ' + e); 139 } 140 })); 141} 142function removeNotificationIconImageFiles(projectRoot) { 143 Object.values(exports.dpiValues).forEach(async ({ folderName }) => { 144 const drawableFolderName = folderName.replace('mipmap', 'drawable'); 145 const dpiFolderPath = path_1.resolve(projectRoot, exports.ANDROID_RES_PATH, drawableFolderName); 146 fs_1.unlinkSync(path_1.resolve(dpiFolderPath, exports.NOTIFICATION_ICON + '.png')); 147 }); 148} 149/** 150 * Save sound files to `<project-root>/android/app/src/main/res/raw` 151 */ 152function setNotificationSounds(projectRoot, sounds) { 153 if (!Array.isArray(sounds)) { 154 throw new Error(ERROR_MSG_PREFIX + 155 `Must provide an array of sound files in your app config, found ${typeof sounds}.`); 156 } 157 for (const soundFileRelativePath of sounds) { 158 writeNotificationSoundFile(soundFileRelativePath, projectRoot); 159 } 160} 161exports.setNotificationSounds = setNotificationSounds; 162/** 163 * Copies the input file to the `<project-root>/android/app/src/main/res/raw` directory if 164 * there isn't already an existing file under that name. 165 */ 166function writeNotificationSoundFile(soundFileRelativePath, projectRoot) { 167 const rawResourcesPath = path_1.resolve(projectRoot, exports.ANDROID_RES_PATH, 'raw'); 168 const inputFilename = path_1.basename(soundFileRelativePath); 169 if (inputFilename) { 170 try { 171 const sourceFilepath = path_1.resolve(projectRoot, soundFileRelativePath); 172 const destinationFilepath = path_1.resolve(rawResourcesPath, inputFilename); 173 if (!fs_1.existsSync(rawResourcesPath)) { 174 fs_1.mkdirSync(rawResourcesPath, { recursive: true }); 175 } 176 fs_1.copyFileSync(sourceFilepath, destinationFilepath); 177 } 178 catch (e) { 179 throw new Error(ERROR_MSG_PREFIX + 'Encountered an issue copying Android notification sounds: ' + e); 180 } 181 } 182} 183exports.withNotificationsAndroid = (config, { icon = null, color = null, sounds = [] }) => { 184 config = exports.withNotificationIconColor(config, { color }); 185 config = exports.withNotificationIcons(config, { icon }); 186 config = exports.withNotificationManifest(config, { icon, color }); 187 config = exports.withNotificationSounds(config, { sounds }); 188 return config; 189}; 190