mirror of
https://github.com/expo/config-plugins.git
synced 2026-11-13 20:28:07 -10:00
fix(ios-stickers): fix app icon generation (#82)
This commit is contained in:
1 parent
6c898afd0e
commit
190c489b76
13 files changed
+172
-161
No files matched your search
@@ -0,0 +1,8 @@
|
||||
export const generateImageAsync = jest.fn(async (input: any, { src }) => {
|
||||
const fs = require("fs");
|
||||
return { source: fs.readFileSync(src) };
|
||||
});
|
||||
|
||||
export const compositeImagesAsync = jest.fn(async ({ foreground }) => {
|
||||
return foreground;
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
import { fs } from 'memfs';
|
||||
module.exports = fs;
|
||||
+3
-3
@@ -8,7 +8,7 @@ const config_plugins_1 = require("@expo/config-plugins");
|
||||
const image_utils_1 = require("@expo/image-utils");
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const withStickerIcon_1 = require("./withStickerIcon");
|
||||
const generateImessageIconsAsync_1 = require("./generateImessageIconsAsync");
|
||||
const withStickerXcodeTarget_1 = require("./withStickerXcodeTarget");
|
||||
const STICKERS_ROOT_PATH = "Stickers.xcassets";
|
||||
const IMESSAGE_APP_ICON_PATH = "Stickers.xcassets/iMessage App Icon.stickersiconset";
|
||||
@@ -70,13 +70,13 @@ const withStickerAssets = (config, { stickers, icon, size }) => {
|
||||
const iMessageAppIconsPath = path_1.default.join(stickerRootPath, IMESSAGE_APP_ICON_PATH);
|
||||
// Only generate icons if an icon is defined
|
||||
const imessageIconContents = icon
|
||||
? await withStickerIcon_1.generateImessageIconsAsync(config.modRequest.projectRoot, icon, iMessageAppIconsPath)
|
||||
? await generateImessageIconsAsync_1.generateImessageIconsAsync(config.modRequest.projectRoot, icon, iMessageAppIconsPath)
|
||||
: [];
|
||||
const iMessageAppIconContentsJsonPath = path_1.default.join(iMessageAppIconsPath, "Contents.json");
|
||||
fs_1.default.mkdirSync(iMessageAppIconsPath, {
|
||||
recursive: true,
|
||||
});
|
||||
fs_1.default.writeFileSync(iMessageAppIconContentsJsonPath, JSON.stringify({ info: defaultInfo, images: imessageIconContents }, null, 2));
|
||||
fs_1.default.writeFileSync(iMessageAppIconContentsJsonPath, JSON.stringify({ images: imessageIconContents, info: defaultInfo }, null, 2));
|
||||
const stickerPackContentsPath = path_1.default.join(stickerRootPath, STICKER_PACK_PATH);
|
||||
const stickersContents = {
|
||||
info: defaultInfo,
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
export declare type ContentsJsonImageIdiom = "iphone" | "ipad" | "ios-marketing" | "universal";
|
||||
export declare type ContentsJsonImageAppearance = {
|
||||
appearance: "luminosity";
|
||||
value: "dark";
|
||||
};
|
||||
export declare type ContentsJsonImageScale = "1x" | "2x" | "3x";
|
||||
export interface ContentsJsonImage {
|
||||
appearances?: ContentsJsonImageAppearance[];
|
||||
idiom: ContentsJsonImageIdiom;
|
||||
size?: string;
|
||||
scale: ContentsJsonImageScale;
|
||||
filename?: string;
|
||||
platform?: string;
|
||||
}
|
||||
export declare function generateImessageIconsAsync(projectRoot: string, icon: string, iconsPath: string): Promise<ContentsJsonImage[]>;
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.generateImessageIconsAsync = void 0;
|
||||
const image_utils_1 = require("@expo/image-utils");
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = require("path");
|
||||
const IMAGE_CACHE_NAME = "ios-stickers-icons";
|
||||
// Hard-coding seemed like the clearest and safest way to implement the sizes.
|
||||
const ICON_CONTENTS = [
|
||||
{
|
||||
idiom: "iphone",
|
||||
sizes: [
|
||||
{
|
||||
size: 29,
|
||||
scales: [2, 3],
|
||||
},
|
||||
{
|
||||
size: [60, 45],
|
||||
scales: [2, 3],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
idiom: "ipad",
|
||||
sizes: [
|
||||
{
|
||||
size: 29,
|
||||
scales: [2],
|
||||
},
|
||||
{
|
||||
size: [67, 50],
|
||||
scales: [2],
|
||||
},
|
||||
{
|
||||
size: [74, 55],
|
||||
scales: [2],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
idiom: "universal",
|
||||
sizes: [
|
||||
{
|
||||
platform: "ios",
|
||||
size: [27, 20],
|
||||
scales: [2, 3],
|
||||
},
|
||||
{
|
||||
platform: "ios",
|
||||
size: [32, 24],
|
||||
scales: [2, 3],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
idiom: "ios-marketing",
|
||||
sizes: [
|
||||
{
|
||||
platform: "ios",
|
||||
size: 1024,
|
||||
scales: [1],
|
||||
},
|
||||
{
|
||||
size: [1024, 768],
|
||||
scales: [1],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
async function generateImessageIconsAsync(projectRoot, icon, iconsPath) {
|
||||
// Ensure the Images.xcassets/AppIcon.appiconset path exists
|
||||
fs_1.default.mkdirSync(iconsPath, { recursive: true });
|
||||
// Store the image JSON data for assigning via the Contents.json
|
||||
const imagesJson = [];
|
||||
// keep track of icons that have been generated so we can reuse them in the Contents.json
|
||||
const generatedIcons = {};
|
||||
for (const platform of ICON_CONTENTS) {
|
||||
// const isMarketing = platform.idiom === 'ios-marketing';
|
||||
for (const { size, scales, ...rest } of platform.sizes) {
|
||||
for (const scale of scales) {
|
||||
// The marketing icon is special because it makes no sense.
|
||||
const filename = getAppleIconName(size, scale, platform.idiom);
|
||||
// Only create an image that hasn't already been generated.
|
||||
const [width, height] = Array.isArray(size) ? size : [size, size];
|
||||
if (!(filename in generatedIcons)) {
|
||||
const iconWSizePx = width * scale;
|
||||
const iconHSizePx = height * scale;
|
||||
// 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.
|
||||
const { source } = await image_utils_1.generateImageAsync({ projectRoot, cacheType: IMAGE_CACHE_NAME }, {
|
||||
src: icon,
|
||||
name: filename,
|
||||
width: iconWSizePx,
|
||||
height: iconHSizePx,
|
||||
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",
|
||||
});
|
||||
// Write image buffer to the file system.
|
||||
const assetPath = path_1.join(iconsPath, filename);
|
||||
await fs_1.default.promises.writeFile(assetPath, source);
|
||||
// Save a reference to the generated image so we don't create a duplicate.
|
||||
generatedIcons[filename] = true;
|
||||
}
|
||||
imagesJson.push({
|
||||
idiom: platform.idiom,
|
||||
size: `${width}x${height}`,
|
||||
// @ts-ignore: template types not supported in TS yet
|
||||
scale: `${scale}x`,
|
||||
filename,
|
||||
});
|
||||
if (rest.platform) {
|
||||
imagesJson[imagesJson.length - 1].platform = rest.platform;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return imagesJson;
|
||||
}
|
||||
exports.generateImessageIconsAsync = generateImessageIconsAsync;
|
||||
function getAppleIconName(size, scale, idiom) {
|
||||
const [width, height] = Array.isArray(size) ? size : [size, size];
|
||||
return `App-Icon-${idiom}-${width}x${height}@${scale}x.png`;
|
||||
}
|
||||
@@ -1 +1,12 @@
|
||||
module.exports = require('expo-module-scripts/jest-preset-plugin');
|
||||
const path = require("path");
|
||||
|
||||
const roots = ["__mocks__", "src"];
|
||||
|
||||
module.exports = {
|
||||
...require("expo-module-scripts/jest-preset-plugin"),
|
||||
rootDir: path.resolve(__dirname),
|
||||
displayName: require("./package").name,
|
||||
roots,
|
||||
setupFiles: ["<rootDir>/jest/setup.js"],
|
||||
clearMocks: true,
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
jest.mock("@expo/image-utils");
|
||||
jest.mock("fs");
|
||||
@@ -5,6 +5,9 @@
|
||||
"main": "build/withStickerPack.js",
|
||||
"types": "build/withStickerPack.d.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/expo/config-plugins.git",
|
||||
@@ -30,6 +33,7 @@
|
||||
"xcode": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expo-module-scripts": "^2.0.0"
|
||||
"expo-module-scripts": "^2.0.0",
|
||||
"memfs": "^2.15.5"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 80 B |
@@ -0,0 +1,111 @@
|
||||
import { generateImageAsync } from "@expo/image-utils";
|
||||
import fs from "fs";
|
||||
import { vol } from "memfs";
|
||||
import path from "path";
|
||||
|
||||
import { generateImessageIconsAsync } from "../generateImessageIconsAsync";
|
||||
|
||||
const fsReal = jest.requireActual("fs") as typeof fs;
|
||||
|
||||
describe(generateImessageIconsAsync, () => {
|
||||
it(`should generate imessage icons`, async () => {
|
||||
vol.fromJSON({}, "/");
|
||||
await fs.promises.writeFile(
|
||||
"/icon.png",
|
||||
await fsReal.promises.readFile(path.join(__dirname, "fixtures/icon.png"))
|
||||
);
|
||||
const contents = await generateImessageIconsAsync(
|
||||
"/",
|
||||
"/icon.png",
|
||||
"/output"
|
||||
);
|
||||
|
||||
expect(generateImageAsync).toHaveBeenCalledTimes(13);
|
||||
|
||||
expect(contents).toEqual([
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "iphone",
|
||||
scale: "2x",
|
||||
size: "29x29",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "iphone",
|
||||
scale: "3x",
|
||||
size: "29x29",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "iphone",
|
||||
scale: "2x",
|
||||
size: "60x45",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "iphone",
|
||||
scale: "3x",
|
||||
size: "60x45",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "ipad",
|
||||
scale: "2x",
|
||||
size: "29x29",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "ipad",
|
||||
scale: "2x",
|
||||
size: "67x50",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "ipad",
|
||||
scale: "2x",
|
||||
size: "74x55",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "universal",
|
||||
platform: "ios",
|
||||
scale: "2x",
|
||||
size: "27x20",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "universal",
|
||||
platform: "ios",
|
||||
scale: "3x",
|
||||
size: "27x20",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "universal",
|
||||
platform: "ios",
|
||||
scale: "2x",
|
||||
size: "32x24",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "universal",
|
||||
platform: "ios",
|
||||
scale: "3x",
|
||||
size: "32x24",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "ios-marketing",
|
||||
scale: "1x",
|
||||
size: "1024x1024",
|
||||
},
|
||||
{
|
||||
filename: "[email protected]",
|
||||
idiom: "ios-marketing",
|
||||
platform: "ios",
|
||||
scale: "1x",
|
||||
size: "1024x768",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
+9
-9
@@ -86,12 +86,11 @@ const ICON_CONTENTS: {
|
||||
idiom: "ios-marketing",
|
||||
sizes: [
|
||||
{
|
||||
platform: "ios",
|
||||
|
||||
size: 1024,
|
||||
scales: [1],
|
||||
},
|
||||
{
|
||||
platform: "ios",
|
||||
size: [1024, 768],
|
||||
scales: [1],
|
||||
},
|
||||
@@ -148,15 +147,16 @@ export async function generateImessageIconsAsync(
|
||||
// Save a reference to the generated image so we don't create a duplicate.
|
||||
generatedIcons[filename] = true;
|
||||
}
|
||||
imagesJson.push({
|
||||
idiom: platform.idiom as ContentsJsonImageIdiom,
|
||||
size: `${width}x${height}`,
|
||||
// @ts-ignore: template types not supported in TS yet
|
||||
scale: `${scale}x`,
|
||||
const imgJson: ContentsJsonImage = {
|
||||
filename,
|
||||
});
|
||||
idiom: platform.idiom as ContentsJsonImageIdiom,
|
||||
scale: `${scale}x`,
|
||||
size: `${width}x${height}`,
|
||||
};
|
||||
// This order closely matches Xcode formatting.
|
||||
imagesJson.push(imgJson);
|
||||
if (rest.platform) {
|
||||
imagesJson[imagesJson.length - 1].platform = rest.platform;
|
||||
imgJson.platform = rest.platform;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { generateImageAsync } from "@expo/image-utils";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { generateImessageIconsAsync } from "./withStickerIcon";
|
||||
import { generateImessageIconsAsync } from "./generateImessageIconsAsync";
|
||||
import { getProjectStickersName } from "./withStickerXcodeTarget";
|
||||
|
||||
const STICKERS_ROOT_PATH = "Stickers.xcassets";
|
||||
@@ -136,7 +136,7 @@ export const withStickerAssets: ConfigPlugin<{
|
||||
fs.writeFileSync(
|
||||
iMessageAppIconContentsJsonPath,
|
||||
JSON.stringify(
|
||||
{ info: defaultInfo, images: imessageIconContents },
|
||||
{ images: imessageIconContents, info: defaultInfo },
|
||||
null,
|
||||
2
|
||||
)
|
||||
|
||||
@@ -9060,6 +9060,11 @@ fast-diff@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||
|
||||
[email protected]:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-extend/-/fast-extend-0.0.2.tgz#f5ec42cf40b9460f521a6387dfb52deeed671dbd"
|
||||
integrity sha1-9exCz0C5Rg9SGmOH37Ut7u1nHb0=
|
||||
|
||||
fast-glob@^2.2.6:
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
|
||||
@@ -9532,6 +9537,11 @@ fs-minipass@^2.0.0, fs-minipass@^2.1.0:
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
|
||||
fs-monkey@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-0.3.3.tgz#7960bb2b1fa2653731b9d0e2e84812a7e8b3664a"
|
||||
integrity sha512-FNUvuTAJ3CqCQb5ELn+qCbGR/Zllhf2HtwsdAtBi59s1WeCjKMT81fHcSu7dwIskqGVK+MmOrb7VOBlq3/SItw==
|
||||
|
||||
fs-readdir-recursive@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
|
||||
@@ -13566,6 +13576,14 @@ [email protected]:
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
memfs@^2.15.5:
|
||||
version "2.17.1"
|
||||
resolved "https://registry.yarnpkg.com/memfs/-/memfs-2.17.1.tgz#bfcc11b6308214b4fb33eaca101132722d22e103"
|
||||
integrity sha512-4kAcqibPAd8DN8/UIW/6OwvbhNq6MqwsQ0pxjo+2ZmxPlQAbn9TAepnRpdeYfw4Qq0Y+QqwE6wngLUowo2fcjg==
|
||||
dependencies:
|
||||
fast-extend "0.0.2"
|
||||
fs-monkey "^0.3.3"
|
||||
|
||||
memory-cache@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/memory-cache/-/memory-cache-0.2.0.tgz#7890b01d52c00c8ebc9d533e1f8eb17e3034871a"
|
||||
|
||||
Reference in new issue
Block a user