react-native-bootsplash 6.3.10 → 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.
Files changed (75) hide show
  1. package/README.md +16 -41
  2. package/android/build.gradle +10 -17
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashModuleImpl.kt +2 -2
  5. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashPackage.kt +0 -1
  6. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashQueue.kt +1 -1
  7. package/android/src/main/res/drawable/compat_splash_screen.xml +11 -6
  8. package/android/src/main/res/drawable/compat_splash_screen_oneui_4.xml +13 -6
  9. package/android/src/main/res/values/colors.xml +1 -3
  10. package/android/src/main/res/values/public.xml +0 -2
  11. package/android/src/main/res/values/styles.xml +13 -19
  12. package/android/src/main/res/values-night/styles.xml +1 -1
  13. package/android/src/main/res/values-v27/colors.xml +4 -0
  14. package/android/src/main/res/values-v27/styles.xml +1 -6
  15. package/android/src/main/res/values-v29/styles.xml +1 -11
  16. package/android/src/main/res/values-v30/styles.xml +1 -9
  17. package/android/src/main/res/values-v31/styles.xml +1 -1
  18. package/android/src/newarch/com/zoontek/rnbootsplash/RNBootSplashModule.kt +2 -2
  19. package/android/src/oldarch/com/zoontek/rnbootsplash/RNBootSplashModule.kt +3 -3
  20. package/app.plugin.js +1 -1
  21. package/cli.js +2 -11
  22. package/dist/commonjs/addon/index.js +49 -49
  23. package/dist/commonjs/addon/index.js.map +1 -1
  24. package/dist/commonjs/generate.js +811 -541
  25. package/dist/commonjs/generate.js.map +1 -1
  26. package/dist/commonjs/index.js +1 -1
  27. package/dist/commonjs/index.js.map +1 -1
  28. package/dist/commonjs/specs/NativeRNBootSplash.js.map +1 -0
  29. package/dist/commonjs/{NativeRNBootSplash.web.js → specs/NativeRNBootSplash.web.js} +1 -1
  30. package/dist/commonjs/specs/NativeRNBootSplash.web.js.map +1 -0
  31. package/dist/module/addon/index.js +49 -49
  32. package/dist/module/addon/index.js.map +1 -1
  33. package/dist/module/generate.js +807 -539
  34. package/dist/module/generate.js.map +1 -1
  35. package/dist/module/index.js +1 -1
  36. package/dist/module/index.js.map +1 -1
  37. package/dist/module/specs/NativeRNBootSplash.js.map +1 -0
  38. package/dist/module/{NativeRNBootSplash.web.js → specs/NativeRNBootSplash.web.js} +1 -1
  39. package/dist/module/specs/NativeRNBootSplash.web.js.map +1 -0
  40. package/dist/typescript/addon/index.d.ts +8 -2
  41. package/dist/typescript/addon/index.d.ts.map +1 -1
  42. package/dist/typescript/generate.d.ts +90 -35
  43. package/dist/typescript/generate.d.ts.map +1 -1
  44. package/dist/typescript/index.d.ts +2 -2
  45. package/dist/typescript/index.d.ts.map +1 -1
  46. package/dist/typescript/{NativeRNBootSplash.d.ts → specs/NativeRNBootSplash.d.ts} +1 -1
  47. package/dist/typescript/specs/NativeRNBootSplash.d.ts.map +1 -0
  48. package/dist/typescript/{NativeRNBootSplash.web.d.ts → specs/NativeRNBootSplash.web.d.ts} +1 -1
  49. package/dist/typescript/specs/NativeRNBootSplash.web.d.ts.map +1 -0
  50. package/ios/RNBootSplash.mm +4 -10
  51. package/package.json +24 -29
  52. package/src/generate.ts +1048 -782
  53. package/src/index.ts +7 -7
  54. package/src/{NativeRNBootSplash.ts → specs/NativeRNBootSplash.ts} +1 -1
  55. package/src/{NativeRNBootSplash.web.ts → specs/NativeRNBootSplash.web.ts} +1 -1
  56. package/android/src/main/AndroidManifestNew.xml +0 -3
  57. package/android/src/main/res/drawable-v23/compat_splash_screen.xml +0 -19
  58. package/android/src/main/res/drawable-v23/compat_splash_screen_oneui_4.xml +0 -21
  59. package/android/src/main/res/values-v23/styles.xml +0 -13
  60. package/dist/commonjs/NativeRNBootSplash.js.map +0 -1
  61. package/dist/commonjs/NativeRNBootSplash.web.js.map +0 -1
  62. package/dist/commonjs/expo.js +0 -348
  63. package/dist/commonjs/expo.js.map +0 -1
  64. package/dist/module/NativeRNBootSplash.js.map +0 -1
  65. package/dist/module/NativeRNBootSplash.web.js.map +0 -1
  66. package/dist/module/expo.js +0 -341
  67. package/dist/module/expo.js.map +0 -1
  68. package/dist/typescript/NativeRNBootSplash.d.ts.map +0 -1
  69. package/dist/typescript/NativeRNBootSplash.web.d.ts.map +0 -1
  70. package/dist/typescript/expo.d.ts +0 -11
  71. package/dist/typescript/expo.d.ts.map +0 -1
  72. package/react-native.config.js +0 -101
  73. package/src/expo.ts +0 -439
  74. /package/dist/commonjs/{NativeRNBootSplash.js → specs/NativeRNBootSplash.js} +0 -0
  75. /package/dist/module/{NativeRNBootSplash.js → specs/NativeRNBootSplash.js} +0 -0
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  import * as Expo from "@expo/config-plugins";
4
- import plist from "@expo/plist";
4
+ import { assignColorValue } from "@expo/config-plugins/build/android/Colors";
5
+ import { addImports } from "@expo/config-plugins/build/android/codeMod";
6
+ import { mergeContents } from "@expo/config-plugins/build/utils/generateCode";
7
+ import ExpoPlist from "@expo/plist";
5
8
  import { projectConfig as getAndroidProjectConfig } from "@react-native-community/cli-config-android";
6
9
  import { getProjectConfig as getAppleProjectConfig } from "@react-native-community/cli-config-apple";
7
10
  import { findProjectRoot } from "@react-native-community/cli-tools";
@@ -20,13 +23,10 @@ import sharp from "sharp";
20
23
  import { dedent } from "ts-dedent";
21
24
  import util from "util";
22
25
  import formatXml from "xml-formatter";
23
- const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
26
+ const PACKAGE_NAME = "react-native-bootsplash";
27
+ let isExpo = false;
28
+ export const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
24
29
  const projectRoot = findProjectRoot(workingPath);
25
- const getIOSProjectConfig = getAppleProjectConfig({
26
- platformName: "ios"
27
- });
28
- const ios = getIOSProjectConfig(projectRoot, {});
29
- const android = getAndroidProjectConfig(projectRoot);
30
30
  const promisifiedExec = util.promisify(childProcess.exec);
