react-native-bootsplash 6.3.11 → 7.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -41
- package/android/build.gradle +10 -17
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashModuleImpl.kt +2 -2
- package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashPackage.kt +0 -1
- package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashQueue.kt +1 -1
- package/android/src/main/res/drawable/compat_splash_screen.xml +11 -6
- package/android/src/main/res/drawable/compat_splash_screen_oneui_4.xml +13 -6
- package/android/src/main/res/values/colors.xml +1 -3
- package/android/src/main/res/values/public.xml +0 -2
- package/android/src/main/res/values/styles.xml +13 -19
- package/android/src/main/res/values-night/styles.xml +1 -1
- package/android/src/main/res/values-v27/colors.xml +4 -0
- package/android/src/main/res/values-v27/styles.xml +1 -6
- package/android/src/main/res/values-v29/styles.xml +1 -11
- package/android/src/main/res/values-v30/styles.xml +1 -9
- package/android/src/main/res/values-v31/styles.xml +1 -1
- package/android/src/newarch/com/zoontek/rnbootsplash/RNBootSplashModule.kt +2 -2
- package/android/src/oldarch/com/zoontek/rnbootsplash/RNBootSplashModule.kt +3 -3
- package/app.plugin.js +1 -1
- package/cli.js +2 -11
- package/dist/commonjs/addon/index.js +49 -49
- package/dist/commonjs/addon/index.js.map +1 -1
- package/dist/commonjs/generate.js +811 -541
- package/dist/commonjs/generate.js.map +1 -1
- package/dist/commonjs/index.js +1 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/specs/NativeRNBootSplash.js.map +1 -0
- package/dist/commonjs/{NativeRNBootSplash.web.js → specs/NativeRNBootSplash.web.js} +1 -1
- package/dist/commonjs/specs/NativeRNBootSplash.web.js.map +1 -0
- package/dist/module/addon/index.js +49 -49
- package/dist/module/addon/index.js.map +1 -1
- package/dist/module/generate.js +807 -539
- package/dist/module/generate.js.map +1 -1
- package/dist/module/index.js +1 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/specs/NativeRNBootSplash.js.map +1 -0
- package/dist/module/{NativeRNBootSplash.web.js → specs/NativeRNBootSplash.web.js} +1 -1
- package/dist/module/specs/NativeRNBootSplash.web.js.map +1 -0
- package/dist/typescript/addon/index.d.ts +8 -2
- package/dist/typescript/addon/index.d.ts.map +1 -1
- package/dist/typescript/generate.d.ts +90 -35
- package/dist/typescript/generate.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +2 -2
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/{NativeRNBootSplash.d.ts → specs/NativeRNBootSplash.d.ts} +1 -1
- package/dist/typescript/specs/NativeRNBootSplash.d.ts.map +1 -0
- package/dist/typescript/{NativeRNBootSplash.web.d.ts → specs/NativeRNBootSplash.web.d.ts} +1 -1
- package/dist/typescript/specs/NativeRNBootSplash.web.d.ts.map +1 -0
- package/ios/RNBootSplash.mm +4 -10
- package/package.json +24 -29
- package/src/generate.ts +1048 -782
- package/src/index.ts +7 -7
- package/src/{NativeRNBootSplash.ts → specs/NativeRNBootSplash.ts} +1 -1
- package/src/{NativeRNBootSplash.web.ts → specs/NativeRNBootSplash.web.ts} +1 -1
- package/android/src/main/AndroidManifestNew.xml +0 -3
- package/android/src/main/res/drawable-v23/compat_splash_screen.xml +0 -19
- package/android/src/main/res/drawable-v23/compat_splash_screen_oneui_4.xml +0 -21
- package/android/src/main/res/values-v23/styles.xml +0 -13
- package/dist/commonjs/NativeRNBootSplash.js.map +0 -1
- package/dist/commonjs/NativeRNBootSplash.web.js.map +0 -1
- package/dist/commonjs/expo.js +0 -348
- package/dist/commonjs/expo.js.map +0 -1
- package/dist/module/NativeRNBootSplash.js.map +0 -1
- package/dist/module/NativeRNBootSplash.web.js.map +0 -1
- package/dist/module/expo.js +0 -341
- package/dist/module/expo.js.map +0 -1
- package/dist/typescript/NativeRNBootSplash.d.ts.map +0 -1
- package/dist/typescript/NativeRNBootSplash.web.d.ts.map +0 -1
- package/dist/typescript/expo.d.ts +0 -11
- package/dist/typescript/expo.d.ts.map +0 -1
- package/react-native.config.js +0 -101
- package/src/expo.ts +0 -439
- /package/dist/commonjs/{NativeRNBootSplash.js → specs/NativeRNBootSplash.js} +0 -0
- /package/dist/module/{NativeRNBootSplash.js → specs/NativeRNBootSplash.js} +0 -0
package/src/generate.ts
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
import * as Expo from "@expo/config-plugins";
|
|
2
|
-
import
|
|
2
|
+
import { assignColorValue } from "@expo/config-plugins/build/android/Colors";
|
|
3
|
+
import { addImports } from "@expo/config-plugins/build/android/codeMod";
|
|
4
|
+
import { mergeContents } from "@expo/config-plugins/build/utils/generateCode";
|
|
5
|
+
import ExpoPlist from "@expo/plist";
|
|
3
6
|
import { projectConfig as getAndroidProjectConfig } from "@react-native-community/cli-config-android";
|
|
4
7
|
import { getProjectConfig as getAppleProjectConfig } from "@react-native-community/cli-config-apple";
|
|
5
8
|
import { findProjectRoot } from "@react-native-community/cli-tools";
|
|
6
9
|
import childProcess from "child_process";
|
|
7
10
|
import crypto from "crypto";
|
|
8
|
-
import detectIndent from "detect-indent";
|
|
11
|
+
import detectIndent, { type Indent } from "detect-indent";
|
|
9
12
|
import fs from "fs-extra";
|
|
10
13
|
import { HTMLElement, parse as parseHtml } from "node-html-parser";
|
|
11
14
|
import path from "path";
|
|
12
15
|
import pc from "picocolors";
|
|
13
|
-
import { Options as PrettierOptions } from "prettier";
|
|
16
|
+
import type { Options as PrettierOptions } from "prettier";
|
|
14
17
|
import * as htmlPlugin from "prettier/plugins/html";
|
|
15
18
|
import * as cssPlugin from "prettier/plugins/postcss";
|
|
16
19
|
import * as prettier from "prettier/standalone";
|
|
17
20
|
import semver from "semver";
|
|
18
|
-
import sharp, { Sharp } from "sharp";
|
|
21
|
+
import sharp, { type Sharp } from "sharp";
|
|
19
22
|
import { dedent } from "ts-dedent";
|
|
20
23
|
import util from "util";
|
|
21
|
-
import formatXml, { XMLFormatterOptions } from "xml-formatter";
|
|
22
|
-
import { Manifest } from ".";
|
|
24
|
+
import formatXml, { type XMLFormatterOptions } from "xml-formatter";
|
|
25
|
+
import type { Manifest } from ".";
|
|
23
26
|
|
|
24
|
-
const
|
|
25
|
-
const projectRoot = findProjectRoot(workingPath);
|
|
27
|
+
const PACKAGE_NAME = "react-native-bootsplash";
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
const ios = getIOSProjectConfig(projectRoot, {});
|
|
29
|
-
const android = getAndroidProjectConfig(projectRoot);
|
|
29
|
+
let isExpo = false;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
export const workingPath =
|
|
32
|
+
process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
|
|
33
|
+
|
|
34
|
+
const projectRoot = findProjectRoot(workingPath);
|
|
35
35
|
|
|
36
|
-
type
|
|
37
|
-
type Platforms = ("android" | "ios" | "web")[];
|
|
36
|
+
export type Platforms = ("android" | "ios" | "web")[];
|
|
38
37
|
|
|
39
38
|
export type RGBColor = {
|
|
40
39
|
R: string;
|
|
@@ -54,22 +53,30 @@ const exec = (cmd: string) =>
|
|
|
54
53
|
|
|
55
54
|
export const log = {
|
|
56
55
|
error: (text: string) => {
|
|
57
|
-
console.log(
|
|
56
|
+
console.log(
|
|
57
|
+
pc.red(isExpo ? `❌ [${PACKAGE_NAME}] ${text}` : `❌ ${text}`),
|
|
58
|
+
);
|
|
58
59
|
},
|
|
59
60
|
title: (emoji: string, text: string) => {
|
|
60
|
-
|
|
61
|
+
if (!isExpo) {
|
|
62
|
+
console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`);
|
|
63
|
+
}
|
|
61
64
|
},
|
|
62
65
|
warn: (text: string) => {
|
|
63
|
-
console.log(pc.yellow(`⚠️ ${text}`));
|
|
64
|
-
},
|
|
65
|
-
write: (filePath: string, dimensions?: { width: number; height: number }) => {
|
|
66
66
|
console.log(
|
|
67
|
-
|
|
68
|
-
(dimensions != null
|
|
69
|
-
? ` (${dimensions.width}x${dimensions.height})`
|
|
70
|
-
: ""),
|
|
67
|
+
pc.yellow(isExpo ? `⚠️ [${PACKAGE_NAME}] ${text}` : `⚠️ ${text}`),
|
|
71
68
|
);
|
|
72
69
|
},
|
|
70
|
+
write: (filePath: string, dimensions?: { width: number; height: number }) => {
|
|
71
|
+
if (!isExpo) {
|
|
72
|
+
console.log(
|
|
73
|
+
` ${path.relative(workingPath, filePath)}` +
|
|
74
|
+
(dimensions != null
|
|
75
|
+
? ` (${dimensions.width}x${dimensions.height})`
|
|
76
|
+
: ""),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
73
80
|
};
|
|
74
81
|
|
|
75
82
|
const parseColor = (value: string): Color => {
|
|
@@ -94,21 +101,13 @@ const parseColor = (value: string): Color => {
|
|
|
94
101
|
return { hex: hex.toLowerCase(), rgb };
|
|
95
102
|
};
|
|
96
103
|
|
|
97
|
-
const getStoryboard = ({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}: {
|
|
103
|
-
logoHeight: number;
|
|
104
|
-
logoWidth: number;
|
|
105
|
-
background: Color["rgb"];
|
|
106
|
-
fileNameSuffix: string;
|
|
107
|
-
}) => {
|
|
104
|
+
const getStoryboard = ({ props, extras }: { props: Props; extras: Extras }) => {
|
|
105
|
+
const { background, logo } = props;
|
|
106
|
+
const { fileNameSuffix, logoHeight } = extras;
|
|
107
|
+
|
|
108
|
+
const { R, G, B } = background.rgb;
|
|
108
109
|
const frameWidth = 375;
|
|
109
110
|
const frameHeight = 667;
|
|
110
|
-
const logoX = (frameWidth - logoWidth) / 2;
|
|
111
|
-
const logoY = (frameHeight - logoHeight) / 2;
|
|
112
111
|
|
|
113
112
|
return dedent`
|
|
114
113
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -131,7 +130,7 @@ const getStoryboard = ({
|
|
|
131
130
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
132
131
|
<subviews>
|
|
133
132
|
<imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo-${fileNameSuffix}" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
|
|
134
|
-
<rect key="frame" x="${
|
|
133
|
+
<rect key="frame" x="${(frameWidth - logo.width) / 2}" y="${(frameHeight - logoHeight) / 2}" width="${logo.width}" height="${logoHeight}"/>
|
|
135
134
|
<accessibility key="accessibilityConfiguration">
|
|
136
135
|
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
|
|
137
136
|
</accessibility>
|
|
@@ -151,7 +150,7 @@ const getStoryboard = ({
|
|
|
151
150
|
</scene>
|
|
152
151
|
</scenes>
|
|
153
152
|
<resources>
|
|
154
|
-
<image name="BootSplashLogo-${fileNameSuffix}" width="${
|
|
153
|
+
<image name="BootSplashLogo-${fileNameSuffix}" width="${logo.width}" height="${logoHeight}"/>
|
|
155
154
|
<namedColor name="BootSplashBackground-${fileNameSuffix}">
|
|
156
155
|
<color red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
157
156
|
</namedColor>
|
|
@@ -171,18 +170,6 @@ export const hfs = {
|
|
|
171
170
|
rm: (path: string) => fs.rmSync(path, { force: true, recursive: true }),
|
|
172
171
|
text: (path: string) => fs.readFileSync(path, "utf-8"),
|
|
173
172
|
|
|
174
|
-
copy: (src: string, dest: string) => {
|
|
175
|
-
if (hfs.isDir(src) || !hfs.exists(dest)) {
|
|
176
|
-
return fs.copySync(src, dest, { overwrite: true });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const srcBuffer = fs.readFileSync(src);
|
|
180
|
-
const destBuffer = fs.readFileSync(dest);
|
|
181
|
-
|
|
182
|
-
if (!srcBuffer.equals(destBuffer)) {
|
|
183
|
-
return fs.copySync(src, dest, { overwrite: true });
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
173
|
ensureDir: (dir: string) => {
|
|
187
174
|
fs.mkdirSync(dir, { recursive: true });
|
|
188
175
|
},
|
|
@@ -192,65 +179,12 @@ export const hfs = {
|
|
|
192
179
|
},
|
|
193
180
|
};
|
|
194
181
|
|
|
195
|
-
// Adapted from https://github.com/square/find-yarn-workspace-root
|
|
196
|
-
const findUp = <T>(from: string, matcher: (dir: string) => T | undefined) => {
|
|
197
|
-
let previous: string | undefined;
|
|
198
|
-
let current = path.normalize(from);
|
|
199
|
-
|
|
200
|
-
do {
|
|
201
|
-
const found = matcher(current);
|
|
202
|
-
|
|
203
|
-
if (typeof found !== "undefined") {
|
|
204
|
-
return found;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
previous = current;
|
|
208
|
-
current = path.dirname(current);
|
|
209
|
-
} while (current !== previous);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
export const getExpoConfig = (from: string): { isExpo: boolean } => {
|
|
213
|
-
const hasDependency =
|
|
214
|
-
findUp(from, (dir) => {
|
|
215
|
-
const pkgPath = path.resolve(dir, "package.json");
|
|
216
|
-
|
|
217
|
-
if (fs.existsSync(pkgPath)) {
|
|
218
|
-
try {
|
|
219
|
-
const pkg = hfs.json(pkgPath) as PackageJson;
|
|
220
|
-
return pkg.dependencies?.expo != null;
|
|
221
|
-
} catch {} // eslint-disable-line no-empty
|
|
222
|
-
}
|
|
223
|
-
}) ?? false;
|
|
224
|
-
|
|
225
|
-
if (!hasDependency) {
|
|
226
|
-
return { isExpo: false };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const version = findUp(from, (dir) => {
|
|
230
|
-
const pkgPath = path.resolve(dir, "node_modules", "expo", "package.json");
|
|
231
|
-
|
|
232
|
-
if (fs.existsSync(pkgPath)) {
|
|
233
|
-
try {
|
|
234
|
-
const pkg = hfs.json(pkgPath) as PackageJson;
|
|
235
|
-
return pkg.version;
|
|
236
|
-
} catch {} // eslint-disable-line no-empty
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
if (version == null || semver.lt(version, "51.0.20")) {
|
|
241
|
-
log.error("Requires Expo 51.0.20 (or higher)");
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return { isExpo: true };
|
|
246
|
-
};
|
|
247
|
-
|
|
248
182
|
export const writeJson = (filePath: string, content: object) => {
|
|
249
183
|
hfs.write(filePath, JSON.stringify(content, null, 2));
|
|
250
184
|
log.write(filePath);
|
|
251
185
|
};
|
|
252
186
|
|
|
253
|
-
type FormatOptions = { indent?:
|
|
187
|
+
type FormatOptions = { indent?: Indent } & (
|
|
254
188
|
| {
|
|
255
189
|
formatter: "prettier";
|
|
256
190
|
selfClosingTags?: boolean;
|
|
@@ -321,30 +255,28 @@ export const writeXmlLike = async (
|
|
|
321
255
|
}
|
|
322
256
|
};
|
|
323
257
|
|
|
324
|
-
export
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
.map((file) => path.join(dir, file))
|
|
329
|
-
.flatMap((dir) =>
|
|
330
|
-
hfs
|
|
331
|
-
.readDir(dir)
|
|
332
|
-
.filter((file) => file.startsWith("BootSplash"))
|
|
333
|
-
.map((file) => path.join(dir, file)),
|
|
334
|
-
)
|
|
335
|
-
.forEach((file) => {
|
|
336
|
-
hfs.rm(file);
|
|
337
|
-
});
|
|
258
|
+
export type Asset = {
|
|
259
|
+
path: string;
|
|
260
|
+
image: Sharp;
|
|
261
|
+
width: number;
|
|
338
262
|
};
|
|
339
263
|
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
264
|
+
const getAssetBase64 = async (
|
|
265
|
+
name: string,
|
|
266
|
+
asset: Asset | undefined,
|
|
343
267
|
): Promise<string> => {
|
|
344
|
-
if (
|
|
268
|
+
if (asset == null) {
|
|
345
269
|
return "";
|
|
346
270
|
}
|
|
347
271
|
|
|
272
|
+
const { image, width } = asset;
|
|
273
|
+
const { format } = await image.metadata();
|
|
274
|
+
|
|
275
|
+
if (format !== "png" && format !== "svg") {
|
|
276
|
+
log.error(`${name} image file format (${format}) is not supported`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
|
|
348
280
|
const buffer = await image
|
|
349
281
|
.clone()
|
|
350
282
|
.resize(width)
|
|
@@ -354,115 +286,16 @@ const getImageBase64 = async (
|
|
|
354
286
|
return buffer.toString("base64");
|
|
355
287
|
};
|
|
356
288
|
|
|
357
|
-
const getFileNameSuffix = async ({
|
|
358
|
-
background,
|
|
359
|
-
brand,
|
|
360
|
-
brandWidth,
|
|
361
|
-
darkBackground,
|
|
362
|
-
darkBrand,
|
|
363
|
-
darkLogo,
|
|
364
|
-
logo,
|
|
365
|
-
logoWidth,
|
|
366
|
-
}: {
|
|
367
|
-
background: Color;
|
|
368
|
-
brand: Sharp | undefined;
|
|
369
|
-
brandWidth: number;
|
|
370
|
-
darkBackground: Color | undefined;
|
|
371
|
-
darkBrand: Sharp | undefined;
|
|
372
|
-
darkLogo: Sharp | undefined;
|
|
373
|
-
logo: Sharp;
|
|
374
|
-
logoWidth: number;
|
|
375
|
-
}) => {
|
|
376
|
-
const [logoHash, darkLogoHash, brandHash, darkBrandHash] = await Promise.all([
|
|
377
|
-
getImageBase64(logo, logoWidth),
|
|
378
|
-
getImageBase64(darkLogo, logoWidth),
|
|
379
|
-
getImageBase64(brand, brandWidth),
|
|
380
|
-
getImageBase64(darkBrand, brandWidth),
|
|
381
|
-
]);
|
|
382
|
-
|
|
383
|
-
const record: Record<string, string> = {
|
|
384
|
-
background: background.hex,
|
|
385
|
-
darkBackground: darkBackground?.hex ?? "",
|
|
386
|
-
logo: logoHash,
|
|
387
|
-
darkLogo: darkLogoHash,
|
|
388
|
-
brand: brandHash,
|
|
389
|
-
darkBrand: darkBrandHash,
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const stableKey = Object.keys(record)
|
|
393
|
-
.sort()
|
|
394
|
-
.map((key) => record[key])
|
|
395
|
-
.join();
|
|
396
|
-
|
|
397
|
-
return crypto
|
|
398
|
-
.createHash("shake256", { outputLength: 3 })
|
|
399
|
-
.update(stableKey)
|
|
400
|
-
.digest("hex")
|
|
401
|
-
.toLowerCase();
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
const ensureSupportedFormat = async (
|
|
405
|
-
name: string,
|
|
406
|
-
image: Sharp | undefined,
|
|
407
|
-
) => {
|
|
408
|
-
if (image == null) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const { format } = await image.metadata();
|
|
413
|
-
|
|
414
|
-
if (format !== "png" && format !== "svg") {
|
|
415
|
-
log.error(`${name} image file format (${format}) is not supported`);
|
|
416
|
-
process.exit(1);
|
|
417
|
-
}
|
|
418
|
-
};
|
|
419
|
-
|
|
420
289
|
const getAndroidOutputPath = ({
|
|
421
|
-
assetsOutputPath,
|
|
422
|
-
brandHeight,
|
|
423
|
-
brandWidth,
|
|
424
290
|
flavor,
|
|
425
|
-
isExpo,
|
|
426
|
-
logoHeight,
|
|
427
|
-
logoWidth,
|
|
428
291
|
platforms,
|
|
429
292
|
}: {
|
|
430
|
-
assetsOutputPath: string;
|
|
431
|
-
brandHeight: number;
|
|
432
|
-
brandWidth: number;
|
|
433
293
|
flavor: string;
|
|
434
|
-
isExpo: boolean;
|
|
435
|
-
logoHeight: number;
|
|
436
|
-
logoWidth: number;
|
|
437
294
|
platforms: Platforms;
|
|
438
295
|
}) => {
|
|
439
|
-
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
296
|
+
const android = getAndroidProjectConfig(projectRoot);
|
|
442
297
|
|
|
443
|
-
|
|
444
|
-
if (logoWidth > 192 || logoHeight > 192) {
|
|
445
|
-
return log.warn(
|
|
446
|
-
"Logo size exceeding 192x192dp will be cropped by Android. Skipping Android assets generation…",
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
if (brandWidth > 200 || brandHeight > 80) {
|
|
450
|
-
return log.warn(
|
|
451
|
-
"Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…",
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (logoWidth > 134 || logoHeight > 134) {
|
|
456
|
-
log.warn("Logo size exceeds 134x134dp. It might be cropped by Android.");
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return assetsOutputPath;
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
if (isExpo) {
|
|
463
|
-
return withSizeChecks(path.resolve(assetsOutputPath, "android"));
|
|
464
|
-
}
|
|
465
|
-
if (android == null) {
|
|
298
|
+
if (!platforms.includes("android") || android == null) {
|
|
466
299
|
return;
|
|
467
300
|
}
|
|
468
301
|
|
|
@@ -483,25 +316,13 @@ const getAndroidOutputPath = ({
|
|
|
483
316
|
);
|
|
484
317
|
}
|
|
485
318
|
|
|
486
|
-
return
|
|
319
|
+
return androidOutputPath;
|
|
487
320
|
};
|
|
488
321
|
|
|
489
|
-
const getIOSOutputPath = ({
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
platforms
|
|
493
|
-
}: {
|
|
494
|
-
assetsOutputPath: string;
|
|
495
|
-
isExpo: boolean;
|
|
496
|
-
platforms: Platforms;
|
|
497
|
-
}) => {
|
|
498
|
-
if (!platforms.includes("ios")) {
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (isExpo) {
|
|
502
|
-
return path.resolve(assetsOutputPath, "ios");
|
|
503
|
-
}
|
|
504
|
-
if (ios == null) {
|
|
322
|
+
const getIOSOutputPath = ({ platforms }: { platforms: Platforms }) => {
|
|
323
|
+
const ios = getAppleProjectConfig({ platformName: "ios" })(projectRoot, {});
|
|
324
|
+
|
|
325
|
+
if (!platforms.includes("ios") || ios == null) {
|
|
505
326
|
return;
|
|
506
327
|
}
|
|
507
328
|
if (ios.xcodeProject == null) {
|
|
@@ -524,12 +345,10 @@ const getIOSOutputPath = ({
|
|
|
524
345
|
return iosOutputPath;
|
|
525
346
|
};
|
|
526
347
|
|
|
527
|
-
const getHtmlTemplatePath =
|
|
528
|
-
isExpo,
|
|
348
|
+
const getHtmlTemplatePath = ({
|
|
529
349
|
html,
|
|
530
350
|
platforms,
|
|
531
351
|
}: {
|
|
532
|
-
isExpo: boolean;
|
|
533
352
|
html: string;
|
|
534
353
|
platforms: Platforms;
|
|
535
354
|
}) => {
|
|
@@ -537,24 +356,6 @@ const getHtmlTemplatePath = async ({
|
|
|
537
356
|
return;
|
|
538
357
|
}
|
|
539
358
|
|
|
540
|
-
if (isExpo) {
|
|
541
|
-
const htmlTemplatePath = path.resolve(workingPath, html);
|
|
542
|
-
|
|
543
|
-
const htmlTemplateRelativePath = path.relative(
|
|
544
|
-
workingPath,
|
|
545
|
-
htmlTemplatePath,
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
if (
|
|
549
|
-
htmlTemplateRelativePath === "public/index.html" &&
|
|
550
|
-
!hfs.exists(htmlTemplatePath)
|
|
551
|
-
) {
|
|
552
|
-
const cmd = `npx expo customize ${htmlTemplateRelativePath}`;
|
|
553
|
-
console.log(pc.dim(`Running ${cmd}`));
|
|
554
|
-
await exec(cmd);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
359
|
const htmlTemplatePath = path.resolve(workingPath, html);
|
|
559
360
|
|
|
560
361
|
if (!hfs.exists(htmlTemplatePath)) {
|
|
@@ -562,84 +363,51 @@ const getHtmlTemplatePath = async ({
|
|
|
562
363
|
`No ${path.relative(
|
|
563
364
|
workingPath,
|
|
564
365
|
htmlTemplatePath,
|
|
565
|
-
)}
|
|
366
|
+
)} found. Skipping HTML + CSS generation…`,
|
|
566
367
|
);
|
|
567
368
|
}
|
|
568
369
|
|
|
569
370
|
return htmlTemplatePath;
|
|
570
371
|
};
|
|
571
372
|
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
373
|
+
const getInfoPlistPath = ({
|
|
374
|
+
iosOutputPath,
|
|
375
|
+
plist,
|
|
376
|
+
}: {
|
|
377
|
+
iosOutputPath: string;
|
|
378
|
+
plist: string | undefined;
|
|
379
|
+
}) => {
|
|
380
|
+
if (plist != null) {
|
|
381
|
+
const infoPlistPath = path.resolve(workingPath, plist);
|
|
382
|
+
|
|
383
|
+
if (!hfs.exists(infoPlistPath)) {
|
|
384
|
+
return log.warn(`No ${path.relative(workingPath, infoPlistPath)} found`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return infoPlistPath;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return path.resolve(iosOutputPath, "Info.plist");
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const getAssetHeight = (asset: Asset | undefined): Promise<number> => {
|
|
394
|
+
if (asset == null) {
|
|
577
395
|
return Promise.resolve(0);
|
|
578
396
|
}
|
|
579
397
|
|
|
580
|
-
return image
|
|
398
|
+
return asset.image
|
|
581
399
|
.clone()
|
|
582
|
-
.resize(width)
|
|
400
|
+
.resize(asset.width)
|
|
583
401
|
.toBuffer()
|
|
584
402
|
.then((buffer) => sharp(buffer).metadata())
|
|
585
403
|
.then(({ height = 0 }) => Math.round(height));
|
|
586
404
|
};
|
|
587
405
|
|
|
588
|
-
|
|
589
|
-
licenseKey: string;
|
|
590
|
-
isExpo: boolean;
|
|
591
|
-
fileNameSuffix: string;
|
|
592
|
-
|
|
593
|
-
androidOutputPath: string | void;
|
|
594
|
-
iosOutputPath: string | void;
|
|
595
|
-
htmlTemplatePath: string | void;
|
|
596
|
-
assetsOutputPath: string;
|
|
597
|
-
|
|
598
|
-
logoPath: string;
|
|
599
|
-
darkLogoPath: string | undefined;
|
|
600
|
-
brandPath: string | undefined;
|
|
601
|
-
darkBrandPath: string | undefined;
|
|
602
|
-
|
|
603
|
-
logoHeight: number;
|
|
604
|
-
logoWidth: number;
|
|
605
|
-
brandHeight: number;
|
|
606
|
-
brandWidth: number;
|
|
607
|
-
|
|
608
|
-
background: Color;
|
|
609
|
-
logo: Sharp;
|
|
610
|
-
brand: Sharp | undefined;
|
|
611
|
-
|
|
612
|
-
darkBackground: Color | undefined;
|
|
613
|
-
darkLogo: Sharp | undefined;
|
|
614
|
-
darkBrand: Sharp | undefined;
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
const requireAddon = ():
|
|
618
|
-
| { execute: (config: AddonConfig) => Promise<void> }
|
|
619
|
-
| undefined => {
|
|
620
|
-
try {
|
|
621
|
-
return require("./addon"); // eslint-disable-line
|
|
622
|
-
} catch {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
export const generate = async ({
|
|
628
|
-
projectType,
|
|
629
|
-
platforms,
|
|
630
|
-
html,
|
|
631
|
-
flavor,
|
|
632
|
-
licenseKey,
|
|
633
|
-
...args
|
|
634
|
-
}: {
|
|
406
|
+
type Config = {
|
|
635
407
|
logo: string;
|
|
636
|
-
projectType: ProjectType;
|
|
637
|
-
platforms: Platforms;
|
|
638
408
|
background: string;
|
|
639
409
|
logoWidth: number;
|
|
640
410
|
assetsOutput: string;
|
|
641
|
-
html: string;
|
|
642
|
-
flavor: string;
|
|
643
411
|
|
|
644
412
|
licenseKey?: string;
|
|
645
413
|
brand?: string;
|
|
@@ -647,44 +415,64 @@ export const generate = async ({
|
|
|
647
415
|
darkBackground?: string;
|
|
648
416
|
darkLogo?: string;
|
|
649
417
|
darkBrand?: string;
|
|
650
|
-
}) => {
|
|
651
|
-
const isExpo =
|
|
652
|
-
projectType === "expo" ||
|
|
653
|
-
(projectType === "detect" && getExpoConfig(workingPath).isExpo);
|
|
654
418
|
|
|
655
|
-
|
|
656
|
-
|
|
419
|
+
android?: {
|
|
420
|
+
darkContentBarsStyle?: boolean;
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const getProps = ({ android = {}, licenseKey, ...config }: Config) => {
|
|
425
|
+
if (semver.lt(process.versions.node, "20.0.0")) {
|
|
426
|
+
log.error("Requires Node 20 (or higher)");
|
|
657
427
|
process.exit(1);
|
|
658
428
|
}
|
|
659
429
|
|
|
660
|
-
const
|
|
430
|
+
const assetsOutputPath = path.resolve(workingPath, config.assetsOutput);
|
|
431
|
+
const logoPath = path.resolve(workingPath, config.logo);
|
|
661
432
|
|
|
662
433
|
const darkLogoPath =
|
|
663
|
-
|
|
664
|
-
? path.resolve(workingPath,
|
|
434
|
+
config.darkLogo != null
|
|
435
|
+
? path.resolve(workingPath, config.darkLogo)
|
|
665
436
|
: undefined;
|
|
666
437
|
|
|
667
438
|
const brandPath =
|
|
668
|
-
|
|
439
|
+
config.brand != null ? path.resolve(workingPath, config.brand) : undefined;
|
|
669
440
|
|
|
670
441
|
const darkBrandPath =
|
|
671
|
-
|
|
672
|
-
? path.resolve(workingPath,
|
|
442
|
+
config.darkBrand != null
|
|
443
|
+
? path.resolve(workingPath, config.darkBrand)
|
|
444
|
+
: undefined;
|
|
445
|
+
|
|
446
|
+
const logoWidth = config.logoWidth - (config.logoWidth % 2);
|
|
447
|
+
const brandWidth = config.brandWidth - (config.brandWidth % 2);
|
|
448
|
+
|
|
449
|
+
const logo: Asset = {
|
|
450
|
+
path: logoPath,
|
|
451
|
+
image: sharp(logoPath),
|
|
452
|
+
width: logoWidth,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const darkLogo: Asset | undefined =
|
|
456
|
+
darkLogoPath != null
|
|
457
|
+
? { path: darkLogoPath, image: sharp(darkLogoPath), width: logoWidth }
|
|
673
458
|
: undefined;
|
|
674
459
|
|
|
675
|
-
const
|
|
460
|
+
const brand: Asset | undefined =
|
|
461
|
+
brandPath != null
|
|
462
|
+
? { path: brandPath, image: sharp(brandPath), width: brandWidth }
|
|
463
|
+
: undefined;
|
|
676
464
|
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
465
|
+
const darkBrand: Asset | undefined =
|
|
466
|
+
darkBrandPath != null
|
|
467
|
+
? { path: darkBrandPath, image: sharp(darkBrandPath), width: brandWidth }
|
|
468
|
+
: undefined;
|
|
681
469
|
|
|
682
|
-
const background = parseColor(
|
|
683
|
-
const logoWidth = args.logoWidth - (args.logoWidth % 2);
|
|
684
|
-
const brandWidth = args.brandWidth - (args.brandWidth % 2);
|
|
470
|
+
const background = parseColor(config.background);
|
|
685
471
|
|
|
686
472
|
const darkBackground =
|
|
687
|
-
|
|
473
|
+
config.darkBackground != null
|
|
474
|
+
? parseColor(config.darkBackground)
|
|
475
|
+
: undefined;
|
|
688
476
|
|
|
689
477
|
const executeAddon =
|
|
690
478
|
brand != null ||
|
|
@@ -698,15 +486,21 @@ export const generate = async ({
|
|
|
698
486
|
);
|
|
699
487
|
}
|
|
700
488
|
|
|
489
|
+
const optionNames = {
|
|
490
|
+
brand: isExpo ? "brand" : "--brand",
|
|
491
|
+
darkBackground: isExpo ? "darkBackground" : "--dark-background",
|
|
492
|
+
darkLogo: isExpo ? "darkLogo" : "--dark-logo",
|
|
493
|
+
darkBrand: isExpo ? "darkBrand" : "--dark-brand",
|
|
494
|
+
};
|
|
495
|
+
|
|
701
496
|
if (licenseKey == null && executeAddon) {
|
|
702
497
|
const options = [
|
|
703
|
-
brand != null ?
|
|
704
|
-
darkBackground != null ?
|
|
705
|
-
darkLogo != null ?
|
|
706
|
-
darkBrand != null ?
|
|
498
|
+
brand != null ? optionNames.brand : "",
|
|
499
|
+
darkBackground != null ? optionNames.darkBackground : "",
|
|
500
|
+
darkLogo != null ? optionNames.darkLogo : "",
|
|
501
|
+
darkBrand != null ? optionNames.darkBrand : "",
|
|
707
502
|
]
|
|
708
503
|
.filter((option) => option !== "")
|
|
709
|
-
.map((option) => `--${option}`)
|
|
710
504
|
.join(", ");
|
|
711
505
|
|
|
712
506
|
log.error(`You need to specify a license key in order to use ${options}.`);
|
|
@@ -714,483 +508,394 @@ export const generate = async ({
|
|
|
714
508
|
}
|
|
715
509
|
|
|
716
510
|
if (brand == null && darkBrand != null) {
|
|
717
|
-
log.error(
|
|
511
|
+
log.error(
|
|
512
|
+
`${optionNames.darkBrand} option couldn't be used without ${optionNames.brand}.`,
|
|
513
|
+
);
|
|
514
|
+
|
|
718
515
|
process.exit(1);
|
|
719
516
|
}
|
|
720
517
|
|
|
721
|
-
|
|
722
|
-
await ensureSupportedFormat("Dark logo", darkLogo);
|
|
723
|
-
await ensureSupportedFormat("Brand", brand);
|
|
724
|
-
await ensureSupportedFormat("Dark brand", darkBrand);
|
|
725
|
-
|
|
726
|
-
const logoHeight = await getImageHeight(logo, logoWidth);
|
|
727
|
-
const brandHeight = await getImageHeight(brand, brandWidth);
|
|
728
|
-
|
|
729
|
-
if (logoWidth < args.logoWidth) {
|
|
518
|
+
if (logoWidth < config.logoWidth) {
|
|
730
519
|
log.warn(
|
|
731
520
|
`Logo width must be a multiple of 2. It has been rounded to ${logoWidth}dp.`,
|
|
732
521
|
);
|
|
733
522
|
}
|
|
734
|
-
if (brandWidth <
|
|
523
|
+
if (brandWidth < config.brandWidth) {
|
|
735
524
|
log.warn(
|
|
736
525
|
`Brand width must be a multiple of 2. It has been rounded to ${brandWidth}dp.`,
|
|
737
526
|
);
|
|
738
527
|
}
|
|
739
528
|
|
|
740
|
-
|
|
529
|
+
return {
|
|
530
|
+
android,
|
|
531
|
+
assetsOutputPath,
|
|
532
|
+
licenseKey,
|
|
533
|
+
executeAddon,
|
|
741
534
|
background,
|
|
742
|
-
brand,
|
|
743
|
-
brandWidth,
|
|
744
535
|
darkBackground,
|
|
745
|
-
darkBrand,
|
|
746
|
-
darkLogo,
|
|
747
536
|
logo,
|
|
748
|
-
|
|
749
|
-
|
|
537
|
+
darkLogo,
|
|
538
|
+
brand,
|
|
539
|
+
darkBrand,
|
|
540
|
+
};
|
|
541
|
+
};
|
|
750
542
|
|
|
751
|
-
|
|
752
|
-
assetsOutputPath,
|
|
753
|
-
brandHeight,
|
|
754
|
-
brandWidth,
|
|
755
|
-
flavor,
|
|
756
|
-
isExpo,
|
|
757
|
-
logoHeight,
|
|
758
|
-
logoWidth,
|
|
759
|
-
platforms,
|
|
760
|
-
});
|
|
543
|
+
export type Props = ReturnType<typeof getProps>;
|
|
761
544
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
545
|
+
export const getExtras = async ({
|
|
546
|
+
background,
|
|
547
|
+
darkBackground,
|
|
548
|
+
logo,
|
|
549
|
+
darkLogo,
|
|
550
|
+
brand,
|
|
551
|
+
darkBrand,
|
|
552
|
+
}: Props) => {
|
|
553
|
+
const [
|
|
554
|
+
logoHash,
|
|
555
|
+
darkLogoHash,
|
|
556
|
+
brandHash,
|
|
557
|
+
darkBrandHash,
|
|
767
558
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
559
|
+
logoHeight,
|
|
560
|
+
brandHeight,
|
|
561
|
+
] = await Promise.all([
|
|
562
|
+
getAssetBase64("Logo", logo),
|
|
563
|
+
getAssetBase64("Dark logo", darkLogo),
|
|
564
|
+
getAssetBase64("Brand", brand),
|
|
565
|
+
getAssetBase64("Dark brand", darkBrand),
|
|
566
|
+
|
|
567
|
+
getAssetHeight(logo),
|
|
568
|
+
getAssetHeight(brand),
|
|
569
|
+
]);
|
|
773
570
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
{ ratio: 1.5, suffix: "hdpi" },
|
|
783
|
-
{ ratio: 2, suffix: "xhdpi" },
|
|
784
|
-
{ ratio: 3, suffix: "xxhdpi" },
|
|
785
|
-
{ ratio: 4, suffix: "xxxhdpi" },
|
|
786
|
-
].map(({ ratio, suffix }) => {
|
|
787
|
-
const drawableDirPath = path.resolve(
|
|
788
|
-
androidOutputPath,
|
|
789
|
-
`drawable-${suffix}`,
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
hfs.ensureDir(drawableDirPath);
|
|
793
|
-
|
|
794
|
-
// https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
|
|
795
|
-
const canvasSize = 288 * ratio;
|
|
796
|
-
|
|
797
|
-
// https://sharp.pixelplumbing.com/api-constructor
|
|
798
|
-
const canvas = sharp({
|
|
799
|
-
create: {
|
|
800
|
-
width: canvasSize,
|
|
801
|
-
height: canvasSize,
|
|
802
|
-
channels: 4,
|
|
803
|
-
background: {
|
|
804
|
-
r: 255,
|
|
805
|
-
g: 255,
|
|
806
|
-
b: 255,
|
|
807
|
-
alpha: 0,
|
|
808
|
-
},
|
|
809
|
-
},
|
|
810
|
-
});
|
|
571
|
+
const record: Record<string, string> = {
|
|
572
|
+
background: background.hex,
|
|
573
|
+
darkBackground: darkBackground?.hex ?? "",
|
|
574
|
+
logo: logoHash,
|
|
575
|
+
darkLogo: darkLogoHash,
|
|
576
|
+
brand: brandHash,
|
|
577
|
+
darkBrand: darkBrandHash,
|
|
578
|
+
};
|
|
811
579
|
|
|
812
|
-
|
|
580
|
+
const stableKey = Object.keys(record)
|
|
581
|
+
.sort()
|
|
582
|
+
.map((key) => record[key])
|
|
583
|
+
.join();
|
|
813
584
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
canvas
|
|
820
|
-
.composite([{ input }])
|
|
821
|
-
.png({ quality: 100 })
|
|
822
|
-
.toFile(filePath),
|
|
823
|
-
)
|
|
824
|
-
.then(() => {
|
|
825
|
-
log.write(filePath, {
|
|
826
|
-
width: canvasSize,
|
|
827
|
-
height: canvasSize,
|
|
828
|
-
});
|
|
829
|
-
});
|
|
830
|
-
}),
|
|
831
|
-
);
|
|
585
|
+
const fileNameSuffix = crypto
|
|
586
|
+
.createHash("shake256", { outputLength: 3 })
|
|
587
|
+
.update(stableKey)
|
|
588
|
+
.digest("hex")
|
|
589
|
+
.toLowerCase();
|
|
832
590
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
591
|
+
return {
|
|
592
|
+
fileNameSuffix,
|
|
593
|
+
logoHeight,
|
|
594
|
+
brandHeight,
|
|
595
|
+
};
|
|
596
|
+
};
|
|
839
597
|
|
|
840
|
-
|
|
841
|
-
const manifestXml = readXmlLike(manifestXmlPath);
|
|
842
|
-
const activities = manifestXml.root.querySelectorAll("activity");
|
|
598
|
+
export type Extras = Awaited<ReturnType<typeof getExtras>>;
|
|
843
599
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
selfClosingTags: true,
|
|
855
|
-
singleAttributePerLine: true,
|
|
856
|
-
});
|
|
857
|
-
} else {
|
|
858
|
-
log.warn("No AndroidManifest.xml found. Skipping…");
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
const valuesPath = path.resolve(androidOutputPath, "values");
|
|
862
|
-
hfs.ensureDir(valuesPath);
|
|
863
|
-
|
|
864
|
-
const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
|
|
865
|
-
const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
|
|
866
|
-
|
|
867
|
-
if (hfs.exists(colorsXmlPath)) {
|
|
868
|
-
const colorsXml = readXmlLike(colorsXmlPath);
|
|
869
|
-
const nextColor = parseHtml(colorsXmlEntry);
|
|
600
|
+
export const writeAndroidAssets = async ({
|
|
601
|
+
androidOutputPath,
|
|
602
|
+
props,
|
|
603
|
+
extras,
|
|
604
|
+
}: {
|
|
605
|
+
androidOutputPath: string;
|
|
606
|
+
props: Props;
|
|
607
|
+
extras: Extras;
|
|
608
|
+
}) => {
|
|
609
|
+
const { logo, brand } = props;
|
|
870
610
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
611
|
+
if (logo.width > 192 || extras.logoHeight > 192) {
|
|
612
|
+
return log.warn(
|
|
613
|
+
"Logo size exceeding 192x192dp will be cropped by Android. Skipping Android assets generation…",
|
|
614
|
+
);
|
|
615
|
+
}
|
|
874
616
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
617
|
+
if (brand != null && (brand.width > 200 || extras.brandHeight > 80)) {
|
|
618
|
+
return log.warn(
|
|
619
|
+
"Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…",
|
|
620
|
+
);
|
|
621
|
+
}
|
|
880
622
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
});
|
|
885
|
-
} else {
|
|
886
|
-
await writeXmlLike(
|
|
887
|
-
colorsXmlPath,
|
|
888
|
-
`<resources>${colorsXmlEntry}</resources>`,
|
|
889
|
-
{ formatter: "xmlFormatter" },
|
|
890
|
-
);
|
|
891
|
-
}
|
|
623
|
+
if (logo.width > 134 || extras.logoHeight > 134) {
|
|
624
|
+
log.warn("Logo size exceeds 134x134dp. It might be cropped by Android.");
|
|
625
|
+
}
|
|
892
626
|
|
|
893
|
-
|
|
627
|
+
log.title("🤖", "Android");
|
|
628
|
+
hfs.ensureDir(androidOutputPath);
|
|
894
629
|
|
|
895
|
-
|
|
896
|
-
|
|
630
|
+
await Promise.all(
|
|
631
|
+
[
|
|
632
|
+
{ ratio: 1, suffix: "mdpi" },
|
|
633
|
+
{ ratio: 1.5, suffix: "hdpi" },
|
|
634
|
+
{ ratio: 2, suffix: "xhdpi" },
|
|
635
|
+
{ ratio: 3, suffix: "xxhdpi" },
|
|
636
|
+
{ ratio: 4, suffix: "xxxhdpi" },
|
|
637
|
+
].map(({ ratio, suffix }) => {
|
|
638
|
+
const drawableDirPath = path.resolve(
|
|
639
|
+
androidOutputPath,
|
|
640
|
+
`drawable-${suffix}`,
|
|
641
|
+
);
|
|
897
642
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
643
|
+
hfs.ensureDir(drawableDirPath);
|
|
644
|
+
|
|
645
|
+
// https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
|
|
646
|
+
const canvasSize = 288 * ratio;
|
|
647
|
+
|
|
648
|
+
// https://sharp.pixelplumbing.com/api-constructor
|
|
649
|
+
const canvas = sharp({
|
|
650
|
+
create: {
|
|
651
|
+
width: canvasSize,
|
|
652
|
+
height: canvasSize,
|
|
653
|
+
channels: 4,
|
|
654
|
+
background: {
|
|
655
|
+
r: 255,
|
|
656
|
+
g: 255,
|
|
657
|
+
b: 255,
|
|
658
|
+
alpha: 0,
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
});
|
|
901
662
|
|
|
902
|
-
|
|
663
|
+
const filePath = path.resolve(drawableDirPath, "bootsplash_logo.png");
|
|
903
664
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
665
|
+
return logo.image
|
|
666
|
+
.clone()
|
|
667
|
+
.resize(logo.width * ratio)
|
|
668
|
+
.toBuffer()
|
|
669
|
+
.then((input) =>
|
|
670
|
+
canvas.composite([{ input }]).png({ quality: 100 }).toFile(filePath),
|
|
909
671
|
)
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const name = node.getAttribute("name");
|
|
916
|
-
|
|
917
|
-
return (
|
|
918
|
-
name !== "bootSplashBackground" &&
|
|
919
|
-
name !== "bootSplashLogo" &&
|
|
920
|
-
name !== "bootSplashBrand" &&
|
|
921
|
-
name !== "postBootSplashTheme"
|
|
922
|
-
);
|
|
923
|
-
})
|
|
924
|
-
.map((node) => node.toString());
|
|
925
|
-
|
|
926
|
-
const styleItems: string[] = [
|
|
927
|
-
...(extraItems.length > 0 ? [...extraItems, ""] : []),
|
|
928
|
-
|
|
929
|
-
'<item name="bootSplashBackground">@color/bootsplash_background</item>',
|
|
930
|
-
'<item name="bootSplashLogo">@drawable/bootsplash_logo</item>',
|
|
931
|
-
|
|
932
|
-
...(brand != null && brandPath != null
|
|
933
|
-
? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>']
|
|
934
|
-
: []),
|
|
935
|
-
|
|
936
|
-
'<item name="postBootSplashTheme">@style/AppTheme</item>',
|
|
937
|
-
];
|
|
938
|
-
|
|
939
|
-
const nextStyle = parseHtml(dedent`
|
|
940
|
-
<style name="BootTheme" parent="${parent}">
|
|
941
|
-
${styleItems.join("\n")}
|
|
942
|
-
</style>
|
|
943
|
-
`);
|
|
944
|
-
|
|
945
|
-
prevStyle?.remove(); // remove the existing style
|
|
946
|
-
stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
|
|
947
|
-
|
|
948
|
-
await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
|
|
949
|
-
...stylesXml.formatOptions,
|
|
950
|
-
formatter: "prettier",
|
|
951
|
-
htmlWhitespaceSensitivity: "ignore",
|
|
672
|
+
.then(() => {
|
|
673
|
+
log.write(filePath, {
|
|
674
|
+
width: canvasSize,
|
|
675
|
+
height: canvasSize,
|
|
676
|
+
});
|
|
952
677
|
});
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
if (iosOutputPath != null) {
|
|
960
|
-
log.title("🍏", "iOS");
|
|
678
|
+
}),
|
|
679
|
+
);
|
|
680
|
+
};
|
|
961
681
|
|
|
962
|
-
|
|
963
|
-
|
|
682
|
+
export const writeIOSAssets = async ({
|
|
683
|
+
iosOutputPath,
|
|
684
|
+
props,
|
|
685
|
+
extras,
|
|
686
|
+
}: {
|
|
687
|
+
iosOutputPath: string;
|
|
688
|
+
props: Props;
|
|
689
|
+
extras: Extras;
|
|
690
|
+
}) => {
|
|
691
|
+
const { background, logo } = props;
|
|
964
692
|
|
|
965
|
-
|
|
693
|
+
log.title("🍏", "iOS");
|
|
694
|
+
hfs.ensureDir(iosOutputPath);
|
|
966
695
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
)
|
|
696
|
+
// clean existing assets
|
|
697
|
+
hfs
|
|
698
|
+
.readDir(iosOutputPath)
|
|
699
|
+
.filter((file) => file === "Colors.xcassets" || file === "Images.xcassets")
|
|
700
|
+
.map((file) => path.join(iosOutputPath, file))
|
|
701
|
+
.flatMap((dir) =>
|
|
702
|
+
hfs
|
|
703
|
+
.readDir(dir)
|
|
704
|
+
.filter((file) => file.startsWith("BootSplash"))
|
|
705
|
+
.map((file) => path.join(dir, file)),
|
|
706
|
+
)
|
|
707
|
+
.forEach((file) => {
|
|
708
|
+
hfs.rm(file);
|
|
709
|
+
});
|
|
972
710
|
|
|
973
|
-
|
|
974
|
-
iosOutputPath,
|
|
975
|
-
"Images.xcassets",
|
|
976
|
-
`BootSplashLogo-${fileNameSuffix}.imageset`,
|
|
977
|
-
);
|
|
711
|
+
const storyboardPath = path.resolve(iosOutputPath, "BootSplash.storyboard");
|
|
978
712
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
storyboardPath,
|
|
984
|
-
getStoryboard({
|
|
985
|
-
logoHeight,
|
|
986
|
-
logoWidth,
|
|
987
|
-
background: background.rgb,
|
|
988
|
-
fileNameSuffix,
|
|
989
|
-
}),
|
|
990
|
-
{
|
|
991
|
-
formatter: "xmlFormatter",
|
|
992
|
-
whiteSpaceAtEndOfSelfclosingTag: false,
|
|
993
|
-
},
|
|
994
|
-
);
|
|
713
|
+
await writeXmlLike(storyboardPath, getStoryboard({ props, extras }), {
|
|
714
|
+
formatter: "xmlFormatter",
|
|
715
|
+
whiteSpaceAtEndOfSelfclosingTag: false,
|
|
716
|
+
});
|
|
995
717
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
"color-space": "srgb",
|
|
1002
|
-
components: {
|
|
1003
|
-
blue: background.rgb.B,
|
|
1004
|
-
green: background.rgb.G,
|
|
1005
|
-
red: background.rgb.R,
|
|
1006
|
-
alpha: "1.000",
|
|
1007
|
-
},
|
|
1008
|
-
},
|
|
1009
|
-
},
|
|
1010
|
-
],
|
|
1011
|
-
info: {
|
|
1012
|
-
author: "xcode",
|
|
1013
|
-
version: 1,
|
|
1014
|
-
},
|
|
1015
|
-
});
|
|
718
|
+
const colorsSetPath = path.resolve(
|
|
719
|
+
iosOutputPath,
|
|
720
|
+
"Colors.xcassets",
|
|
721
|
+
`BootSplashBackground-${extras.fileNameSuffix}.colorset`,
|
|
722
|
+
);
|
|
1016
723
|
|
|
1017
|
-
|
|
724
|
+
hfs.ensureDir(colorsSetPath);
|
|
1018
725
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
{
|
|
1032
|
-
idiom: "universal",
|
|
1033
|
-
filename: `${logoFileName}@3x.png`,
|
|
1034
|
-
scale: "3x",
|
|
726
|
+
writeJson(path.resolve(colorsSetPath, "Contents.json"), {
|
|
727
|
+
colors: [
|
|
728
|
+
{
|
|
729
|
+
idiom: "universal",
|
|
730
|
+
color: {
|
|
731
|
+
"color-space": "srgb",
|
|
732
|
+
components: {
|
|
733
|
+
blue: background.rgb.B,
|
|
734
|
+
green: background.rgb.G,
|
|
735
|
+
red: background.rgb.R,
|
|
736
|
+
alpha: "1.000",
|
|
737
|
+
},
|
|
1035
738
|
},
|
|
1036
|
-
],
|
|
1037
|
-
info: {
|
|
1038
|
-
author: "xcode",
|
|
1039
|
-
version: 1,
|
|
1040
739
|
},
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
{ ratio: 3, suffix: "@3x" },
|
|
1048
|
-
].map(({ ratio, suffix }) => {
|
|
1049
|
-
const filePath = path.resolve(
|
|
1050
|
-
imageSetPath,
|
|
1051
|
-
`${logoFileName}${suffix}.png`,
|
|
1052
|
-
);
|
|
1053
|
-
|
|
1054
|
-
return logo
|
|
1055
|
-
.clone()
|
|
1056
|
-
.resize(logoWidth * ratio)
|
|
1057
|
-
.png({ quality: 100 })
|
|
1058
|
-
.toFile(filePath)
|
|
1059
|
-
.then(({ width, height }) => {
|
|
1060
|
-
log.write(filePath, { width, height });
|
|
1061
|
-
});
|
|
1062
|
-
}),
|
|
1063
|
-
);
|
|
740
|
+
],
|
|
741
|
+
info: {
|
|
742
|
+
author: "xcode",
|
|
743
|
+
version: 1,
|
|
744
|
+
},
|
|
745
|
+
});
|
|
1064
746
|
|
|
1065
|
-
|
|
1066
|
-
const infoPlistPath = path.resolve(iosOutputPath, "Info.plist");
|
|
747
|
+
const logoFileName = `logo-${extras.fileNameSuffix}`;
|
|
1067
748
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
749
|
+
const imagesSetPath = path.resolve(
|
|
750
|
+
iosOutputPath,
|
|
751
|
+
"Images.xcassets",
|
|
752
|
+
`BootSplashLogo-${extras.fileNameSuffix}.imageset`,
|
|
753
|
+
);
|
|
1072
754
|
|
|
1073
|
-
|
|
755
|
+
hfs.ensureDir(imagesSetPath);
|
|
1074
756
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
|
|
757
|
+
writeJson(path.resolve(imagesSetPath, "Contents.json"), {
|
|
758
|
+
images: [
|
|
759
|
+
{
|
|
760
|
+
idiom: "universal",
|
|
761
|
+
filename: `${logoFileName}.png`,
|
|
762
|
+
scale: "1x",
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
idiom: "universal",
|
|
766
|
+
filename: `${logoFileName}@2x.png`,
|
|
767
|
+
scale: "2x",
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
idiom: "universal",
|
|
771
|
+
filename: `${logoFileName}@3x.png`,
|
|
772
|
+
scale: "3x",
|
|
773
|
+
},
|
|
774
|
+
],
|
|
775
|
+
info: {
|
|
776
|
+
author: "xcode",
|
|
777
|
+
version: 1,
|
|
778
|
+
},
|
|
779
|
+
});
|
|
1084
780
|
|
|
1085
|
-
|
|
1086
|
-
|
|
781
|
+
await Promise.all(
|
|
782
|
+
[
|
|
783
|
+
{ ratio: 1, suffix: "" },
|
|
784
|
+
{ ratio: 2, suffix: "@2x" },
|
|
785
|
+
{ ratio: 3, suffix: "@3x" },
|
|
786
|
+
].map(({ ratio, suffix }) => {
|
|
787
|
+
const filePath = path.resolve(
|
|
788
|
+
imagesSetPath,
|
|
789
|
+
`${logoFileName}${suffix}.png`,
|
|
790
|
+
);
|
|
1087
791
|
|
|
1088
|
-
|
|
1089
|
-
|
|
792
|
+
return logo.image
|
|
793
|
+
.clone()
|
|
794
|
+
.resize(logo.width * ratio)
|
|
795
|
+
.png({ quality: 100 })
|
|
796
|
+
.toFile(filePath)
|
|
797
|
+
.then(({ width, height }) => {
|
|
798
|
+
log.write(filePath, { width, height });
|
|
799
|
+
});
|
|
800
|
+
}),
|
|
801
|
+
);
|
|
802
|
+
};
|
|
1090
803
|
|
|
1091
|
-
|
|
1092
|
-
|
|
804
|
+
export const writeWebAssets = async ({
|
|
805
|
+
htmlTemplatePath,
|
|
806
|
+
props,
|
|
807
|
+
extras,
|
|
808
|
+
}: {
|
|
809
|
+
htmlTemplatePath: string;
|
|
810
|
+
props: Props;
|
|
811
|
+
extras: Extras;
|
|
812
|
+
}) => {
|
|
813
|
+
const { background, logo } = props;
|
|
1093
814
|
|
|
1094
|
-
|
|
1095
|
-
const projectName = path.basename(iosOutputPath);
|
|
1096
|
-
const groupName = path.parse(xcodeProjectPath).name;
|
|
815
|
+
log.title("🌐", "Web");
|
|
1097
816
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
groupName,
|
|
1102
|
-
isBuildFile: true,
|
|
1103
|
-
});
|
|
817
|
+
const htmlTemplate = readXmlLike(htmlTemplatePath);
|
|
818
|
+
const { format } = await logo.image.metadata();
|
|
819
|
+
const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
|
|
1104
820
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
821
|
+
const base64 = (
|
|
822
|
+
format === "svg"
|
|
823
|
+
? hfs.buffer(logo.path)
|
|
824
|
+
: await logo.image
|
|
825
|
+
.clone()
|
|
826
|
+
.resize(Math.round(logo.width * 2))
|
|
827
|
+
.png({ quality: 100 })
|
|
828
|
+
.toBuffer()
|
|
829
|
+
).toString("base64");
|
|
830
|
+
|
|
831
|
+
const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
|
|
832
|
+
|
|
833
|
+
const nextStyle = parseHtml(dedent`
|
|
834
|
+
<style id="bootsplash-style">
|
|
835
|
+
#bootsplash {
|
|
836
|
+
position: absolute;
|
|
837
|
+
top: 0;
|
|
838
|
+
bottom: 0;
|
|
839
|
+
left: 0;
|
|
840
|
+
right: 0;
|
|
841
|
+
overflow: hidden;
|
|
842
|
+
display: flex;
|
|
843
|
+
justify-content: center;
|
|
844
|
+
align-items: center;
|
|
845
|
+
background-color: ${background.hex};
|
|
846
|
+
}
|
|
847
|
+
#bootsplash-logo {
|
|
848
|
+
content: url("${dataURI}");
|
|
849
|
+
width: ${logo.width}px;
|
|
850
|
+
height: ${extras.logoHeight}px;
|
|
851
|
+
}
|
|
852
|
+
</style>
|
|
853
|
+
`);
|
|
1111
854
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
855
|
+
if (prevStyle != null) {
|
|
856
|
+
prevStyle.replaceWith(nextStyle);
|
|
857
|
+
} else {
|
|
858
|
+
htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
|
|
1115
859
|
}
|
|
1116
860
|
|
|
1117
|
-
|
|
1118
|
-
log.title("🌐", "Web");
|
|
1119
|
-
|
|
1120
|
-
const htmlTemplate = readXmlLike(htmlTemplatePath);
|
|
1121
|
-
const { format } = await logo.metadata();
|
|
1122
|
-
const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
|
|
1123
|
-
|
|
1124
|
-
const base64 = (
|
|
1125
|
-
format === "svg"
|
|
1126
|
-
? hfs.buffer(logoPath)
|
|
1127
|
-
: await logo
|
|
1128
|
-
.clone()
|
|
1129
|
-
.resize(Math.round(logoWidth * 2))
|
|
1130
|
-
.png({ quality: 100 })
|
|
1131
|
-
.toBuffer()
|
|
1132
|
-
).toString("base64");
|
|
1133
|
-
|
|
1134
|
-
const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
|
|
1135
|
-
|
|
1136
|
-
const nextStyle = parseHtml(dedent`
|
|
1137
|
-
<style id="bootsplash-style">
|
|
1138
|
-
#bootsplash {
|
|
1139
|
-
position: absolute;
|
|
1140
|
-
top: 0;
|
|
1141
|
-
bottom: 0;
|
|
1142
|
-
left: 0;
|
|
1143
|
-
right: 0;
|
|
1144
|
-
overflow: hidden;
|
|
1145
|
-
display: flex;
|
|
1146
|
-
justify-content: center;
|
|
1147
|
-
align-items: center;
|
|
1148
|
-
background-color: ${background.hex};
|
|
1149
|
-
}
|
|
1150
|
-
#bootsplash-logo {
|
|
1151
|
-
content: url("${dataURI}");
|
|
1152
|
-
width: ${logoWidth}px;
|
|
1153
|
-
height: ${logoHeight}px;
|
|
1154
|
-
}
|
|
1155
|
-
</style>
|
|
1156
|
-
`);
|
|
1157
|
-
|
|
1158
|
-
if (prevStyle != null) {
|
|
1159
|
-
prevStyle.replaceWith(nextStyle);
|
|
1160
|
-
} else {
|
|
1161
|
-
htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
|
|
1162
|
-
}
|
|
861
|
+
const prevDiv = htmlTemplate.root.querySelector("#bootsplash");
|
|
1163
862
|
|
|
1164
|
-
|
|
863
|
+
const nextDiv = parseHtml(dedent`
|
|
864
|
+
<div id="bootsplash">
|
|
865
|
+
<div id="bootsplash-logo"></div>
|
|
866
|
+
</div>
|
|
867
|
+
`);
|
|
1165
868
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
869
|
+
if (prevDiv != null) {
|
|
870
|
+
prevDiv.replaceWith(nextDiv);
|
|
871
|
+
} else {
|
|
872
|
+
htmlTemplate.root.querySelector("body")?.appendChild(nextDiv);
|
|
873
|
+
}
|
|
1171
874
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
875
|
+
await writeXmlLike(htmlTemplatePath, htmlTemplate.root.toString(), {
|
|
876
|
+
...htmlTemplate.formatOptions,
|
|
877
|
+
formatter: "prettier",
|
|
878
|
+
useCssPlugin: true,
|
|
879
|
+
});
|
|
880
|
+
};
|
|
1177
881
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
882
|
+
export const writeGenericAssets = async ({
|
|
883
|
+
props,
|
|
884
|
+
extras,
|
|
885
|
+
}: {
|
|
886
|
+
props: Props;
|
|
887
|
+
extras: Extras;
|
|
888
|
+
}) => {
|
|
889
|
+
const { assetsOutputPath, background, logo } = props;
|
|
1184
890
|
|
|
1185
891
|
log.title("📄", "Assets");
|
|
1186
|
-
|
|
1187
892
|
hfs.ensureDir(assetsOutputPath);
|
|
1188
893
|
|
|
1189
894
|
writeJson(path.resolve(assetsOutputPath, "manifest.json"), {
|
|
1190
895
|
background: background.hex,
|
|
1191
896
|
logo: {
|
|
1192
|
-
width:
|
|
1193
|
-
height: logoHeight,
|
|
897
|
+
width: logo.width,
|
|
898
|
+
height: extras.logoHeight,
|
|
1194
899
|
},
|
|
1195
900
|
} satisfies Manifest);
|
|
1196
901
|
|
|
@@ -1204,9 +909,9 @@ export const generate = async ({
|
|
|
1204
909
|
].map(({ ratio, suffix }) => {
|
|
1205
910
|
const filePath = path.resolve(assetsOutputPath, `logo${suffix}.png`);
|
|
1206
911
|
|
|
1207
|
-
return logo
|
|
912
|
+
return logo.image
|
|
1208
913
|
.clone()
|
|
1209
|
-
.resize(Math.round(
|
|
914
|
+
.resize(Math.round(logo.width * ratio))
|
|
1210
915
|
.png({ quality: 100 })
|
|
1211
916
|
.toFile(filePath)
|
|
1212
917
|
.then(({ width, height }) => {
|
|
@@ -1214,37 +919,259 @@ export const generate = async ({
|
|
|
1214
919
|
});
|
|
1215
920
|
}),
|
|
1216
921
|
);
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
export type AddonConfig = {
|
|
925
|
+
props: Props;
|
|
926
|
+
extras: Extras;
|
|
927
|
+
|
|
928
|
+
androidOutputPath: string | void;
|
|
929
|
+
iosOutputPath: string | void;
|
|
930
|
+
htmlTemplatePath: string | void;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const requireAddon = ():
|
|
934
|
+
| {
|
|
935
|
+
execute: (config: AddonConfig) => Promise<void>;
|
|
936
|
+
|
|
937
|
+
withAndroidAssets: Expo.ConfigPlugin<Props>;
|
|
938
|
+
withAndroidColorsNight: Expo.ConfigPlugin<Props>;
|
|
939
|
+
withIOSAssets: Expo.ConfigPlugin<Props>;
|
|
940
|
+
withWebAssets: Expo.ConfigPlugin<Props>;
|
|
941
|
+
withGenericAssets: Expo.ConfigPlugin<Props>;
|
|
942
|
+
}
|
|
943
|
+
| undefined => {
|
|
944
|
+
try {
|
|
945
|
+
return require("./addon");
|
|
946
|
+
} catch {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
export const generate = async ({
|
|
952
|
+
platforms,
|
|
953
|
+
html,
|
|
954
|
+
flavor,
|
|
955
|
+
plist,
|
|
956
|
+
...config
|
|
957
|
+
}: {
|
|
958
|
+
platforms: Platforms;
|
|
959
|
+
html: string;
|
|
960
|
+
flavor: string;
|
|
961
|
+
plist?: string;
|
|
962
|
+
|
|
963
|
+
logo: string;
|
|
964
|
+
background: string;
|
|
965
|
+
logoWidth: number;
|
|
966
|
+
assetsOutput: string;
|
|
967
|
+
licenseKey?: string;
|
|
968
|
+
brand?: string;
|
|
969
|
+
brandWidth: number;
|
|
970
|
+
darkBackground?: string;
|
|
971
|
+
darkLogo?: string;
|
|
972
|
+
darkBrand?: string;
|
|
973
|
+
}) => {
|
|
974
|
+
const props = getProps(config);
|
|
975
|
+
const { background, brand, licenseKey, executeAddon } = props;
|
|
976
|
+
const extras = await getExtras(props);
|
|
977
|
+
|
|
978
|
+
const androidOutputPath = getAndroidOutputPath({ flavor, platforms });
|
|
979
|
+
const iosOutputPath = getIOSOutputPath({ platforms });
|
|
980
|
+
const htmlTemplatePath = getHtmlTemplatePath({ html, platforms });
|
|
981
|
+
|
|
982
|
+
if (androidOutputPath != null) {
|
|
983
|
+
await writeAndroidAssets({ androidOutputPath, props, extras });
|
|
984
|
+
|
|
985
|
+
const manifestXmlPath = path.resolve(
|
|
986
|
+
androidOutputPath,
|
|
987
|
+
"..",
|
|
988
|
+
"AndroidManifest.xml",
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
if (hfs.exists(manifestXmlPath)) {
|
|
992
|
+
const manifestXml = readXmlLike(manifestXmlPath);
|
|
993
|
+
const activities = manifestXml.root.querySelectorAll("activity");
|
|
994
|
+
|
|
995
|
+
for (const activity of activities) {
|
|
996
|
+
if (activity.getAttribute("android:name") === ".MainActivity") {
|
|
997
|
+
activity.setAttribute("android:theme", "@style/BootTheme");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
|
|
1002
|
+
...manifestXml.formatOptions,
|
|
1003
|
+
formatter: "prettier",
|
|
1004
|
+
htmlWhitespaceSensitivity: "ignore",
|
|
1005
|
+
selfClosingTags: true,
|
|
1006
|
+
singleAttributePerLine: true,
|
|
1007
|
+
});
|
|
1008
|
+
} else {
|
|
1009
|
+
log.warn("No AndroidManifest.xml found");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const valuesPath = path.resolve(androidOutputPath, "values");
|
|
1013
|
+
hfs.ensureDir(valuesPath);
|
|
1014
|
+
|
|
1015
|
+
const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
|
|
1016
|
+
const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
|
|
1017
|
+
|
|
1018
|
+
if (hfs.exists(colorsXmlPath)) {
|
|
1019
|
+
const colorsXml = readXmlLike(colorsXmlPath);
|
|
1020
|
+
const nextColor = parseHtml(colorsXmlEntry);
|
|
1021
|
+
|
|
1022
|
+
const prevColor = colorsXml.root.querySelector(
|
|
1023
|
+
'color[name="bootsplash_background"]',
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
if (prevColor != null) {
|
|
1027
|
+
prevColor.replaceWith(nextColor);
|
|
1028
|
+
} else {
|
|
1029
|
+
colorsXml.root.querySelector("resources")?.appendChild(nextColor);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
|
|
1033
|
+
...colorsXml.formatOptions,
|
|
1034
|
+
formatter: "xmlFormatter",
|
|
1035
|
+
});
|
|
1036
|
+
} else {
|
|
1037
|
+
await writeXmlLike(
|
|
1038
|
+
colorsXmlPath,
|
|
1039
|
+
`<resources>${colorsXmlEntry}</resources>`,
|
|
1040
|
+
{ formatter: "xmlFormatter" },
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const stylesXmlPath = path.resolve(valuesPath, "styles.xml");
|
|
1045
|
+
|
|
1046
|
+
if (hfs.exists(stylesXmlPath)) {
|
|
1047
|
+
const stylesXml = readXmlLike(stylesXmlPath);
|
|
1048
|
+
const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
|
|
1049
|
+
const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
|
|
1050
|
+
|
|
1051
|
+
const extraItems = parseHtml(
|
|
1052
|
+
prevStyle?.text
|
|
1053
|
+
.split("\n")
|
|
1054
|
+
.map((line) => line.trim())
|
|
1055
|
+
.join("") ?? "",
|
|
1056
|
+
)
|
|
1057
|
+
.childNodes.filter((node) => {
|
|
1058
|
+
if (!(node instanceof HTMLElement)) {
|
|
1059
|
+
return true;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const name = node.getAttribute("name");
|
|
1063
|
+
|
|
1064
|
+
return (
|
|
1065
|
+
name !== "bootSplashBackground" &&
|
|
1066
|
+
name !== "bootSplashLogo" &&
|
|
1067
|
+
name !== "bootSplashBrand" &&
|
|
1068
|
+
name !== "postBootSplashTheme"
|
|
1069
|
+
);
|
|
1070
|
+
})
|
|
1071
|
+
.map((node) => node.toString());
|
|
1072
|
+
|
|
1073
|
+
const styleItems: string[] = [
|
|
1074
|
+
...(extraItems.length > 0 ? [...extraItems, ""] : []),
|
|
1075
|
+
|
|
1076
|
+
'<item name="bootSplashBackground">@color/bootsplash_background</item>',
|
|
1077
|
+
'<item name="bootSplashLogo">@drawable/bootsplash_logo</item>',
|
|
1078
|
+
|
|
1079
|
+
...(brand != null
|
|
1080
|
+
? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>']
|
|
1081
|
+
: []),
|
|
1082
|
+
|
|
1083
|
+
'<item name="postBootSplashTheme">@style/AppTheme</item>',
|
|
1084
|
+
];
|
|
1085
|
+
|
|
1086
|
+
const nextStyle = parseHtml(dedent`
|
|
1087
|
+
<style name="BootTheme" parent="${parent}">
|
|
1088
|
+
${styleItems.join("\n")}
|
|
1089
|
+
</style>
|
|
1090
|
+
`);
|
|
1091
|
+
|
|
1092
|
+
prevStyle?.remove(); // remove the existing style
|
|
1093
|
+
stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
|
|
1094
|
+
|
|
1095
|
+
await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
|
|
1096
|
+
...stylesXml.formatOptions,
|
|
1097
|
+
formatter: "prettier",
|
|
1098
|
+
htmlWhitespaceSensitivity: "ignore",
|
|
1099
|
+
});
|
|
1100
|
+
} else {
|
|
1101
|
+
log.warn("No styles.xml found");
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (iosOutputPath != null) {
|
|
1106
|
+
await writeIOSAssets({ iosOutputPath, props, extras });
|
|
1107
|
+
|
|
1108
|
+
const infoPlistPath = getInfoPlistPath({ iosOutputPath, plist });
|
|
1109
|
+
|
|
1110
|
+
if (infoPlistPath != null) {
|
|
1111
|
+
const infoPlist = ExpoPlist.parse(hfs.text(infoPlistPath)) as Record<
|
|
1112
|
+
string,
|
|
1113
|
+
unknown
|
|
1114
|
+
>;
|
|
1115
|
+
|
|
1116
|
+
infoPlist["UILaunchStoryboardName"] = "BootSplash";
|
|
1117
|
+
|
|
1118
|
+
const formatted = formatXml(ExpoPlist.build(infoPlist), {
|
|
1119
|
+
collapseContent: true,
|
|
1120
|
+
forceSelfClosingEmptyTag: false,
|
|
1121
|
+
indentation: "\t",
|
|
1122
|
+
lineSeparator: "\n",
|
|
1123
|
+
whiteSpaceAtEndOfSelfclosingTag: false,
|
|
1124
|
+
})
|
|
1125
|
+
.replace(/<string\/>/gm, "<string></string>")
|
|
1126
|
+
.replace(/^\t/gm, "");
|
|
1127
|
+
|
|
1128
|
+
hfs.write(infoPlistPath, formatted);
|
|
1129
|
+
log.write(infoPlistPath);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const pbxprojectPath = Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
|
|
1133
|
+
|
|
1134
|
+
const xcodeProjectPath =
|
|
1135
|
+
Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
|
|
1136
|
+
|
|
1137
|
+
const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
|
|
1138
|
+
const projectName = path.basename(iosOutputPath);
|
|
1139
|
+
const groupName = path.parse(xcodeProjectPath).name;
|
|
1140
|
+
|
|
1141
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1142
|
+
project,
|
|
1143
|
+
filepath: path.join(projectName, "BootSplash.storyboard"),
|
|
1144
|
+
groupName,
|
|
1145
|
+
isBuildFile: true,
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1149
|
+
project,
|
|
1150
|
+
filepath: path.join(projectName, "Colors.xcassets"),
|
|
1151
|
+
groupName,
|
|
1152
|
+
isBuildFile: true,
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
hfs.write(pbxprojectPath, project.writeSync());
|
|
1156
|
+
log.write(pbxprojectPath);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (htmlTemplatePath != null) {
|
|
1160
|
+
await writeWebAssets({ htmlTemplatePath, props, extras });
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
await writeGenericAssets({ props, extras });
|
|
1217
1164
|
|
|
1218
1165
|
if (licenseKey != null && executeAddon) {
|
|
1219
1166
|
const addon = requireAddon();
|
|
1220
1167
|
|
|
1221
1168
|
await addon?.execute({
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
fileNameSuffix,
|
|
1169
|
+
props,
|
|
1170
|
+
extras,
|
|
1225
1171
|
|
|
1226
1172
|
androidOutputPath,
|
|
1227
1173
|
iosOutputPath,
|
|
1228
1174
|
htmlTemplatePath,
|
|
1229
|
-
assetsOutputPath,
|
|
1230
|
-
|
|
1231
|
-
logoHeight,
|
|
1232
|
-
logoWidth,
|
|
1233
|
-
brandHeight,
|
|
1234
|
-
brandWidth,
|
|
1235
|
-
|
|
1236
|
-
logoPath,
|
|
1237
|
-
darkLogoPath,
|
|
1238
|
-
brandPath,
|
|
1239
|
-
darkBrandPath,
|
|
1240
|
-
|
|
1241
|
-
background,
|
|
1242
|
-
logo,
|
|
1243
|
-
brand,
|
|
1244
|
-
|
|
1245
|
-
darkBackground,
|
|
1246
|
-
darkLogo,
|
|
1247
|
-
darkBrand,
|
|
1248
1175
|
});
|
|
1249
1176
|
} else {
|
|
1250
1177
|
console.log(`
|
|
@@ -1262,3 +1189,342 @@ ${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━
|
|
|
1262
1189
|
`\n💖 Thanks for using ${pc.underline("react-native-bootsplash")}`,
|
|
1263
1190
|
);
|
|
1264
1191
|
};
|
|
1192
|
+
|
|
1193
|
+
// Expo plugin
|
|
1194
|
+
|
|
1195
|
+
const withoutExpoSplashScreen: Expo.ConfigPlugin<Props> =
|
|
1196
|
+
Expo.createRunOncePlugin((config) => config, "expo-splash-screen", "skip");
|
|
1197
|
+
|
|
1198
|
+
const withAndroidAssets: Expo.ConfigPlugin<Props> = (config, props) =>
|
|
1199
|
+
Expo.withDangerousMod(config, [
|
|
1200
|
+
"android",
|
|
1201
|
+
async (config) => {
|
|
1202
|
+
const { platformProjectRoot } = config.modRequest;
|
|
1203
|
+
const extras = await getExtras(props);
|
|
1204
|
+
|
|
1205
|
+
const androidOutputPath = path.resolve(
|
|
1206
|
+
platformProjectRoot,
|
|
1207
|
+
"app",
|
|
1208
|
+
"src",
|
|
1209
|
+
"main",
|
|
1210
|
+
"res",
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
await writeAndroidAssets({ androidOutputPath, props, extras });
|
|
1214
|
+
return config;
|
|
1215
|
+
},
|
|
1216
|
+
]);
|
|
1217
|
+
|
|
1218
|
+
const withAndroidManifest: Expo.ConfigPlugin<Props> = (config) =>
|
|
1219
|
+
Expo.withAndroidManifest(config, (config) => {
|
|
1220
|
+
config.modResults.manifest.application?.forEach((application) => {
|
|
1221
|
+
if (application.$["android:name"] === ".MainApplication") {
|
|
1222
|
+
const { activity } = application;
|
|
1223
|
+
|
|
1224
|
+
activity?.forEach((activity) => {
|
|
1225
|
+
if (activity.$["android:name"] === ".MainActivity") {
|
|
1226
|
+
activity.$["android:theme"] = "@style/BootTheme";
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
return config;
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
const withMainActivity: Expo.ConfigPlugin<Props> = (config) =>
|
|
1236
|
+
Expo.withMainActivity(config, (config) => {
|
|
1237
|
+
const { modResults } = config;
|
|
1238
|
+
|
|
1239
|
+
const withImports = addImports(
|
|
1240
|
+
modResults.contents.replace(
|
|
1241
|
+
/(\/\/ )?setTheme\(R\.style\.AppTheme\)/,
|
|
1242
|
+
"// setTheme(R.style.AppTheme)",
|
|
1243
|
+
),
|
|
1244
|
+
["android.os.Bundle", "com.zoontek.rnbootsplash.RNBootSplash"],
|
|
1245
|
+
false,
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
// indented with 4 spaces
|
|
1249
|
+
const withInit = mergeContents({
|
|
1250
|
+
src: withImports,
|
|
1251
|
+
comment: " //",
|
|
1252
|
+
tag: "bootsplash-init",
|
|
1253
|
+
offset: 0,
|
|
1254
|
+
anchor: /super\.onCreate\(null\)/,
|
|
1255
|
+
newSrc: " RNBootSplash.init(this, R.style.BootTheme)",
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
return {
|
|
1259
|
+
...config,
|
|
1260
|
+
modResults: {
|
|
1261
|
+
...modResults,
|
|
1262
|
+
contents: withInit.contents,
|
|
1263
|
+
},
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
const withAndroidStyles: Expo.ConfigPlugin<Props> = (config, props) =>
|
|
1268
|
+
Expo.withAndroidStyles(config, (config) => {
|
|
1269
|
+
const { android, brand } = props;
|
|
1270
|
+
const { darkContentBarsStyle } = android;
|
|
1271
|
+
|
|
1272
|
+
const { modResults } = config;
|
|
1273
|
+
const { resources } = modResults;
|
|
1274
|
+
const { style = [] } = resources;
|
|
1275
|
+
|
|
1276
|
+
const item = [
|
|
1277
|
+
{
|
|
1278
|
+
$: { name: "postBootSplashTheme" },
|
|
1279
|
+
_: "@style/AppTheme",
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
$: { name: "bootSplashBackground" },
|
|
1283
|
+
_: "@color/bootsplash_background",
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
$: { name: "bootSplashLogo" },
|
|
1287
|
+
_: "@drawable/bootsplash_logo",
|
|
1288
|
+
},
|
|
1289
|
+
];
|
|
1290
|
+
|
|
1291
|
+
if (brand != null) {
|
|
1292
|
+
item.push({
|
|
1293
|
+
$: { name: "bootSplashBrand" },
|
|
1294
|
+
_: "@drawable/bootsplash_brand",
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
if (darkContentBarsStyle != null) {
|
|
1298
|
+
item.push({
|
|
1299
|
+
$: { name: "darkContentBarsStyle" },
|
|
1300
|
+
_: String(darkContentBarsStyle),
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
const withBootTheme = [
|
|
1305
|
+
...style.filter(({ $ }) => $.name !== "BootTheme"),
|
|
1306
|
+
{
|
|
1307
|
+
$: {
|
|
1308
|
+
name: "BootTheme",
|
|
1309
|
+
parent: "Theme.BootSplash",
|
|
1310
|
+
},
|
|
1311
|
+
item,
|
|
1312
|
+
},
|
|
1313
|
+
];
|
|
1314
|
+
|
|
1315
|
+
return {
|
|
1316
|
+
...config,
|
|
1317
|
+
modResults: {
|
|
1318
|
+
...modResults,
|
|
1319
|
+
resources: {
|
|
1320
|
+
...resources,
|
|
1321
|
+
style: withBootTheme,
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
};
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
const withAndroidColors: Expo.ConfigPlugin<Props> = (config, props) =>
|
|
1328
|
+
Expo.withAndroidColors(config, (config) => {
|
|
1329
|
+
const { background } = props;
|
|
1330
|
+
|
|
1331
|
+
config.modResults = assignColorValue(config.modResults, {
|
|
1332
|
+
name: "bootsplash_background",
|
|
1333
|
+
value: background.hex,
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
return config;
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
const withIOSAssets: Expo.ConfigPlugin<Props> = (config, props) =>
|
|
1340
|
+
Expo.withDangerousMod(config, [
|
|
1341
|
+
"ios",
|
|
1342
|
+
async (config) => {
|
|
1343
|
+
const { platformProjectRoot, projectName = "" } = config.modRequest;
|
|
1344
|
+
const extras = await getExtras(props);
|
|
1345
|
+
const iosOutputPath = path.resolve(platformProjectRoot, projectName);
|
|
1346
|
+
|
|
1347
|
+
await writeIOSAssets({ iosOutputPath, props, extras });
|
|
1348
|
+
return config;
|
|
1349
|
+
},
|
|
1350
|
+
]);
|
|
1351
|
+
|
|
1352
|
+
const withAppDelegate: Expo.ConfigPlugin<Props> = (config) =>
|
|
1353
|
+
Expo.withAppDelegate(config, (config) => {
|
|
1354
|
+
const { modResults } = config;
|
|
1355
|
+
const { language } = modResults;
|
|
1356
|
+
|
|
1357
|
+
if (language !== "objc" && language !== "objcpp" && language !== "swift") {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
`Cannot modify the project AppDelegate as it's not in a supported language: ${language}`,
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const withHeader = mergeContents({
|
|
1364
|
+
src: modResults.contents,
|
|
1365
|
+
comment: "//",
|
|
1366
|
+
tag: "bootsplash-header",
|
|
1367
|
+
offset: 1,
|
|
1368
|
+
anchor: /import Expo/,
|
|
1369
|
+
newSrc: "import RNBootSplash",
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
const withRootView = mergeContents({
|
|
1373
|
+
src: withHeader.contents,
|
|
1374
|
+
comment: "//",
|
|
1375
|
+
tag: "bootsplash-init",
|
|
1376
|
+
offset: 1,
|
|
1377
|
+
anchor: /class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {/,
|
|
1378
|
+
newSrc: dedent`
|
|
1379
|
+
public override func customize(_ rootView: UIView) {
|
|
1380
|
+
super.customize(rootView)
|
|
1381
|
+
RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
|
|
1382
|
+
}
|
|
1383
|
+
`,
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
return {
|
|
1387
|
+
...config,
|
|
1388
|
+
modResults: {
|
|
1389
|
+
...modResults,
|
|
1390
|
+
contents: withRootView.contents,
|
|
1391
|
+
},
|
|
1392
|
+
};
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
const withInfoPlist: Expo.ConfigPlugin<Props> = (config) =>
|
|
1396
|
+
Expo.withInfoPlist(config, (config) => {
|
|
1397
|
+
config.modResults["UILaunchStoryboardName"] = "BootSplash";
|
|
1398
|
+
return config;
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
const withXcodeProject: Expo.ConfigPlugin<Props> = (config) =>
|
|
1402
|
+
Expo.withXcodeProject(config, (config) => {
|
|
1403
|
+
const { projectName = "" } = config.modRequest;
|
|
1404
|
+
|
|
1405
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1406
|
+
filepath: path.join(projectName, "BootSplash.storyboard"),
|
|
1407
|
+
groupName: projectName,
|
|
1408
|
+
project: config.modResults,
|
|
1409
|
+
isBuildFile: true,
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1413
|
+
filepath: path.join(projectName, "Colors.xcassets"),
|
|
1414
|
+
groupName: projectName,
|
|
1415
|
+
project: config.modResults,
|
|
1416
|
+
isBuildFile: true,
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
return config;
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
const withWebAssets: Expo.ConfigPlugin<Props> = (config, props) =>
|
|
1423
|
+
Expo.withDangerousMod(config, [
|
|
1424
|
+
config.platforms?.includes("ios") ? "ios" : "android",
|
|
1425
|
+
async (config) => {
|
|
1426
|
+
const extras = await getExtras(props);
|
|
1427
|
+
const fileName = "public/index.html";
|
|
1428
|
+
const htmlTemplatePath = path.resolve(workingPath, fileName);
|
|
1429
|
+
|
|
1430
|
+
if (!hfs.exists(htmlTemplatePath)) {
|
|
1431
|
+
await exec(`npx expo customize ${fileName}`);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
await writeWebAssets({ htmlTemplatePath, props, extras });
|
|
1435
|
+
return config;
|
|
1436
|
+
},
|
|
1437
|
+
]);
|
|
1438
|
+
|
|
1439
|
+
const withGenericAssets: Expo.ConfigPlugin<Props> = (config, props) =>
|
|
1440
|
+
Expo.withDangerousMod(config, [
|
|
1441
|
+
config.platforms?.includes("ios") ? "ios" : "android",
|
|
1442
|
+
async (config) => {
|
|
1443
|
+
const extras = await getExtras(props);
|
|
1444
|
+
await writeGenericAssets({ props, extras });
|
|
1445
|
+
return config;
|
|
1446
|
+
},
|
|
1447
|
+
]);
|
|
1448
|
+
|
|
1449
|
+
export const withBootSplash = Expo.createRunOncePlugin<
|
|
1450
|
+
| {
|
|
1451
|
+
logo: string;
|
|
1452
|
+
background?: string;
|
|
1453
|
+
logoWidth?: number;
|
|
1454
|
+
assetsOutput?: string;
|
|
1455
|
+
|
|
1456
|
+
licenseKey?: string;
|
|
1457
|
+
brand?: string;
|
|
1458
|
+
brandWidth?: number;
|
|
1459
|
+
darkBackground?: string;
|
|
1460
|
+
darkLogo?: string;
|
|
1461
|
+
darkBrand?: string;
|
|
1462
|
+
|
|
1463
|
+
android?: {
|
|
1464
|
+
darkContentBarsStyle?: boolean;
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
| undefined
|
|
1468
|
+
>((config, baseProps) => {
|
|
1469
|
+
const { platforms = [], sdkVersion = "0.1.0" } = config;
|
|
1470
|
+
|
|
1471
|
+
isExpo = true;
|
|
1472
|
+
|
|
1473
|
+
if (semver.lt(sdkVersion, "53.0.0")) {
|
|
1474
|
+
log.error("Requires Expo 53.0.0 (or higher)");
|
|
1475
|
+
process.exit(1);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
if (!baseProps?.logo) {
|
|
1479
|
+
log.error("Missing required parameter 'logo'");
|
|
1480
|
+
process.exit(1);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
const props = getProps({
|
|
1484
|
+
assetsOutput: "assets/bootsplash",
|
|
1485
|
+
background: "#fff",
|
|
1486
|
+
brandWidth: 80,
|
|
1487
|
+
logoWidth: 100,
|
|
1488
|
+
...baseProps,
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
const { executeAddon } = props;
|
|
1492
|
+
const addon = executeAddon ? requireAddon() : undefined;
|
|
1493
|
+
|
|
1494
|
+
const plugins: Expo.ConfigPlugin<Props>[] = [
|
|
1495
|
+
withoutExpoSplashScreen,
|
|
1496
|
+
addon?.withGenericAssets ?? withGenericAssets,
|
|
1497
|
+
];
|
|
1498
|
+
|
|
1499
|
+
if (platforms.includes("android")) {
|
|
1500
|
+
plugins.push(
|
|
1501
|
+
addon?.withAndroidAssets ?? withAndroidAssets,
|
|
1502
|
+
withAndroidManifest,
|
|
1503
|
+
withMainActivity,
|
|
1504
|
+
withAndroidStyles,
|
|
1505
|
+
withAndroidColors,
|
|
1506
|
+
);
|
|
1507
|
+
|
|
1508
|
+
if (addon != null) {
|
|
1509
|
+
plugins.push(addon.withAndroidColorsNight);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
if (platforms.includes("ios")) {
|
|
1514
|
+
plugins.push(
|
|
1515
|
+
addon?.withIOSAssets ?? withIOSAssets,
|
|
1516
|
+
withAppDelegate,
|
|
1517
|
+
withInfoPlist,
|
|
1518
|
+
withXcodeProject,
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
if (platforms.includes("web")) {
|
|
1523
|
+
plugins.push(addon?.withWebAssets ?? withWebAssets);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
return Expo.withPlugins(
|
|
1527
|
+
config,
|
|
1528
|
+
plugins.map((plugin) => [plugin, props]),
|
|
1529
|
+
);
|
|
1530
|
+
}, PACKAGE_NAME);
|