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