271 lines
9.1 KiB
JavaScript
271 lines
9.1 KiB
JavaScript
"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 _Target() {
|
|
const data = require("@expo/config-plugins/build/ios/Target");
|
|
_Target = 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 _AssetContents() {
|
|
const data = require("./AssetContents");
|
|
_AssetContents = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
const {
|
|
getProjectName
|
|
} = _configPlugins().IOSConfig.XcodeUtils;
|
|
const IMAGE_CACHE_NAME = 'icons';
|
|
const IMAGESET_PATH = 'Images.xcassets/AppIcon.appiconset';
|
|
const withIosIcons = config => {
|
|
config = (0, _configPlugins().withDangerousMod)(config, ['ios', async config => {
|
|
await setIconsAsync(config, config.modRequest.projectRoot);
|
|
return config;
|
|
}]);
|
|
config = (0, _configPlugins().withXcodeProject)(config, config => {
|
|
const icon = getIcons(config);
|
|
const projectName = config.modRequest.projectName;
|
|
if (icon && typeof icon === 'string' && _path().default.extname(icon) === '.icon' && projectName) {
|
|
const iconName = _path().default.basename(icon, '.icon');
|
|
setIconName(config.modResults, projectName, iconName);
|
|
addIconFileToProject(config.modResults, projectName, iconName);
|
|
}
|
|
return config;
|
|
});
|
|
return config;
|
|
};
|
|
exports.withIosIcons = withIosIcons;
|
|
function getIcons(config) {
|
|
const iosSpecificIcons = config.ios?.icon;
|
|
if (iosSpecificIcons) {
|
|
// For backwards compatibility, the icon can be a string
|
|
if (typeof iosSpecificIcons === 'string') {
|
|
return iosSpecificIcons || config.icon || null;
|
|
}
|
|
if (typeof iosSpecificIcons === 'object') {
|
|
const paths = [iosSpecificIcons.light, iosSpecificIcons.dark, iosSpecificIcons.tinted].filter(Boolean);
|
|
for (const iconPath of paths) {
|
|
if (typeof iconPath === 'string' && _path().default.extname(iconPath) === '.icon') {
|
|
_configPlugins().WarningAggregator.addWarningIOS('icon', `Liquid glass icons (.icon) should be provided as a string to the "ios.icon" property, not as an object. Found: "${iconPath}"`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// in iOS 18 introduced the ability to specify dark and tinted icons, which users can specify as an object
|
|
if (!iosSpecificIcons.light && !iosSpecificIcons.dark && !iosSpecificIcons.tinted) {
|
|
return config.icon || null;
|
|
}
|
|
return iosSpecificIcons;
|
|
}
|
|
|
|
// Top level icon property should not be used to specify a `.icon` folder
|
|
if (config.icon && typeof config.icon === 'string' && _path().default.extname(config.icon) === '.icon') {
|
|
_configPlugins().WarningAggregator.addWarningIOS('icon', `Liquid glass icons (.icon) should be provided via the "ios.icon" property, not the root "icon" property. Found: "${config.icon}"`);
|
|
}
|
|
if (config.icon) {
|
|
return config.icon;
|
|
}
|
|
return null;
|
|
}
|
|
async function setIconsAsync(config, projectRoot) {
|
|
const icon = getIcons(config);
|
|
if (!icon || typeof icon === 'string' && !icon || typeof icon === 'object' && !icon?.light && !icon?.dark && !icon?.tinted) {
|
|
_configPlugins().WarningAggregator.addWarningIOS('icon', 'No icon is defined in the Expo config.');
|
|
}
|
|
|
|
// Something like projectRoot/ios/MyApp/
|
|
const iosNamedProjectRoot = getIosNamedProjectPath(projectRoot);
|
|
if (typeof icon === 'string' && _path().default.extname(icon) === '.icon') {
|
|
return await addLiquidGlassIcon(icon, projectRoot, iosNamedProjectRoot);
|
|
}
|
|
|
|
// Ensure the Images.xcassets/AppIcon.appiconset path exists
|
|
await _fs().default.promises.mkdir(_path().default.join(iosNamedProjectRoot, IMAGESET_PATH), {
|
|
recursive: true
|
|
});
|
|
const imagesJson = [];
|
|
const baseIconPath = typeof icon === 'object' ? icon?.light || icon?.dark || icon?.tinted : icon;
|
|
|
|
// Store the image JSON data for assigning via the Contents.json
|
|
const baseIcon = await generateUniversalIconAsync(projectRoot, {
|
|
icon: baseIconPath,
|
|
cacheKey: 'universal-icon',
|
|
iosNamedProjectRoot,
|
|
platform: 'ios'
|
|
});
|
|
imagesJson.push(baseIcon);
|
|
if (typeof icon === 'object') {
|
|
if (icon?.dark) {
|
|
const darkIcon = await generateUniversalIconAsync(projectRoot, {
|
|
icon: icon.dark,
|
|
cacheKey: 'universal-icon-dark',
|
|
iosNamedProjectRoot,
|
|
platform: 'ios',
|
|
appearance: 'dark'
|
|
});
|
|
imagesJson.push(darkIcon);
|
|
}
|
|
if (icon?.tinted) {
|
|
const tintedIcon = await generateUniversalIconAsync(projectRoot, {
|
|
icon: icon.tinted,
|
|
cacheKey: 'universal-icon-tinted',
|
|
iosNamedProjectRoot,
|
|
platform: 'ios',
|
|
appearance: 'tinted'
|
|
});
|
|
imagesJson.push(tintedIcon);
|
|
}
|
|
}
|
|
|
|
// Finally, write the Contents.json
|
|
await (0, _AssetContents().writeContentsJsonAsync)(_path().default.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 _path().default.join(projectRoot, 'ios', projectName);
|
|
}
|
|
function getAppleIconName(size, scale, appearance) {
|
|
let name = 'App-Icon';
|
|
if (appearance) {
|
|
name = `${name}-${appearance}`;
|
|
}
|
|
name = `${name}-${size}x${size}@${scale}x.png`;
|
|
return name;
|
|
}
|
|
async function generateUniversalIconAsync(projectRoot, {
|
|
icon,
|
|
cacheKey,
|
|
iosNamedProjectRoot,
|
|
platform,
|
|
appearance
|
|
}) {
|
|
const size = 1024;
|
|
const filename = getAppleIconName(size, 1, appearance);
|
|
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,
|
|
// Transparency needs to be preserved in dark variant, but can safely be removed in "light" and "tinted" variants.
|
|
removeTransparency: appearance !== 'dark',
|
|
// 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. (for "any" and "tinted" variants)
|
|
// TODO: Maybe use a more adaptive option based on the icon color?
|
|
backgroundColor: appearance !== 'dark' ? '#ffffff' : undefined
|
|
})).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 = _path().default.join(iosNamedProjectRoot, IMAGESET_PATH, filename);
|
|
await _fs().default.promises.writeFile(assetPath, source);
|
|
return {
|
|
filename,
|
|
idiom: 'universal',
|
|
platform,
|
|
size: `${size}x${size}`,
|
|
...(appearance ? {
|
|
appearances: [{
|
|
appearance: 'luminosity',
|
|
value: appearance
|
|
}]
|
|
} : {})
|
|
};
|
|
}
|
|
async function addLiquidGlassIcon(iconPath, projectRoot, iosNamedProjectRoot) {
|
|
const iconName = _path().default.basename(iconPath, '.icon');
|
|
const sourceIconPath = _path().default.join(projectRoot, iconPath);
|
|
const targetIconPath = _path().default.join(iosNamedProjectRoot, `${iconName}.icon`);
|
|
if (!_fs().default.existsSync(sourceIconPath)) {
|
|
_configPlugins().WarningAggregator.addWarningIOS('icon', `Liquid glass icon file not found at path: ${iconPath}`);
|
|
return;
|
|
}
|
|
await _fs().default.promises.cp(sourceIconPath, targetIconPath, {
|
|
recursive: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adds the .icons name to the project
|
|
*/
|
|
function setIconName(project, projectName, iconName) {
|
|
const [, target] = (0, _Target().findNativeTargetByName)(project, projectName);
|
|
const configurations = _configPlugins().IOSConfig.XcodeUtils.getBuildConfigurationsForListId(project, target.buildConfigurationList);
|
|
for (const [, config] of configurations) {
|
|
if (config?.buildSettings) {
|
|
config.buildSettings.ASSETCATALOG_COMPILER_APPICON_NAME = iconName;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the .icon file to the project
|
|
*/
|
|
function addIconFileToProject(project, projectName, iconName) {
|
|
const iconPath = `${iconName}.icon`;
|
|
_configPlugins().IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
filepath: `${projectName}/${iconPath}`,
|
|
groupName: projectName,
|
|
project,
|
|
isBuildFile: true,
|
|
verbose: true
|
|
});
|
|
}
|
|
//# sourceMappingURL=withIosIcons.js.map
|