31
31
  const exec = cmd => promisifiedExec(cmd).then(({
32
32
  stdout,
@@ -34,16 +34,20 @@ const exec = cmd => promisifiedExec(cmd).then(({
34
34
  }) => stdout || stderr);
35
35
  export const log = {
36
36
  error: text => {
37
- console.log(pc.red(`❌ ${text}`));
37
+ console.log(pc.red(isExpo ? `❌ [${PACKAGE_NAME}] ${text}` : `❌ ${text}`));
38
38
  },
39
39
  title: (emoji, text) => {
40
- console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`);
40
+ if (!isExpo) {
41
+ console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`);
42
+ }
41
43
  },
42
44
  warn: text => {
43
- console.log(pc.yellow(`⚠️ ${text}`));
45
+ console.log(pc.yellow(isExpo ? `⚠️ [${PACKAGE_NAME}] ${text}` : `⚠️ ${text}`));
44
46
  },
45
47
  write: (filePath, dimensions) => {
46
- console.log(` ${path.relative(workingPath, filePath)}` + (dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : ""));
48
+ if (!isExpo) {
49
+ console.log(` ${path.relative(workingPath, filePath)}` + (dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : ""));
50
+ }
47
51
  }
48
52
  };
49
53
  const parseColor = value => {
@@ -64,19 +68,24 @@ const parseColor = value => {
64
68
  };
65
69
  };
66
70
  const getStoryboard = ({
67
- logoHeight,
68
- logoWidth,
69
- background: {
71
+ props,
72
+ extras
73
+ }) => {
74
+ const {
75
+ background,
76
+ logo
77
+ } = props;
78
+ const {
79
+ fileNameSuffix,
80
+ logoHeight
81
+ } = extras;
82
+ const {
70
83
  R,
71
84
  G,
72
85
  B
73
- },
74
- fileNameSuffix
75
- }) => {
86
+ } = background.rgb;
76
87
  const frameWidth = 375;
77
88
  const frameHeight = 667;
78
- const logoX = (frameWidth - logoWidth) / 2;
79
- const logoY = (frameHeight - logoHeight) / 2;
80
89
  return dedent`
81
90
  <?xml version="1.0" encoding="UTF-8"?>
82
91
  <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
@@ -98,7 +107,7 @@ const getStoryboard = ({
98
107
  <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
99
108
  <subviews>
100
109
  <imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo-${fileNameSuffix}" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
101
- <rect key="frame" x="${logoX}" y="${logoY}" width="${logoWidth}" height="${logoHeight}"/>
110
+ <rect key="frame" x="${(frameWidth - logo.width) / 2}" y="${(frameHeight - logoHeight) / 2}" width="${logo.width}" height="${logoHeight}"/>
102
111
  <accessibility key="accessibilityConfiguration">
103
112
  <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
104
113
  </accessibility>
@@ -118,7 +127,7 @@ const getStoryboard = ({
118
127
  </scene>
119
128
  </scenes>
120
129
  <resources>
121
- <image name="BootSplashLogo-${fileNameSuffix}" width="${logoWidth}" height="${logoHeight}"/>
130
+ <image name="BootSplashLogo-${fileNameSuffix}" width="${logo.width}" height="${logoHeight}"/>
122
131
  <namedColor name="BootSplashBackground-${fileNameSuffix}">
123
132
  <color red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
124
133
  </namedColor>
@@ -140,20 +149,6 @@ export const hfs = {
140
149
  recursive: true
141
150
  }),
142
151
  text: path => fs.readFileSync(path, "utf-8"),
143
- copy: (src, dest) => {
144
- if (hfs.isDir(src) || !hfs.exists(dest)) {
145
- return fs.copySync(src, dest, {
146
- overwrite: true
147
- });
148
- }
149
- const srcBuffer = fs.readFileSync(src);
150
- const destBuffer = fs.readFileSync(dest);
151
- if (!srcBuffer.equals(destBuffer)) {
152
- return fs.copySync(src, dest, {
153
- overwrite: true
154
- });
155
- }
156
- },
157
152
  ensureDir: dir => {
158
153
  fs.mkdirSync(dir, {
159
154
  recursive: true
@@ -164,52 +159,6 @@ export const hfs = {
164
159
  fs.writeFileSync(path, trimmed === "" ? trimmed : trimmed + "\n", "utf-8");
165
160
  }
166
161
  };
167
-
168
- // Adapted from https://github.com/square/find-yarn-workspace-root
169
- const findUp = (from, matcher) => {
170
- let previous;
171
- let current = path.normalize(from);
172
- do {
173
- const found = matcher(current);
174
- if (typeof found !== "undefined") {
175
- return found;
176
- }
177
- previous = current;
178
- current = path.dirname(current);
179
- } while (current !== previous);
180
- };
181
- export const getExpoConfig = from => {
182
- const hasDependency = findUp(from, dir => {
183
- const pkgPath = path.resolve(dir, "package.json");
184
- if (fs.existsSync(pkgPath)) {
185
- try {
186
- const pkg = hfs.json(pkgPath);
187
- return pkg.dependencies?.expo != null;
188
- } catch {} // eslint-disable-line no-empty
189
- }
190
- }) ?? false;
191
- if (!hasDependency) {
192
- return {
193
- isExpo: false
194
- };
195
- }
196
- const version = findUp(from, dir => {
197
- const pkgPath = path.resolve(dir, "node_modules", "expo", "package.json");
198
- if (fs.existsSync(pkgPath)) {
199
- try {
200
- const pkg = hfs.json(pkgPath);
201
- return pkg.version;
202
- } catch {} // eslint-disable-line no-empty
203
- }
204
- });
205
- if (version == null || semver.lt(version, "51.0.20")) {
206
- log.error("Requires Expo 51.0.20 (or higher)");
207
- process.exit(1);
208
- }
209
- return {
210
- isExpo: true
211
- };
212
- };
213
162
  export const writeJson = (filePath, content) => {
214
163
  hfs.write(filePath, JSON.stringify(content, null, 2));
215
164
  log.write(filePath);
@@ -262,48 +211,14 @@ export const writeXmlLike = async (filePath, content, {
262
211
  log.write(filePath);
263
212
  }
264
213
  };
265
- export const cleanIOSAssets = dir => {
266
- hfs.readDir(dir).filter(file => file === "Colors.xcassets" || file === "Images.xcassets").map(file => path.join(dir, file)).flatMap(dir => hfs.readDir(dir).filter(file => file.startsWith("BootSplash")).map(file => path.join(dir, file))).forEach(file => {
267
- hfs.rm(file);
268
- });
269
- };
270
- const getImageBase64 = async (image, width) => {
271
- if (image == null) {
214
+ const getAssetBase64 = async (name, asset) => {
215
+ if (asset == null) {
272
216
  return "";
273
217
  }
274
- const buffer = await image.clone().resize(width).png({
275
- quality: 100
276
- }).toBuffer();
277
- return buffer.toString("base64");
278
- };
279
- const getFileNameSuffix = async ({
280
- background,
281
- brand,
282
- brandWidth,
283
- darkBackground,
284
- darkBrand,
285
- darkLogo,
286
- logo,
287
- logoWidth
288
- }) => {
289
- const [logoHash, darkLogoHash, brandHash, darkBrandHash] = await Promise.all([getImageBase64(logo, logoWidth), getImageBase64(darkLogo, logoWidth), getImageBase64(brand, brandWidth), getImageBase64(darkBrand, brandWidth)]);
290
- const record = {
291
- background: background.hex,
292
- darkBackground: darkBackground?.hex ?? "",
293
- logo: logoHash,
294
- darkLogo: darkLogoHash,
295
- brand: brandHash,
296
- darkBrand: darkBrandHash
297
- };
298
- const stableKey = Object.keys(record).sort().map(key => record[key]).join();
299
- return crypto.createHash("shake256", {
300
- outputLength: 3
301
- }).update(stableKey).digest("hex").toLowerCase();
302
- };
303
- const ensureSupportedFormat = async (name, image) => {
304
- if (image == null) {
305
- return;
306
- }
218
+ const {
219
+ image,
220
+ width
221
+ } = asset;
307
222
  const {
308
223
  format
309
224
  } = await image.metadata();
@@ -311,56 +226,32 @@ const ensureSupportedFormat = async (name, image) => {
311
226
  log.error(`${name} image file format (${format}) is not supported`);
312
227
  process.exit(1);
313
228
  }
229
+ const buffer = await image.clone().resize(width).png({
230
+ quality: 100
231
+ }).toBuffer();
232
+ return buffer.toString("base64");
314
233
  };
315
234
  const getAndroidOutputPath = ({
316
- assetsOutputPath,
317
- brandHeight,
318
- brandWidth,
319
235
  flavor,
320
- isExpo,
321
- logoHeight,
322
- logoWidth,
323
236
  platforms
324
237
  }) => {
325
- if (!platforms.includes("android")) {
326
- return;
327
- }
328
- const withSizeChecks = assetsOutputPath => {
329
- if (logoWidth > 288 || logoHeight > 288) {
330
- return log.warn("Logo size exceeding 288x288dp will be cropped by Android. Skipping Android assets generation…");
331
- }
332
- if (brandWidth > 200 || brandHeight > 80) {
333
- return log.warn("Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…");
334
- }
335
- if (logoWidth > 192 || logoHeight > 192) {
336
- log.warn("Logo size exceeds 192x192dp. It might be cropped by Android.");
337
- }
338
- return assetsOutputPath;
339
- };
340
- if (isExpo) {
341
- return withSizeChecks(path.resolve(assetsOutputPath, "android"));
342
- }
343
- if (android == null) {
238
+ const android = getAndroidProjectConfig(projectRoot);
239
+ if (!platforms.includes("android") || android == null) {
344
240
  return;
345
241
  }
346
242
  const androidOutputPath = path.resolve(android.sourceDir, android.appName, "src", flavor, "res");
347
243
  if (!hfs.exists(androidOutputPath)) {
348
244
  return log.warn(`No ${path.relative(workingPath, androidOutputPath)} directory found. Skipping Android assets generation…`);
349
245
  }
350
- return withSizeChecks(androidOutputPath);
246
+ return androidOutputPath;
351
247
  };
352
248
  const getIOSOutputPath = ({
353
- assetsOutputPath,
354
- isExpo,
355
249
  platforms
356
250
  }) => {
357
- if (!platforms.includes("ios")) {
358
- return;
359
- }
360
- if (isExpo) {
361
- return path.resolve(assetsOutputPath, "ios");
362
- }
363
- if (ios == null) {
251
+ const ios = getAppleProjectConfig({
252
+ platformName: "ios"
253
+ })(projectRoot, {});
254
+ if (!platforms.includes("ios") || ios == null) {
364
255
  return;
365
256
  }
366
257
  if (ios.xcodeProject == null) {
@@ -372,428 +263,386 @@ const getIOSOutputPath = ({
372
263
  }
373
264
  return iosOutputPath;
374
265
  };
375
- const getHtmlTemplatePath = async ({
376
- isExpo,
266
+ const getHtmlTemplatePath = ({
377
267
  html,
378
268
  platforms
379
269
  }) => {
380
270
  if (!platforms.includes("web")) {
381
271
  return;
382
272
  }
383
- if (isExpo) {
384
- const htmlTemplatePath = path.resolve(workingPath, html);
385
- const htmlTemplateRelativePath = path.relative(workingPath, htmlTemplatePath);
386
- if (htmlTemplateRelativePath === "public/index.html" && !hfs.exists(htmlTemplatePath)) {
387
- const cmd = `npx expo customize ${htmlTemplateRelativePath}`;
388
- console.log(pc.dim(`Running ${cmd}`));
389
- await exec(cmd);
390
- }
391
- }
392
273
  const htmlTemplatePath = path.resolve(workingPath, html);
393
274
  if (!hfs.exists(htmlTemplatePath)) {
394
- return log.warn(`No ${path.relative(workingPath, htmlTemplatePath)} file found. Skipping HTML + CSS generation…`);
275
+ return log.warn(`No ${path.relative(workingPath, htmlTemplatePath)} found. Skipping HTML + CSS generation…`);
395
276
  }
396
277
  return htmlTemplatePath;
397
278
  };
398
- const getImageHeight = (image, width) => {
399
- if (image == null) {
279
+ const getInfoPlistPath = ({
280
+ iosOutputPath,
281
+ plist
282
+ }) => {
283
+ if (plist != null) {
284
+ const infoPlistPath = path.resolve(workingPath, plist);
285
+ if (!hfs.exists(infoPlistPath)) {
286
+ return log.warn(`No ${path.relative(workingPath, infoPlistPath)} found`);
287
+ }
288
+ return infoPlistPath;
289
+ }
290
+ return path.resolve(iosOutputPath, "Info.plist");
291
+ };
292
+ const getAssetHeight = asset => {
293
+ if (asset == null) {
400
294
  return Promise.resolve(0);
401
295
  }
402
- return image.clone().resize(width).toBuffer().then(buffer => sharp(buffer).metadata()).then(({
296
+ return asset.image.clone().resize(asset.width).toBuffer().then(buffer => sharp(buffer).metadata()).then(({
403
297
  height = 0
404
298
  }) => Math.round(height));
405
299
  };
406
- const requireAddon = () => {
407
- try {
408
- return require("./addon"); // eslint-disable-line
409
- } catch {
410
- return;
411
- }
412
- };
413
- export const generate = async ({
414
- projectType,
415
- platforms,
416
- html,
417
- flavor,
300
+ const getProps = ({
301
+ android = {},
418
302
  licenseKey,
419
- ...args
303
+ ...config
420
304
  }) => {
421
- const isExpo = projectType === "expo" || projectType === "detect" && getExpoConfig(workingPath).isExpo;
422
- if (semver.lt(process.versions.node, "18.0.0")) {
423
- log.error("Requires Node 18 (or higher)");
305
+ if (semver.lt(process.versions.node, "20.0.0")) {
306
+ log.error("Requires Node 20 (or higher)");
424
307
  process.exit(1);
425
308
  }
426
- const logoPath = path.resolve(workingPath, args.logo);
427
- const darkLogoPath = args.darkLogo != null ? path.resolve(workingPath, args.darkLogo) : undefined;
428
- const brandPath = args.brand != null ? path.resolve(workingPath, args.brand) : undefined;
429
- const darkBrandPath = args.darkBrand != null ? path.resolve(workingPath, args.darkBrand) : undefined;
430
- const assetsOutputPath = path.resolve(workingPath, args.assetsOutput);
431
- const logo = sharp(logoPath);
432
- const darkLogo = darkLogoPath != null ? sharp(darkLogoPath) : undefined;
433
- const brand = brandPath != null ? sharp(brandPath) : undefined;
434
- const darkBrand = darkBrandPath != null ? sharp(darkBrandPath) : undefined;
435
- const background = parseColor(args.background);
436
- const logoWidth = args.logoWidth - args.logoWidth % 2;
437
- const brandWidth = args.brandWidth - args.brandWidth % 2;
438
- const darkBackground = args.darkBackground != null ? parseColor(args.darkBackground) : undefined;
309
+ const assetsOutputPath = path.resolve(workingPath, config.assetsOutput);
310
+ const logoPath = path.resolve(workingPath, config.logo);
311
+ const darkLogoPath = config.darkLogo != null ? path.resolve(workingPath, config.darkLogo) : undefined;
312
+ const brandPath = config.brand != null ? path.resolve(workingPath, config.brand) : undefined;
313
+ const darkBrandPath = config.darkBrand != null ? path.resolve(workingPath, config.darkBrand) : undefined;
314
+ const logoWidth = config.logoWidth - config.logoWidth % 2;
315
+ const brandWidth = config.brandWidth - config.brandWidth % 2;
316
+ const logo = {
317
+ path: logoPath,
318
+ image: sharp(logoPath),
319
+ width: logoWidth
320
+ };
321
+ const darkLogo = darkLogoPath != null ? {
322
+ path: darkLogoPath,
323
+ image: sharp(darkLogoPath),
324
+ width: logoWidth
325
+ } : undefined;
326
+ const brand = brandPath != null ? {
327
+ path: brandPath,
328
+ image: sharp(brandPath),
329
+ width: brandWidth
330
+ } : undefined;
331
+ const darkBrand = darkBrandPath != null ? {
332
+ path: darkBrandPath,
333
+ image: sharp(darkBrandPath),
334
+ width: brandWidth
335
+ } : undefined;
336
+ const background = parseColor(config.background);
337
+ const darkBackground = config.darkBackground != null ? parseColor(config.darkBackground) : undefined;
439
338
  const executeAddon = brand != null || darkBackground != null || darkLogo != null || darkBrand != null;
440
339
  if (licenseKey != null && !executeAddon) {
441
340
  log.warn("You specified a license key but none of the options that requires it.");
442
341
  }
342
+ const optionNames = {
343
+ brand: isExpo ? "brand" : "--brand",
344
+ darkBackground: isExpo ? "darkBackground" : "--dark-background",
345
+ darkLogo: isExpo ? "darkLogo" : "--dark-logo",
346
+ darkBrand: isExpo ? "darkBrand" : "--dark-brand"
347
+ };
443
348
  if (licenseKey == null && executeAddon) {
444
- const options = [brand != null ? "brand" : "", darkBackground != null ? "dark-background" : "", darkLogo != null ? "dark-logo" : "", darkBrand != null ? "dark-brand" : ""].filter(option => option !== "").map(option => `--${option}`).join(", ");
349
+ const options = [brand != null ? optionNames.brand : "", darkBackground != null ? optionNames.darkBackground : "", darkLogo != null ? optionNames.darkLogo : "", darkBrand != null ? optionNames.darkBrand : ""].filter(option => option !== "").join(", ");
445
350
  log.error(`You need to specify a license key in order to use ${options}.`);
446
351
  process.exit(1);
447
352
  }
448
353
  if (brand == null && darkBrand != null) {
449
- log.error("--dark-brand option couldn't be used without --brand.");
354
+ log.error(`${optionNames.darkBrand} option couldn't be used without ${optionNames.brand}.`);
450
355
  process.exit(1);
451
356
  }
