/
withIosIcons.ts
166 lines (156 loc) · 4.49 KB
/
withIosIcons.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { ConfigPlugin, withDangerousMod } from "@expo/config-plugins";
import { generateImageAsync } from "@expo/image-utils";
import {
ContentsJson,
ContentsJsonImageIdiom,
writeContentsJsonAsync,
} from "@expo/prebuild-config/build/plugins/icons/AssetContents";
import * as fs from "fs-extra";
import { join } from "path";
export const withIosIcons: ConfigPlugin<{
projectName: string;
iconFilePath: string;
}> = (config, { projectName, iconFilePath }) => {
return withDangerousMod(config, [
"ios",
async (config) => {
await setIconsAsync(
iconFilePath,
config.modRequest.projectRoot,
join(config.modRequest.platformProjectRoot, projectName)
);
return config;
},
]);
};
const IMAGE_CACHE_NAME = "icons";
const IMAGESET_PATH = "Images.xcassets/AppIcon.appiconset";
// Hard-coding seemed like the clearest and safest way to implement the sizes.
export const ICON_CONTENTS: {
idiom: ContentsJsonImageIdiom;
sizes: { size: number; scales: (1 | 2 | 3)[] }[];
}[] = [
{
idiom: "iphone",
sizes: [
{
size: 20,
scales: [2, 3],
},
{
size: 29,
scales: [1, 2, 3],
},
{
size: 40,
scales: [2, 3],
},
{
size: 60,
scales: [2, 3],
},
// TODO: 76x76@2x seems unused now
// {
// size: 76,
// scales: [2],
// },
],
},
{
idiom: "ipad",
sizes: [
{
size: 20,
scales: [1, 2],
},
{
size: 29,
scales: [1, 2],
},
{
size: 40,
scales: [1, 2],
},
{
size: 76,
scales: [1, 2],
},
{
size: 83.5,
scales: [2],
},
],
},
{
idiom: "ios-marketing",
sizes: [
{
size: 1024,
scales: [1],
},
],
},
];
export async function setIconsAsync(
icon: string,
projectRoot: string,
iosNamedProjectRoot: string
) {
// Ensure the Images.xcassets/AppIcon.appiconset path exists
await fs.ensureDir(join(iosNamedProjectRoot, IMAGESET_PATH));
// Store the image JSON data for assigning via the Contents.json
const imagesJson: ContentsJson["images"] = [];
// keep track of icons that have been generated so we can reuse them in the Contents.json
const generatedIcons: Record<string, boolean> = {};
for (const platform of ICON_CONTENTS) {
const isMarketing = platform.idiom === "ios-marketing";
for (const { size, scales } of platform.sizes) {
for (const scale of scales) {
// The marketing icon is special because it makes no sense.
const filename = isMarketing
? "ItunesArtwork@2x.png"
: getAppleIconName(size, scale);
// Only create an image that hasn't already been generated.
if (!(filename in generatedIcons)) {
const iconSizePx = size * 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 generateImageAsync(
{ projectRoot, cacheType: IMAGE_CACHE_NAME },
{
src: icon,
name: filename,
width: iconSizePx,
height: iconSizePx,
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 = join(iosNamedProjectRoot, IMAGESET_PATH, filename);
await fs.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: `${size}x${size}`,
// @ts-ignore: template types not supported in TS yet
scale: `${scale}x`,
filename,
});
}
}
}
// Finally, write the Config.json
await writeContentsJsonAsync(join(iosNamedProjectRoot, IMAGESET_PATH), {
images: imagesJson,
});
}
function getAppleIconName(size: number, scale: number): string {
return `App-Icon-${size}x${size}@${scale}x.png`;
}