452
- await ensureSupportedFormat("Logo", logo);
453
- await ensureSupportedFormat("Dark logo", darkLogo);
454
- await ensureSupportedFormat("Brand", brand);
455
- await ensureSupportedFormat("Dark brand", darkBrand);
456
- const logoHeight = await getImageHeight(logo, logoWidth);
457
- const brandHeight = await getImageHeight(brand, brandWidth);
458
- if (logoWidth < args.logoWidth) {
357
+ if (logoWidth < config.logoWidth) {
459
358
  log.warn(`Logo width must be a multiple of 2. It has been rounded to ${logoWidth}dp.`);
460
359
  }
461
- if (brandWidth < args.brandWidth) {
360
+ if (brandWidth < config.brandWidth) {
462
361
  log.warn(`Brand width must be a multiple of 2. It has been rounded to ${brandWidth}dp.`);
463
362
  }
464
- const fileNameSuffix = await getFileNameSuffix({
363
+ return {
364
+ android,
365
+ assetsOutputPath,
366
+ licenseKey,
367
+ executeAddon,
465
368
  background,
466
- brand,
467
- brandWidth,
468
369
  darkBackground,
469
- darkBrand,
470
- darkLogo,
471
370
  logo,
472
- logoWidth
473
- });
474
- const androidOutputPath = getAndroidOutputPath({
475
- assetsOutputPath,
476
- brandHeight,
477
- brandWidth,
478
- flavor,
479
- isExpo,
371
+ darkLogo,
372
+ brand,
373
+ darkBrand
374
+ };
375
+ };
376
+ export const getExtras = async ({
377
+ background,
378
+ darkBackground,
379
+ logo,
380
+ darkLogo,
381
+ brand,
382
+ darkBrand
383
+ }) => {
384
+ const [logoHash, darkLogoHash, brandHash, darkBrandHash, logoHeight, brandHeight] = await Promise.all([getAssetBase64("Logo", logo), getAssetBase64("Dark logo", darkLogo), getAssetBase64("Brand", brand), getAssetBase64("Dark brand", darkBrand), getAssetHeight(logo), getAssetHeight(brand)]);
385
+ const record = {
386
+ background: background.hex,
387
+ darkBackground: darkBackground?.hex ?? "",
388
+ logo: logoHash,
389
+ darkLogo: darkLogoHash,
390
+ brand: brandHash,
391
+ darkBrand: darkBrandHash
392
+ };
393
+ const stableKey = Object.keys(record).sort().map(key => record[key]).join();
394
+ const fileNameSuffix = crypto.createHash("shake256", {
395
+ outputLength: 3
396
+ }).update(stableKey).digest("hex").toLowerCase();
397
+ return {
398
+ fileNameSuffix,
480
399
  logoHeight,
481
- logoWidth,
482
- platforms
483
- });
484
- const iosOutputPath = getIOSOutputPath({
485
- assetsOutputPath,
486
- isExpo,
487
- platforms
488
- });
489
- const htmlTemplatePath = await getHtmlTemplatePath({
490
- isExpo,
491
- html,
492
- platforms
493
- });
494
- if (androidOutputPath != null) {
495
- log.title("🤖", "Android");
496
- hfs.ensureDir(androidOutputPath);
497
- await Promise.all([{
498
- ratio: 1,
499
- suffix: "mdpi"
500
- }, {
501
- ratio: 1.5,
502
- suffix: "hdpi"
503
- }, {
504
- ratio: 2,
505
- suffix: "xhdpi"
506
- }, {
507
- ratio: 3,
508
- suffix: "xxhdpi"
509
- }, {
510
- ratio: 4,
511
- suffix: "xxxhdpi"
512
- }].map(({
513
- ratio,
514
- suffix
515
- }) => {
516
- const drawableDirPath = path.resolve(androidOutputPath, `drawable-${suffix}`);
517
- hfs.ensureDir(drawableDirPath);
400
+ brandHeight
401
+ };
402
+ };
403
+ export const writeAndroidAssets = async ({
404
+ androidOutputPath,
405
+ props,
406
+ extras
407
+ }) => {
408
+ const {
409
+ logo,
410
+ brand
411
+ } = props;
412
+ if (logo.width > 192 || extras.logoHeight > 192) {
413
+ return log.warn("Logo size exceeding 192x192dp will be cropped by Android. Skipping Android assets generation…");
414
+ }
415
+ if (brand != null && (brand.width > 200 || extras.brandHeight > 80)) {
416
+ return log.warn("Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…");
417
+ }
418
+ if (logo.width > 134 || extras.logoHeight > 134) {
419
+ log.warn("Logo size exceeds 134x134dp. It might be cropped by Android.");
420
+ }
421
+ log.title("🤖", "Android");
422
+ hfs.ensureDir(androidOutputPath);
423
+ await Promise.all([{
424
+ ratio: 1,
425
+ suffix: "mdpi"
426
+ }, {
427
+ ratio: 1.5,
428
+ suffix: "hdpi"
429
+ }, {
430
+ ratio: 2,
431
+ suffix: "xhdpi"
432
+ }, {
433
+ ratio: 3,
434
+ suffix: "xxhdpi"
435
+ }, {
436
+ ratio: 4,
437
+ suffix: "xxxhdpi"
438
+ }].map(({
439
+ ratio,
440
+ suffix
441
+ }) => {
442
+ const drawableDirPath = path.resolve(androidOutputPath, `drawable-${suffix}`);
443
+ hfs.ensureDir(drawableDirPath);
518
444
 
519
- // https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
520
- const canvasSize = 288 * ratio;
445
+ // https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
446
+ const canvasSize = 288 * ratio;
521
447
 
522
- // https://sharp.pixelplumbing.com/api-constructor
523
- const canvas = sharp({
524
- create: {
525
- width: canvasSize,
526
- height: canvasSize,
527
- channels: 4,
528
- background: {
529
- r: 255,
530
- g: 255,
531
- b: 255,
532
- alpha: 0
533
- }
448
+ // https://sharp.pixelplumbing.com/api-constructor
449
+ const canvas = sharp({
450
+ create: {
451
+ width: canvasSize,
452
+ height: canvasSize,
453
+ channels: 4,
454
+ background: {
455
+ r: 255,
456
+ g: 255,
457
+ b: 255,
458
+ alpha: 0
534
459
  }
535
- });
536
- const filePath = path.resolve(drawableDirPath, "bootsplash_logo.png");
537
- return logo.clone().resize(logoWidth * ratio).toBuffer().then(input => canvas.composite([{
538
- input
539
- }]).png({
540
- quality: 100
541
- }).toFile(filePath)).then(() => {
542
- log.write(filePath, {
543
- width: canvasSize,
544
- height: canvasSize
545
- });
546
- });
547
- }));
548
- if (!isExpo) {
549
- const manifestXmlPath = path.resolve(androidOutputPath, "..", "AndroidManifest.xml");
550
- if (hfs.exists(manifestXmlPath)) {
551
- const manifestXml = readXmlLike(manifestXmlPath);
552
- const activities = manifestXml.root.querySelectorAll("activity");
553
- for (const activity of activities) {
554
- if (activity.getAttribute("android:name") === ".MainActivity") {
555
- activity.setAttribute("android:theme", "@style/BootTheme");
556
- }
557
- }
558
- await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
559
- ...manifestXml.formatOptions,
560
- formatter: "prettier",
561
- htmlWhitespaceSensitivity: "ignore",
562
- selfClosingTags: true,
563
- singleAttributePerLine: true
564
- });
565
- } else {
566
- log.warn("No AndroidManifest.xml found. Skipping…");
567
- }
568
- const valuesPath = path.resolve(androidOutputPath, "values");
569
- hfs.ensureDir(valuesPath);
570
- const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
571
- const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
572
- if (hfs.exists(colorsXmlPath)) {
573
- const colorsXml = readXmlLike(colorsXmlPath);
574
- const nextColor = parseHtml(colorsXmlEntry);
575
- const prevColor = colorsXml.root.querySelector('color[name="bootsplash_background"]');
576
- if (prevColor != null) {
577
- prevColor.replaceWith(nextColor);
578
- } else {
579
- colorsXml.root.querySelector("resources")?.appendChild(nextColor);
580
- }
581
- await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
582
- ...colorsXml.formatOptions,
583
- formatter: "xmlFormatter"
584
- });
585
- } else {
586
- await writeXmlLike(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`, {
587
- formatter: "xmlFormatter"
588
- });
589
- }
590
- const stylesXmlPath = path.resolve(valuesPath, "styles.xml");
591
- if (hfs.exists(stylesXmlPath)) {
592
- const stylesXml = readXmlLike(stylesXmlPath);
593
- const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
594
- const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
595
- const extraItems = parseHtml(prevStyle?.text.split("\n").map(line => line.trim()).join("") ?? "").childNodes.filter(node => {
596
- if (!(node instanceof HTMLElement)) {
597
- return true;
598
- }
599
- const name = node.getAttribute("name");
600
- return name !== "bootSplashBackground" && name !== "bootSplashLogo" && name !== "bootSplashBrand" && name !== "postBootSplashTheme";
601
- }).map(node => node.toString());
602
- const styleItems = [...(extraItems.length > 0 ? [...extraItems, ""] : []), '<item name="bootSplashBackground">@color/bootsplash_background</item>', '<item name="bootSplashLogo">@drawable/bootsplash_logo</item>', ...(brand != null && brandPath != null ? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>'] : []), '<item name="postBootSplashTheme">@style/AppTheme</item>'];
603
- const nextStyle = parseHtml(dedent`
604
- <style name="BootTheme" parent="${parent}">
605
- ${styleItems.join("\n")}
606
- </style>
607
- `);
608
- prevStyle?.remove(); // remove the existing style
609
- stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
610
- await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
611
- ...stylesXml.formatOptions,
612
- formatter: "prettier",
613
- htmlWhitespaceSensitivity: "ignore"
614
- });
615
- } else {
616
- log.warn("No styles.xml found. Skipping…");
617
460
  }
618
- }
619
- }
620
- if (iosOutputPath != null) {
621
- log.title("🍏", "iOS");
622
- hfs.ensureDir(iosOutputPath);
623
- cleanIOSAssets(iosOutputPath);
624
- const storyboardPath = path.resolve(iosOutputPath, "BootSplash.storyboard");
625
- const colorsSetPath = path.resolve(iosOutputPath, "Colors.xcassets", `BootSplashBackground-${fileNameSuffix}.colorset`);
626
- const imageSetPath = path.resolve(iosOutputPath, "Images.xcassets", `BootSplashLogo-${fileNameSuffix}.imageset`);
627
- hfs.ensureDir(colorsSetPath);
628
- hfs.ensureDir(imageSetPath);
629
- await writeXmlLike(storyboardPath, getStoryboard({
630
- logoHeight,
631
- logoWidth,
632
- background: background.rgb,
633
- fileNameSuffix
634
- }), {
635
- formatter: "xmlFormatter",
636
- whiteSpaceAtEndOfSelfclosingTag: false
637
461
  });
638
- writeJson(path.resolve(colorsSetPath, "Contents.json"), {
639
- colors: [{
640
- idiom: "universal",
641
- color: {
642
- "color-space": "srgb",
643
- components: {
644
- blue: background.rgb.B,
645
- green: background.rgb.G,
646
- red: background.rgb.R,
647
- alpha: "1.000"
648
- }
649
- }
650
- }],
651
- info: {
652
- author: "xcode",
653
- version: 1
654
- }
462
+ const filePath = path.resolve(drawableDirPath, "bootsplash_logo.png");
463
+ return logo.image.clone().resize(logo.width * ratio).toBuffer().then(input => canvas.composite([{
464
+ input
465
+ }]).png({
466
+ quality: 100
467
+ }).toFile(filePath)).then(() => {
468
+ log.write(filePath, {
469
+ width: canvasSize,
470
+ height: canvasSize
471
+ });
655
472
  });
656
- const logoFileName = `logo-${fileNameSuffix}`;
657
- writeJson(path.resolve(imageSetPath, "Contents.json"), {
658
- images: [{
659
- idiom: "universal",
660
- filename: `${logoFileName}.png`,
661
- scale: "1x"
662
- }, {
663
- idiom: "universal",
664
- filename: `${logoFileName}@2x.png`,
665
- scale: "2x"
666
- }, {
667
- idiom: "universal",
668
- filename: `${logoFileName}@3x.png`,
669
- scale: "3x"
670
- }],
671
- info: {
672
- author: "xcode",
673
- version: 1
473
+ }));
474
+ };
475
+ export const writeIOSAssets = async ({
476
+ iosOutputPath,
477
+ props,
478
+ extras
479
+ }) => {
480
+ const {
481
+ background,
482
+ logo
483
+ } = props;
484
+ log.title("🍏", "iOS");
485
+ hfs.ensureDir(iosOutputPath);
486
+
487
+ // clean existing assets
488
+ hfs.readDir(iosOutputPath).filter(file => file === "Colors.xcassets" || file === "Images.xcassets").map(file => path.join(iosOutputPath, file)).flatMap(dir => hfs.readDir(dir).filter(file => file.startsWith("BootSplash")).map(file => path.join(dir, file))).forEach(file => {
489
+ hfs.rm(file);
490
+ });
491
+ const storyboardPath = path.resolve(iosOutputPath, "BootSplash.storyboard");
492
+ await writeXmlLike(storyboardPath, getStoryboard({
493
+ props,
494
+ extras
495
+ }), {
496
+ formatter: "xmlFormatter",
497
+ whiteSpaceAtEndOfSelfclosingTag: false
498
+ });
499
+ const colorsSetPath = path.resolve(iosOutputPath, "Colors.xcassets", `BootSplashBackground-${extras.fileNameSuffix}.colorset`);
500
+ hfs.ensureDir(colorsSetPath);
501
+ writeJson(path.resolve(colorsSetPath, "Contents.json"), {
502
+ colors: [{
503
+ idiom: "universal",
504
+ color: {
505
+ "color-space": "srgb",
506
+ components: {
507
+ blue: background.rgb.B,
508
+ green: background.rgb.G,
509
+ red: background.rgb.R,
510
+ alpha: "1.000"
511
+ }
674
512
  }
675
- });
676
- await Promise.all([{
677
- ratio: 1,
678
- suffix: ""
513
+ }],
514
+ info: {
515
+ author: "xcode",
516
+ version: 1
517
+ }
518
+ });
519
+ const logoFileName = `logo-${extras.fileNameSuffix}`;
520
+ const imagesSetPath = path.resolve(iosOutputPath, "Images.xcassets", `BootSplashLogo-${extras.fileNameSuffix}.imageset`);
521
+ hfs.ensureDir(imagesSetPath);
522
+ writeJson(path.resolve(imagesSetPath, "Contents.json"), {
523
+ images: [{
524
+ idiom: "universal",
525
+ filename: `${logoFileName}.png`,
526
+ scale: "1x"
679
527
  }, {
680
- ratio: 2,
681
- suffix: "@2x"
528
+ idiom: "universal",
529
+ filename: `${logoFileName}@2x.png`,
530
+ scale: "2x"
682
531
  }, {
683
- ratio: 3,
684
- suffix: "@3x"
685
- }].map(({
686
- ratio,
687
- suffix
532
+ idiom: "universal",
533
+ filename: `${logoFileName}@3x.png`,
534
+ scale: "3x"
535
+ }],
536
+ info: {
537
+ author: "xcode",
538
+ version: 1
539
+ }
540
+ });
541
+ await Promise.all([{
542
+ ratio: 1,
543
+ suffix: ""
544
+ }, {
545
+ ratio: 2,
546
+ suffix: "@2x"
547
+ }, {
548
+ ratio: 3,
549
+ suffix: "@3x"
550
+ }].map(({
551
+ ratio,
552
+ suffix
553
+ }) => {
554
+ const filePath = path.resolve(imagesSetPath, `${logoFileName}${suffix}.png`);
555
+ return logo.image.clone().resize(logo.width * ratio).png({
556
+ quality: 100
557
+ }).toFile(filePath).then(({
558
+ width,
559
+ height
688
560
  }) => {
689
- const filePath = path.resolve(imageSetPath, `${logoFileName}${suffix}.png`);
690
- return logo.clone().resize(logoWidth * ratio).png({
691
- quality: 100
692
- }).toFile(filePath).then(({
561
+ log.write(filePath, {
693
562
  width,
694
563
  height
695
- }) => {
696
- log.write(filePath, {
697
- width,
698
- height
699
- });
700
- });
701
- }));
702
- if (!isExpo) {
703
- const infoPlistPath = path.resolve(iosOutputPath, "Info.plist");
704
- const infoPlist = plist.parse(hfs.text(infoPlistPath));
705
- infoPlist["UILaunchStoryboardName"] = "BootSplash";
706
- const formatted = formatXml(plist.build(infoPlist), {
707
- collapseContent: true,
708
- forceSelfClosingEmptyTag: false,
709
- indentation: "\t",
710
- lineSeparator: "\n",
711
- whiteSpaceAtEndOfSelfclosingTag: false
712
- }).replace(/<string\/>/gm, "<string></string>").replace(/^\t/gm, "");
713
- hfs.write(infoPlistPath, formatted);
714
- log.write(infoPlistPath);
715
- const pbxprojectPath = Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
716
- const xcodeProjectPath = Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
717
- const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
718
- const projectName = path.basename(iosOutputPath);
719
- const groupName = path.parse(xcodeProjectPath).name;
720
- Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
721
- project,
722
- filepath: path.join(projectName, "BootSplash.storyboard"),
723
- groupName,
724
- isBuildFile: true
725
- });
726
- Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
727
- project,
728
- filepath: path.join(projectName, "Colors.xcassets"),
729
- groupName,
730
- isBuildFile: true
731
564
  });
732
- hfs.write(pbxprojectPath, project.writeSync());
733
- log.write(pbxprojectPath);
734
- }
735
- }
736
- if (htmlTemplatePath != null) {
737
- log.title("🌐", "Web");
738
- const htmlTemplate = readXmlLike(htmlTemplatePath);
739
- const {
740
- format
741
- } = await logo.metadata();
742
- const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
743
- const base64 = (format === "svg" ? hfs.buffer(logoPath) : await logo.clone().resize(Math.round(logoWidth * 2)).png({
744
- quality: 100
745
- }).toBuffer()).toString("base64");
746
- const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
747
- const nextStyle = parseHtml(dedent`
748
- <style id="bootsplash-style">
749
- #bootsplash {
750
- position: absolute;
751
- top: 0;
752
- bottom: 0;
753
- left: 0;
754
- right: 0;
755
- overflow: hidden;
756
- display: flex;
757
- justify-content: center;
758
- align-items: center;
759
- background-color: ${background.hex};
760
- }
761
- #bootsplash-logo {
762
- content: url("${dataURI}");
763
- width: ${logoWidth}px;
764
- height: ${logoHeight}px;
765
- }
766
- </style>
767
- `);
768
- if (prevStyle != null) {
769
- prevStyle.replaceWith(nextStyle);
770
- } else {
771
- htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
772
- }
773
- const prevDiv = htmlTemplate.root.querySelector("#bootsplash");
774
- const nextDiv = parseHtml(dedent`
775
- <div id="bootsplash">
776
- <div id="bootsplash-logo"></div>
777
- </div>
778
- `);
779
- if (prevDiv != null) {
780
- prevDiv.replaceWith(nextDiv);
781
- } else {
782
- htmlTemplate.root.querySelector("body")?.appendChild(nextDiv);
783
- }
784
- await writeXmlLike(htmlTemplatePath, htmlTemplate.root.toString(), {
785
- ...htmlTemplate.formatOptions,
786
- formatter: "prettier",
787
- useCssPlugin: true
788
565
  });
566
+ }));
567
+ };
568
+ export const writeWebAssets = async ({
569
+ htmlTemplatePath,
570
+ props,
571
+ extras
572
+ }) => {
573
+ const {
574
+ background,
575
+ logo
576
+ } = props;
577
+ log.title("🌐", "Web");
578
+ const htmlTemplate = readXmlLike(htmlTemplatePath);
579
+ const {
580
+ format
581
+ } = await logo.image.metadata();
582
+ const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
583
+ const base64 = (format === "svg" ? hfs.buffer(logo.path) : await logo.image.clone().resize(Math.round(logo.width * 2)).png({
584
+ quality: 100
585
+ }).toBuffer()).toString("base64");
586
+ const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
587
+ const nextStyle = parseHtml(dedent`
588
+ <style id="bootsplash-style">
589
+ #bootsplash {
590
+ position: absolute;
591
+ top: 0;
592
+ bottom: 0;
593
+ left: 0;
594
+ right: 0;
595
+ overflow: hidden;
596
+ display: flex;
597
+ justify-content: center;
598
+ align-items: center;
599
+ background-color: ${background.hex};
600
+ }
601
+ #bootsplash-logo {
602
+ content: url("${dataURI}");
603
+ width: ${logo.width}px;
604
+ height: ${extras.logoHeight}px;
605
+ }
606
+ </style>
607
+ `);
608
+ if (prevStyle != null) {
609
+ prevStyle.replaceWith(nextStyle);
610
+ } else {
611
+ htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
612
+ }
613
+ const prevDiv = htmlTemplate.root.querySelector("#bootsplash");
614
+ const nextDiv = parseHtml(dedent`
615
+ <div id="bootsplash">
616
+ <div id="bootsplash-logo"></div>
617
+ </div>
618
+ `);
619
+ if (prevDiv != null) {
620
+ prevDiv.replaceWith(nextDiv);
621
+ } else {
622
+ htmlTemplate.root.querySelector("body")?.appendChild(nextDiv);
789
623
  }
624
+ await writeXmlLike(htmlTemplatePath, htmlTemplate.root.toString(), {
625
+ ...htmlTemplate.formatOptions,
626
+ formatter: "prettier",
627
+ useCssPlugin: true
628
+ });
629
+ };
630
+ export const writeGenericAssets = async ({
631
+ props,
632
+ extras
633
+ }) => {
634
+ const {
635
+ assetsOutputPath,
636
+ background,
637
+ logo
638
+ } = props;
790
639
  log.title("📄", "Assets");
791
640
  hfs.ensureDir(assetsOutputPath);
792
641
  writeJson(path.resolve(assetsOutputPath, "manifest.json"), {
793
642
  background: background.hex,
794
643
  logo: {
795
- width: logoWidth,
796
- height: logoHeight
644
+ width: logo.width,
645
+ height: extras.logoHeight
797
646
  }
798
647
  });
799
648
  await Promise.all([{
@@ -816,7 +665,7 @@ export const generate = async ({
816
665
  suffix
817
666
  }) => {
818
667
  const filePath = path.resolve(assetsOutputPath, `logo${suffix}.png`);
819
- return logo.clone().resize(Math.round(logoWidth * ratio)).png({
668
+ return logo.image.clone().resize(Math.round(logo.width * ratio)).png({
820
669
  quality: 100
821
670
  }).toFile(filePath).then(({
822
671
  width,
@@ -828,30 +677,178 @@ export const generate = async ({
828
677
  });
829
678
  });
830
679
  }));
680
+ };
681
+ const requireAddon = () => {
682
+ try {
683
+ return require("./addon");
684
+ } catch {
685
+ return;
686
+ }
687
+ };
688
+ export const generate = async ({
689
+ platforms,
690
+ html,
691
+ flavor,
692
+ plist,
693
+ ...config
694
+ }) => {
695
+ const props = getProps(config);
696
+ const {
697
+ background,
698
+ brand,
699
+ licenseKey,
700
+ executeAddon
701
+ } = props;
702
+ const extras = await getExtras(props);
703
+ const androidOutputPath = getAndroidOutputPath({
704
+ flavor,
705
+ platforms
706
+ });
707
+ const iosOutputPath = getIOSOutputPath({
708
+ platforms
709
+ });
710
+ const htmlTemplatePath = getHtmlTemplatePath({
711
+ html,
712
+ platforms
713
+ });
714
+ if (androidOutputPath != null) {
715
+ await writeAndroidAssets({
716
+ androidOutputPath,
717
+ props,
718
+ extras
719
+ });
720
+ const manifestXmlPath = path.resolve(androidOutputPath, "..", "AndroidManifest.xml");
721
+ if (hfs.exists(manifestXmlPath)) {
722
+ const manifestXml = readXmlLike(manifestXmlPath);
723
+ const activities = manifestXml.root.querySelectorAll("activity");
724
+ for (const activity of activities) {
725
+ if (activity.getAttribute("android:name") === ".MainActivity") {
726
+ activity.setAttribute("android:theme", "@style/BootTheme");
727
+ }
728
+ }
729
+ await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
730
+ ...manifestXml.formatOptions,
731
+ formatter: "prettier",
732
+ htmlWhitespaceSensitivity: "ignore",
733
+ selfClosingTags: true,
734
+ singleAttributePerLine: true
735
+ });
736
+ } else {
737
+ log.warn("No AndroidManifest.xml found");
738
+ }
739
+ const valuesPath = path.resolve(androidOutputPath, "values");
740
+ hfs.ensureDir(valuesPath);
741
+ const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
742
+ const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
743
+ if (hfs.exists(colorsXmlPath)) {
744
+ const colorsXml = readXmlLike(colorsXmlPath);
745
+ const nextColor = parseHtml(colorsXmlEntry);
746
+ const prevColor = colorsXml.root.querySelector('color[name="bootsplash_background"]');
747
+ if (prevColor != null) {
748
+ prevColor.replaceWith(nextColor);
749
+ } else {
750
+ colorsXml.root.querySelector("resources")?.appendChild(nextColor);
751
+ }
752
+ await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
753
+ ...colorsXml.formatOptions,
754
+ formatter: "xmlFormatter"
755
+ });
756
+ } else {
757
+ await writeXmlLike(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`, {
758
+ formatter: "xmlFormatter"
759
+ });
760
+ }
761
+ const stylesXmlPath = path.resolve(valuesPath, "styles.xml");
762
+ if (hfs.exists(stylesXmlPath)) {
763
+ const stylesXml = readXmlLike(stylesXmlPath);
764
+ const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
765
+ const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
766
+ const extraItems = parseHtml(prevStyle?.text.split("\n").map(line => line.trim()).join("") ?? "").childNodes.filter(node => {
767
+ if (!(node instanceof HTMLElement)) {
768
+ return true;
769
+ }
770
+ const name = node.getAttribute("name");
771
+ return name !== "bootSplashBackground" && name !== "bootSplashLogo" && name !== "bootSplashBrand" && name !== "postBootSplashTheme";
772
+ }).map(node => node.toString());
773
+ const styleItems = [...(extraItems.length > 0 ? [...extraItems, ""] : []), '<item name="bootSplashBackground">@color/bootsplash_background</item>', '<item name="bootSplashLogo">@drawable/bootsplash_logo</item>', ...(brand != null ? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>'] : []), '<item name="postBootSplashTheme">@style/AppTheme</item>'];
774
+ const nextStyle = parseHtml(dedent`
775
+ <style name="BootTheme" parent="${parent}">
776
+ ${styleItems.join("\n")}
777
+ </style>
778
+ `);
779
+ prevStyle?.remove(); // remove the existing style
780
+ stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
781
+ await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
782
+ ...stylesXml.formatOptions,
783
+ formatter: "prettier",
784
+ htmlWhitespaceSensitivity: "ignore"
785
+ });
786
+ } else {
787
+ log.warn("No styles.xml found");
788
+ }
789
+ }
790
+ if (iosOutputPath != null) {
791
+ await writeIOSAssets({
792
+ iosOutputPath,
793
+ props,
794
+ extras
795
+ });
796
+ const infoPlistPath = getInfoPlistPath({
797
+ iosOutputPath,
798
+ plist
799
+ });
800
+ if (infoPlistPath != null) {
801
+ const infoPlist = ExpoPlist.parse(hfs.text(infoPlistPath));
802
+ infoPlist["UILaunchStoryboardName"] = "BootSplash";
803
+ const formatted = formatXml(ExpoPlist.build(infoPlist), {
804
+ collapseContent: true,
805
+ forceSelfClosingEmptyTag: false,
806
+ indentation: "\t",
807
+ lineSeparator: "\n",
808
+ whiteSpaceAtEndOfSelfclosingTag: false
809
+ }).replace(/<string\/>/gm, "<string></string>").replace(/^\t/gm, "");
810
+ hfs.write(infoPlistPath, formatted);
811
+ log.write(infoPlistPath);
812
+ }
813
+ const pbxprojectPath = Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
814
+ const xcodeProjectPath = Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
815
+ const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
816
+ const projectName = path.basename(iosOutputPath);
817
+ const groupName = path.parse(xcodeProjectPath).name;
818
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
819
+ project,
820
+ filepath: path.join(projectName, "BootSplash.storyboard"),
821
+ groupName,
822
+ isBuildFile: true
823
+ });
824
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
825
+ project,
826
+ filepath: path.join(projectName, "Colors.xcassets"),
827
+ groupName,
828
+ isBuildFile: true
829
+ });
830
+ hfs.write(pbxprojectPath, project.writeSync());
831
+ log.write(pbxprojectPath);
832
+ }
833
+ if (htmlTemplatePath != null) {
834
+ await writeWebAssets({
835
+ htmlTemplatePath,
836
+ props,
837
+ extras
838
+ });
839
+ }
840
+ await writeGenericAssets({
841
+ props,
842
+ extras
843
+ });
831
844
  if (licenseKey != null && executeAddon) {
832
845
  const addon = requireAddon();
833
846
  await addon?.execute({
834
- licenseKey,
835
- isExpo,
836
- fileNameSuffix,
847
+ props,
848
+ extras,
837
849
  androidOutputPath,
838
850
  iosOutputPath,
839
- htmlTemplatePath,
840
- assetsOutputPath,
841
- logoHeight,
842
- logoWidth,
843
- brandHeight,
844
- brandWidth,
845
- logoPath,
846
- darkLogoPath,
847
- brandPath,
848
- darkBrandPath,
849
- background,
850
- logo,
851
- brand,
852
- darkBackground,
853
- darkLogo,
854
- darkBrand
851
+ htmlTemplatePath
855
852
  });
856
853
  } else {
857
854
  console.log(`
@@ -862,4 +859,275 @@ ${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━
862
859
  }
863
860
  console.log(`\n💖 Thanks for using ${pc.underline("react-native-bootsplash")}`);
864
861
  };
862
+
863
+ // Expo plugin
864
+
865
+ const withoutExpoSplashScreen = Expo.createRunOncePlugin(config => config, "expo-splash-screen", "skip");
866
+ const withAndroidAssets = (config, props) => Expo.withDangerousMod(config, ["android", async config => {
867
+ const {
868
+ platformProjectRoot
869
+ } = config.modRequest;
870
+ const extras = await getExtras(props);
871
+ const androidOutputPath = path.resolve(platformProjectRoot, "app", "src", "main", "res");
872
+ await writeAndroidAssets({
873
+ androidOutputPath,
874
+ props,
875
+ extras
876
+ });
877
+ return config;
878
+ }]);
879
+ const withAndroidManifest = config => Expo.withAndroidManifest(config, config => {
880
+ config.modResults.manifest.application?.forEach(application => {
881
+ if (application.$["android:name"] === ".MainApplication") {
882
+ const {
883
+ activity
884
+ } = application;
885
+ activity?.forEach(activity => {
886
+ if (activity.$["android:name"] === ".MainActivity") {
887
+ activity.$["android:theme"] = "@style/BootTheme";
888
+ }
889
+ });
890
+ }
891
+ });
892
+ return config;
893
+ });
894
+ const withMainActivity = config => Expo.withMainActivity(config, config => {
895
+ const {
896
+ modResults
897
+ } = config;
898
+ const withImports = addImports(modResults.contents.replace(/(\/\/ )?setTheme\(R\.style\.AppTheme\)/, "// setTheme(R.style.AppTheme)"), ["android.os.Bundle", "com.zoontek.rnbootsplash.RNBootSplash"], false);
899
+
900
+ // indented with 4 spaces
901
+ const withInit = mergeContents({
902
+ src: withImports,
903
+ comment: " //",
904
+ tag: "bootsplash-init",
905
+ offset: 0,
906
+ anchor: /super\.onCreate\(null\)/,
907
+ newSrc: " RNBootSplash.init(this, R.style.BootTheme)"
908
+ });
909
+ return {
910
+ ...config,
911
+ modResults: {
912
+ ...modResults,
913
+ contents: withInit.contents
914
+ }
915
+ };
916
+ });
917
+ const withAndroidStyles = (config, props) => Expo.withAndroidStyles(config, config => {
918
+ const {
919
+ android,
920
+ brand
921
+ } = props;
922
+ const {
923
+ darkContentBarsStyle
924
+ } = android;
925
+ const {
926
+ modResults
927
+ } = config;
928
+ const {
929
+ resources
930
+ } = modResults;
931
+ const {
932
+ style = []
933
+ } = resources;
934
+ const item = [{
935
+ $: {
936
+ name: "postBootSplashTheme"
937
+ },
938
+ _: "@style/AppTheme"
939
+ }, {
940
+ $: {
941
+ name: "bootSplashBackground"
942
+ },
943
+ _: "@color/bootsplash_background"
944
+ }, {
945
+ $: {
946
+ name: "bootSplashLogo"
947
+ },
948
+ _: "@drawable/bootsplash_logo"
949
+ }];
950
+ if (brand != null) {
951
+ item.push({
952
+ $: {
953
+ name: "bootSplashBrand"
954
+ },
955
+ _: "@drawable/bootsplash_brand"
956
+ });
957
+ }
958
+ if (darkContentBarsStyle != null) {
959
+ item.push({
960
+ $: {
961
+ name: "darkContentBarsStyle"
962
+ },
963
+ _: String(darkContentBarsStyle)
964
+ });
965
+ }
966
+ const withBootTheme = [...style.filter(({
967
+ $
968
+ }) => $.name !== "BootTheme"), {
969
+ $: {
970
+ name: "BootTheme",
971
+ parent: "Theme.BootSplash"
972
+ },
973
+ item
974
+ }];
975
+ return {
976
+ ...config,
977
+ modResults: {
978
+ ...modResults,
979
+ resources: {
980
+ ...resources,
981
+ style: withBootTheme
982
+ }
983
+ }
984
+ };
985
+ });
986
+ const withAndroidColors = (config, props) => Expo.withAndroidColors(config, config => {
987
+ const {
988
+ background
989
+ } = props;
990
+ config.modResults = assignColorValue(config.modResults, {
991
+ name: "bootsplash_background",
992
+ value: background.hex
993
+ });
994
+ return config;
995
+ });
996
+ const withIOSAssets = (config, props) => Expo.withDangerousMod(config, ["ios", async config => {
997
+ const {
998
+ platformProjectRoot,
999
+ projectName = ""
1000
+ } = config.modRequest;
1001
+ const extras = await getExtras(props);
1002
+ const iosOutputPath = path.resolve(platformProjectRoot, projectName);
1003
+ await writeIOSAssets({
1004
+ iosOutputPath,
1005
+ props,
1006
+ extras
1007
+ });
1008
+ return config;
1009
+ }]);
1010
+ const withAppDelegate = config => Expo.withAppDelegate(config, config => {
1011
+ const {
1012
+ modResults
1013
+ } = config;
1014
+ const {
1015
+ language
1016
+ } = modResults;
1017
+ if (language !== "objc" && language !== "objcpp" && language !== "swift") {
1018
+ throw new Error(`Cannot modify the project AppDelegate as it's not in a supported language: ${language}`);
1019
+ }
1020
+ const withHeader = mergeContents({
1021
+ src: modResults.contents,
1022
+ comment: "//",
1023
+ tag: "bootsplash-header",
1024
+ offset: 1,
1025
+ anchor: /import Expo/,
1026
+ newSrc: "import RNBootSplash"
1027
+ });
1028
+ const withRootView = mergeContents({
1029
+ src: withHeader.contents,
1030
+ comment: "//",
1031
+ tag: "bootsplash-init",
1032
+ offset: 1,
1033
+ anchor: /class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {/,
1034
+ newSrc: dedent`
1035
+ public override func customize(_ rootView: UIView) {
1036
+ super.customize(rootView)
1037
+ RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
1038
+ }
1039
+ `
1040
+ });
1041
+ return {
1042
+ ...config,
1043
+ modResults: {
1044
+ ...modResults,
1045
+ contents: withRootView.contents
1046
+ }
1047
+ };
1048
+ });
1049
+ const withInfoPlist = config => Expo.withInfoPlist(config, config => {
1050
+ config.modResults["UILaunchStoryboardName"] = "BootSplash";
1051
+ return config;
1052
+ });
1053
+ const withXcodeProject = config => Expo.withXcodeProject(config, config => {
1054
+ const {
1055
+ projectName = ""
1056
+ } = config.modRequest;
1057
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
1058
+ filepath: path.join(projectName, "BootSplash.storyboard"),
1059
+ groupName: projectName,
1060
+ project: config.modResults,
1061
+ isBuildFile: true
1062
+ });
1063
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
1064
+ filepath: path.join(projectName, "Colors.xcassets"),
1065
+ groupName: projectName,
1066
+ project: config.modResults,
1067
+ isBuildFile: true
1068
+ });
1069
+ return config;
1070
+ });
1071
+ const withWebAssets = (config, props) => Expo.withDangerousMod(config, [config.platforms?.includes("ios") ? "ios" : "android", async config => {
1072
+ const extras = await getExtras(props);
1073
+ const fileName = "public/index.html";
1074
+ const htmlTemplatePath = path.resolve(workingPath, fileName);
1075
+ if (!hfs.exists(htmlTemplatePath)) {
1076
+ await exec(`npx expo customize ${fileName}`);
1077
+ }
1078
+ await writeWebAssets({
1079
+ htmlTemplatePath,
1080
+ props,
1081
+ extras
1082
+ });
1083
+ return config;
1084
+ }]);
1085
+ const withGenericAssets = (config, props) => Expo.withDangerousMod(config, [config.platforms?.includes("ios") ? "ios" : "android", async config => {
1086
+ const extras = await getExtras(props);
1087
+ await writeGenericAssets({
1088
+ props,
1089
+ extras
1090
+ });
1091
+ return config;
1092
+ }]);
1093
+ export const withBootSplash = Expo.createRunOncePlugin((config, baseProps) => {
1094
+ const {
1095
+ platforms = [],
1096
+ sdkVersion = "0.1.0"
1097
+ } = config;
1098
+ isExpo = true;
1099
+ if (semver.lt(sdkVersion, "53.0.0")) {
1100
+ log.error("Requires Expo 53.0.0 (or higher)");
1101
+ process.exit(1);
1102
+ }
1103
+ if (!baseProps?.logo) {
1104
+ log.error("Missing required parameter 'logo'");
1105
+ process.exit(1);
1106
+ }
1107
+ const props = getProps({
1108
+ assetsOutput: "assets/bootsplash",
1109
+ background: "#fff",
1110
+ brandWidth: 80,
1111
+ logoWidth: 100,
1112
+ ...baseProps
1113
+ });
1114
+ const {
1115
+ executeAddon
1116
+ } = props;
1117
+ const addon = executeAddon ? requireAddon() : undefined;
1118
+ const plugins = [withoutExpoSplashScreen, addon?.withGenericAssets ?? withGenericAssets];
1119
+ if (platforms.includes("android")) {
1120
+ plugins.push(addon?.withAndroidAssets ?? withAndroidAssets, withAndroidManifest, withMainActivity, withAndroidStyles, withAndroidColors);
1121
+ if (addon != null) {
1122
+ plugins.push(addon.withAndroidColorsNight);
1123
+ }
1124
+ }
1125
+ if (platforms.includes("ios")) {
1126
+ plugins.push(addon?.withIOSAssets ?? withIOSAssets, withAppDelegate, withInfoPlist, withXcodeProject);
1127
+ }
1128
+ if (platforms.includes("web")) {
1129
+ plugins.push(addon?.withWebAssets ?? withWebAssets);
1130
+ }
1131
+ return Expo.withPlugins(config, plugins.map(plugin => [plugin, props]));
1132
+ }, PACKAGE_NAME);
865
1133
  //# sourceMappingURL=generate.js.map