react-native-bootsplash 6.0.0-beta.0 → 6.0.0-beta.10

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 (37) hide show
  1. package/README.md +33 -88
  2. package/android/src/main/res/values/public.xml +1 -0
  3. package/android/src/main/res/values/styles.xml +9 -6
  4. package/android/src/main/res/values-night/styles.xml +1 -1
  5. package/android/src/main/res/values-v23/styles.xml +6 -1
  6. package/android/src/main/res/values-v27/styles.xml +1 -1
  7. package/android/src/main/res/values-v29/styles.xml +1 -1
  8. package/android/src/main/res/values-v30/styles.xml +1 -1
  9. package/app.plugin.js +2 -2
  10. package/dist/commonjs/addon/index.js +43 -38
  11. package/dist/commonjs/addon/index.js.map +1 -1
  12. package/dist/commonjs/expo.js +283 -0
  13. package/dist/commonjs/expo.js.map +1 -0
  14. package/dist/commonjs/generate.js +165 -323
  15. package/dist/commonjs/generate.js.map +1 -1
  16. package/dist/commonjs/index.js +3 -3
  17. package/dist/commonjs/index.js.map +1 -1
  18. package/dist/module/addon/index.js +43 -38
  19. package/dist/module/addon/index.js.map +1 -1
  20. package/dist/module/expo.js +273 -0
  21. package/dist/module/expo.js.map +1 -0
  22. package/dist/module/generate.js +162 -317
  23. package/dist/module/generate.js.map +1 -1
  24. package/dist/module/index.js +2 -2
  25. package/dist/module/index.js.map +1 -1
  26. package/dist/typescript/addon/index.d.ts.map +1 -1
  27. package/dist/typescript/expo.d.ts +11 -0
  28. package/dist/typescript/expo.d.ts.map +1 -0
  29. package/dist/typescript/generate.d.ts +27 -21
  30. package/dist/typescript/generate.d.ts.map +1 -1
  31. package/dist/typescript/index.d.ts +22 -4
  32. package/dist/typescript/index.d.ts.map +1 -1
  33. package/ios/RNBootSplash.mm +33 -37
  34. package/package.json +8 -8
  35. package/src/expo.ts +368 -0
  36. package/src/generate.ts +235 -403
  37. package/src/index.ts +32 -10
package/src/generate.ts CHANGED
@@ -1,33 +1,32 @@
1
- import murmurhash from "@emotion/hash";
2
- import { getConfig as getExpoConfig } from "@expo/config";
3
1
  import * as Expo from "@expo/config-plugins";
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
2
  import plist from "@expo/plist";
8
3
  import { findProjectRoot } from "@react-native-community/cli-tools";
9
4
  import {
10
5
  AndroidProjectConfig,
11
6
  IOSProjectConfig,
12
7
  } from "@react-native-community/cli-types";
8
+ import childProcess from "child_process";
9
+ import crypto from "crypto";
13
10
  import detectIndent from "detect-indent";
14
11
  import fs from "fs-extra";
15
- import { parse as parseHtml } from "node-html-parser";
12
+ import { HTMLElement, parse as parseHtml } from "node-html-parser";
16
13
  import path from "path";
17
14
  import pc from "picocolors";
18
15
  import { Options as PrettierOptions } from "prettier";
19
16
  import * as htmlPlugin from "prettier/plugins/html";
20
17
  import * as cssPlugin from "prettier/plugins/postcss";
21
18
  import * as prettier from "prettier/standalone";
19
+ import semver from "semver";
22
20
  import sharp, { Sharp } from "sharp";
23
21
  import { dedent } from "ts-dedent";
22
+ import util from "util";
24
23
  import formatXml, { XMLFormatterOptions } from "xml-formatter";
25
24
  import { Manifest } from ".";
26
25
 
27
26
  const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
28
27
  const projectRoot = findProjectRoot(workingPath);
29
28
 
30
- export type Platforms = ("android" | "ios" | "web")[];
29
+ type Platforms = ("android" | "ios" | "web")[];
31
30
 
32
31
  export type RGBColor = {
33
32
  R: string;
@@ -35,11 +34,16 @@ export type RGBColor = {
35
34
  B: string;
36
35
  };
37
36
 
38
- export type Color = {
37
+ type Color = {
39
38
  hex: string;
40
39
  rgb: RGBColor;
41
40
  };
42
41
 
42
+ const promisifiedExec = util.promisify(childProcess.exec);
43
+
44
+ const exec = (cmd: string) =>
45
+ promisifiedExec(cmd).then(({ stdout, stderr }) => stdout || stderr);
46
+
43
47
  export const log = {
44
48
  error: (text: string) => {
45
49
  console.log(pc.red(`❌ ${text}`));
@@ -60,7 +64,7 @@ export const log = {
60
64
  },
61
65
  };
62
66
 
63
- export const parseColor = (value: string): Color => {
67
+ const parseColor = (value: string): Color => {
64
68
  const up = value.toUpperCase().replace(/[^0-9A-F]/g, "");
65
69
 
66
70
  if (up.length !== 3 && up.length !== 6) {
@@ -180,70 +184,102 @@ export const hfs = {
180
184
  },
181
185
  };
182
186
 
183
- export const writeJson = (filePath: string, content: object) => {
184
- hfs.write(filePath, JSON.stringify(content, null, 2));
185
- log.write(filePath);
186
- };
187
+ export const getExpoConfig = (projectRoot: string): { isExpo: boolean } => {
188
+ try {
189
+ const pkg = hfs.json(
190
+ path.resolve(projectRoot, "node_modules", "expo", "package.json"),
191
+ ) as { version?: string };
187
192
 
188
- export const readXml = (filePath: string) => {
189
- const xml = hfs.text(filePath);
190
- const { indent } = detectIndent(xml);
193
+ const version = pkg.version;
191
194
 
192
- const formatOptions: XMLFormatterOptions = {
193
- indentation: indent || " ",
194
- };
195
+ if (version == null || semver.lt(version, "51.0.20")) {
196
+ log.error("Requires Expo 51.0.20 (or higher)");
197
+ process.exit(1);
198
+ }
195
199
 
196
- return { root: parseHtml(xml), formatOptions };
200
+ return { isExpo: true };
201
+ } catch {
202
+ return { isExpo: false };
203
+ }
197
204
  };
198
205
 
199
- export const writeXml = (
200
- filePath: string,
201
- content: string,
202
- options?: XMLFormatterOptions,
203
- ) => {
204
- const formatted = formatXml(content, {
205
- collapseContent: true,
206
- forceSelfClosingEmptyTag: true,
207
- indentation: " ",
208
- lineSeparator: "\n",
209
- whiteSpaceAtEndOfSelfclosingTag: true,
210
- ...options,
211
- });
212
-
213
- hfs.write(filePath, formatted);
206
+ export const writeJson = (filePath: string, content: object) => {
207
+ hfs.write(filePath, JSON.stringify(content, null, 2));
214
208
  log.write(filePath);
215
209
  };
216
210
 
217
- export const readHtml = (filePath: string) => {
218
- const html = hfs.text(filePath);
219
- const { type, amount } = detectIndent(html);
211
+ type FormatOptions = { indent?: detectIndent.Indent } & (
212
+ | {
213
+ formatter: "prettier";
214
+ selfClosingTags?: boolean;
215
+ useCssPlugin?: boolean;
216
+ htmlWhitespaceSensitivity?: PrettierOptions["htmlWhitespaceSensitivity"];
217
+ singleAttributePerLine?: PrettierOptions["singleAttributePerLine"];
218
+ }
219
+ | {
220
+ formatter: "xmlFormatter";
221
+ whiteSpaceAtEndOfSelfclosingTag?: XMLFormatterOptions["whiteSpaceAtEndOfSelfclosingTag"];
222
+ }
223
+ );
220
224
 
221
- const formatOptions: PrettierOptions = {
222
- useTabs: type === "tab",
223
- tabWidth: amount || 2,
224
- };
225
+ export const readXmlLike = (filePath: string) => {
226
+ const content = hfs.text(filePath);
225
227
 
226
- return { root: parseHtml(html), formatOptions };
228
+ return {
229
+ root: parseHtml(content),
230
+ formatOptions: { indent: detectIndent(content) },
231
+ };
227
232
  };
228
233
 
229
- export const writeHtml = async (
234
+ export const writeXmlLike = async (
230
235
  filePath: string,
231
236
  content: string,
232
- options?: Omit<PrettierOptions, "parser" | "plugins">,
237
+ { indent, ...formatOptions }: FormatOptions,
233
238
  ) => {
234
- const formatted = await prettier.format(content, {
235
- parser: "html",
236
- plugins: [htmlPlugin, cssPlugin],
237
- tabWidth: 2,
238
- useTabs: false,
239
- ...options,
240
- });
239
+ if (formatOptions.formatter === "prettier") {
240
+ const {
241
+ formatter,
242
+ useCssPlugin = false,
243
+ selfClosingTags = false,
244
+ ...options
245
+ } = formatOptions;
246
+
247
+ const formatted = await prettier.format(content, {
248
+ parser: "html",
249
+ bracketSameLine: true,
250
+ printWidth: 10000,
251
+ plugins: [htmlPlugin, ...(useCssPlugin ? [cssPlugin] : [])],
252
+ useTabs: indent?.type === "tab",
253
+ tabWidth: (indent?.amount ?? 0) || 2,
254
+ ...options,
255
+ });
241
256
 
242
- hfs.write(filePath, formatted);
243
- log.write(filePath);
257
+ hfs.write(
258
+ filePath,
259
+ selfClosingTags
260
+ ? formatted.replace(/><\/[a-z-0-9]+>/gi, " />")
261
+ : formatted,
262
+ );
263
+
264
+ log.write(filePath);
265
+ } else {
266
+ const { formatter, ...options } = formatOptions;
267
+
268
+ const formatted = formatXml(content, {
269
+ collapseContent: true,
270
+ forceSelfClosingEmptyTag: true,
271
+ lineSeparator: "\n",
272
+ whiteSpaceAtEndOfSelfclosingTag: true,
273
+ indentation: (indent?.indent ?? "") || " ",
274
+ ...options,
275
+ });
276
+
277
+ hfs.write(filePath, formatted);
278
+ log.write(filePath);
279
+ }
244
280
  };
245
281
 
246
- const cleanIOS = (dir: string) => {
282
+ export const cleanIOSAssets = (dir: string) => {
247
283
  hfs
248
284
  .readDir(dir)
249
285
  .filter((file) => file === "Colors.xcassets" || file === "Images.xcassets")
@@ -316,7 +352,11 @@ const getFileNameSuffix = async ({
316
352
  .map((key) => record[key])
317
353
  .join();
318
354
 
319
- return murmurhash(stableKey);
355
+ return crypto
356
+ .createHash("shake256", { outputLength: 3 })
357
+ .update(stableKey)
358
+ .digest("hex")
359
+ .toLowerCase();
320
360
  };
321
361
 
322
362
  const ensureSupportedFormat = async (
@@ -402,8 +442,8 @@ const getAndroidOutputPath = ({
402
442
  };
403
443
 
404
444
  const getIOSOutputPath = ({
405
- assetsOutputPath,
406
445
  ios,
446
+ assetsOutputPath,
407
447
  isExpo,
408
448
  platforms,
409
449
  }: {
@@ -441,10 +481,12 @@ const getIOSOutputPath = ({
441
481
  return iosOutputPath;
442
482
  };
443
483
 
444
- const getHtmlTemplatePath = ({
484
+ const getHtmlTemplatePath = async ({
485
+ isExpo,
445
486
  html,
446
487
  platforms,
447
488
  }: {
489
+ isExpo: boolean;
448
490
  html: string;
449
491
  platforms: Platforms;
450
492
  }) => {
@@ -452,6 +494,24 @@ const getHtmlTemplatePath = ({
452
494
  return;
453
495
  }
454
496
 
497
+ if (isExpo) {
498
+ const htmlTemplatePath = path.resolve(workingPath, html);
499
+
500
+ const htmlTemplateRelativePath = path.relative(
501
+ workingPath,
502
+ htmlTemplatePath,
503
+ );
504
+
505
+ if (
506
+ htmlTemplateRelativePath === "public/index.html" &&
507
+ !hfs.exists(htmlTemplatePath)
508
+ ) {
509
+ const cmd = `npx expo customize ${htmlTemplateRelativePath}`;
510
+ console.log(pc.dim(`Running ${cmd}`));
511
+ await exec(cmd);
512
+ }
513
+ }
514
+
455
515
  const htmlTemplatePath = path.resolve(workingPath, html);
456
516
 
457
517
  if (!hfs.exists(htmlTemplatePath)) {
@@ -466,7 +526,7 @@ const getHtmlTemplatePath = ({
466
526
  return htmlTemplatePath;
467
527
  };
468
528
 
469
- export const getImageHeight = (
529
+ const getImageHeight = (
470
530
  image: Sharp | undefined,
471
531
  width: number,
472
532
  ): Promise<number> => {
@@ -548,14 +608,9 @@ export const generate = async ({
548
608
  darkLogo?: string;
549
609
  darkBrand?: string;
550
610
  }) => {
551
- const isExpo =
552
- getExpoConfig(projectRoot, { skipSDKVersionRequirement: true }).exp
553
- .sdkVersion != null;
554
-
555
- const [nodeStringVersion = ""] = process.versions.node.split(".");
556
- const nodeVersion = Number.parseInt(nodeStringVersion, 10);
611
+ const { isExpo } = getExpoConfig(workingPath);
557
612
 
558
- if (!Number.isNaN(nodeVersion) && nodeVersion < 18) {
613
+ if (semver.lt(process.versions.node, "18.0.0")) {
559
614
  log.error("Requires Node 18 (or higher)");
560
615
  process.exit(1);
561
616
  }
@@ -670,7 +725,8 @@ export const generate = async ({
670
725
  platforms,
671
726
  });
672
727
 
673
- const htmlTemplatePath = getHtmlTemplatePath({
728
+ const htmlTemplatePath = await getHtmlTemplatePath({
729
+ isExpo,
674
730
  html,
675
731
  platforms,
676
732
  });
@@ -735,6 +791,29 @@ export const generate = async ({
735
791
  );
736
792
 
737
793
  if (!isExpo) {
794
+ const manifestXmlPath = path.resolve(
795
+ androidOutputPath,
796
+ "..",
797
+ "AndroidManifest.xml",
798
+ );
799
+
800
+ const manifestXml = readXmlLike(manifestXmlPath);
801
+ const activities = manifestXml.root.querySelectorAll("activity");
802
+
803
+ for (const activity of activities) {
804
+ if (activity.getAttribute("android:name") === ".MainActivity") {
805
+ activity.setAttribute("android:theme", "@style/BootTheme");
806
+ }
807
+ }
808
+
809
+ await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
810
+ ...manifestXml.formatOptions,
811
+ formatter: "prettier",
812
+ htmlWhitespaceSensitivity: "ignore",
813
+ selfClosingTags: true,
814
+ singleAttributePerLine: true,
815
+ });
816
+
738
817
  const valuesPath = path.resolve(androidOutputPath, "values");
739
818
  hfs.ensureDir(valuesPath);
740
819
 
@@ -742,23 +821,86 @@ export const generate = async ({
742
821
  const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
743
822
 
744
823
  if (hfs.exists(colorsXmlPath)) {
745
- const { root, formatOptions } = readXml(colorsXmlPath);
824
+ const colorsXml = readXmlLike(colorsXmlPath);
746
825
  const nextColor = parseHtml(colorsXmlEntry);
747
826
 
748
- const prevColor = root.querySelector(
827
+ const prevColor = colorsXml.root.querySelector(
749
828
  'color[name="bootsplash_background"]',
750
829
  );
751
830
 
752
831
  if (prevColor != null) {
753
832
  prevColor.replaceWith(nextColor);
754
833
  } else {
755
- root.querySelector("resources")?.appendChild(nextColor);
834
+ colorsXml.root.querySelector("resources")?.appendChild(nextColor);
756
835
  }
757
836
 
758
- writeXml(colorsXmlPath, root.toString(), formatOptions);
837
+ await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
838
+ ...colorsXml.formatOptions,
839
+ formatter: "xmlFormatter",
840
+ });
759
841
  } else {
760
- writeXml(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`);
842
+ await writeXmlLike(
843
+ colorsXmlPath,
844
+ `<resources>${colorsXmlEntry}</resources>`,
845
+ { formatter: "xmlFormatter" },
846
+ );
761
847
  }
848
+
849
+ const stylesXmlPath = path.resolve(valuesPath, "styles.xml");
850
+ const stylesXml = readXmlLike(stylesXmlPath);
851
+
852
+ const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
853
+ const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
854
+
855
+ const extraItems = parseHtml(
856
+ prevStyle?.text
857
+ .split("\n")
858
+ .map((line) => line.trim())
859
+ .join("") ?? "",
860
+ )
861
+ .childNodes.filter((node) => {
862
+ if (!(node instanceof HTMLElement)) {
863
+ return true;
864
+ }
865
+
866
+ const name = node.getAttribute("name");
867
+
868
+ return (
869
+ name !== "bootSplashBackground" &&
870
+ name !== "bootSplashLogo" &&
871
+ name !== "bootSplashBrand" &&
872
+ name !== "postBootSplashTheme"
873
+ );
874
+ })
875
+ .map((node) => node.toString());
876
+
877
+ const styleItems: string[] = [
878
+ ...(extraItems.length > 0 ? [...extraItems, ""] : []),
879
+
880
+ '<item name="bootSplashBackground">@color/bootsplash_background</item>',
881
+ '<item name="bootSplashLogo">@drawable/bootsplash_logo</item>',
882
+
883
+ ...(brand != null && brandPath != null
884
+ ? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>']
885
+ : []),
886
+
887
+ '<item name="postBootSplashTheme">@style/AppTheme</item>',
888
+ ];
889
+
890
+ const nextStyle = parseHtml(dedent`
891
+ <style name="BootTheme" parent="${parent}">
892
+ ${styleItems.join("\n")}
893
+ </style>
894
+ `);
895
+
896
+ prevStyle?.remove(); // remove the existing style
897
+ stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
898
+
899
+ await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
900
+ ...stylesXml.formatOptions,
901
+ formatter: "prettier",
902
+ htmlWhitespaceSensitivity: "ignore",
903
+ });
762
904
  }
763
905
  }
764
906
 
@@ -766,7 +908,7 @@ export const generate = async ({
766
908
  log.title("🍏", "iOS");
767
909
 
768
910
  hfs.ensureDir(iosOutputPath);
769
- cleanIOS(iosOutputPath);
911
+ cleanIOSAssets(iosOutputPath);
770
912
 
771
913
  const storyboardPath = path.resolve(iosOutputPath, "BootSplash.storyboard");
772
914
 
@@ -785,7 +927,7 @@ export const generate = async ({
785
927
  hfs.ensureDir(colorsSetPath);
786
928
  hfs.ensureDir(imageSetPath);
787
929
 
788
- writeXml(
930
+ await writeXmlLike(
789
931
  storyboardPath,
790
932
  getStoryboard({
791
933
  logoHeight,
@@ -793,7 +935,10 @@ export const generate = async ({
793
935
  background: background.rgb,
794
936
  fileNameSuffix,
795
937
  }),
796
- { whiteSpaceAtEndOfSelfclosingTag: false },
938
+ {
939
+ formatter: "xmlFormatter",
940
+ whiteSpaceAtEndOfSelfclosingTag: false,
941
+ },
797
942
  );
798
943
 
799
944
  writeJson(path.resolve(colorsSetPath, "Contents.json"), {
@@ -896,18 +1041,19 @@ export const generate = async ({
896
1041
 
897
1042
  const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
898
1043
  const projectName = path.basename(iosOutputPath);
1044
+ const groupName = path.parse(xcodeProjectPath).name;
899
1045
 
900
1046
  Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
901
- filepath: path.join(projectName, "BootSplash.storyboard"),
902
- groupName: path.parse(xcodeProjectPath).name,
903
1047
  project,
1048
+ filepath: path.join(projectName, "BootSplash.storyboard"),
1049
+ groupName,
904
1050
  isBuildFile: true,
905
1051
  });
906
1052
 
907
1053
  Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
908
- filepath: path.join(projectName, "Colors.xcassets"),
909
- groupName: path.parse(xcodeProjectPath).name,
910
1054
  project,
1055
+ filepath: path.join(projectName, "Colors.xcassets"),
1056
+ groupName,
911
1057
  isBuildFile: true,
912
1058
  });
913
1059
 
@@ -919,9 +1065,9 @@ export const generate = async ({
919
1065
  if (htmlTemplatePath != null) {
920
1066
  log.title("🌐", "Web");
921
1067
 
922
- const { root, formatOptions } = readHtml(htmlTemplatePath);
1068
+ const htmlTemplate = readXmlLike(htmlTemplatePath);
923
1069
  const { format } = await logo.metadata();
924
- const prevStyle = root.querySelector("#bootsplash-style");
1070
+ const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
925
1071
 
926
1072
  const base64 = (
927
1073
  format === "svg"
@@ -960,10 +1106,10 @@ export const generate = async ({
960
1106
  if (prevStyle != null) {
961
1107
  prevStyle.replaceWith(nextStyle);
962
1108
  } else {
963
- root.querySelector("head")?.appendChild(nextStyle);
1109
+ htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
964
1110
  }
965
1111
 
966
- const prevDiv = root.querySelector("#bootsplash");
1112
+ const prevDiv = htmlTemplate.root.querySelector("#bootsplash");
967
1113
 
968
1114
  const nextDiv = parseHtml(dedent`
969
1115
  <div id="bootsplash">
@@ -974,10 +1120,14 @@ export const generate = async ({
974
1120
  if (prevDiv != null) {
975
1121
  prevDiv.replaceWith(nextDiv);
976
1122
  } else {
977
- root.querySelector("body")?.appendChild(nextDiv);
1123
+ htmlTemplate.root.querySelector("body")?.appendChild(nextDiv);
978
1124
  }
979
1125
 
980
- await writeHtml(htmlTemplatePath, root.toString(), formatOptions);
1126
+ await writeXmlLike(htmlTemplatePath, htmlTemplate.root.toString(), {
1127
+ ...htmlTemplate.formatOptions,
1128
+ formatter: "prettier",
1129
+ useCssPlugin: true,
1130
+ });
981
1131
  }
982
1132
 
983
1133
  log.title("📄", "Assets");
@@ -1060,321 +1210,3 @@ ${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━
1060
1210
  `\n💖 Thanks for using ${pc.underline("react-native-bootsplash")}`,
1061
1211
  );
1062
1212
  };
1063
-
1064
- export type ExpoProps = {
1065
- assetsDir?: string;
1066
- edgeToEdge?: boolean;
1067
- };
1068
-
1069
- export type ExpoPlugin = Expo.ConfigPlugin<ExpoProps>;
1070
-
1071
- const withAndroidAssets: ExpoPlugin = (config, props) =>
1072
- Expo.withDangerousMod(config, [
1073
- "android",
1074
- (config) => {
1075
- const { assetsDir = "assets/bootsplash" } = props;
1076
- const { platformProjectRoot } = config.modRequest;
1077
-
1078
- const srcDir = path.resolve(workingPath, assetsDir, "android");
1079
-
1080
- const destDir = path.resolve(
1081
- platformProjectRoot,
1082
- "app",
1083
- "src",
1084
- "main",
1085
- "res",
1086
- );
1087
-
1088
- for (const drawableDir of hfs.readDir(srcDir)) {
1089
- const srcDrawableDir = path.join(srcDir, drawableDir);
1090
- const destDrawableDir = path.join(destDir, drawableDir);
1091
-
1092
- hfs.ensureDir(destDrawableDir);
1093
-
1094
- for (const file of hfs.readDir(srcDrawableDir)) {
1095
- hfs.copy(
1096
- path.join(srcDrawableDir, file),
1097
- path.join(destDrawableDir, file),
1098
- );
1099
- }
1100
- }
1101
-
1102
- return config;
1103
- },
1104
- ]);
1105
-
1106
- const withAndroidManifest: ExpoPlugin = (config) =>
1107
- Expo.withAndroidManifest(config, (config) => {
1108
- config.modResults.manifest.application?.forEach((application) => {
1109
- if (application.$["android:name"] === ".MainApplication") {
1110
- const { activity } = application;
1111
-
1112
- activity?.forEach((activity) => {
1113
- if (activity.$["android:name"] === ".MainActivity") {
1114
- activity.$["android:theme"] = "@style/BootTheme";
1115
- }
1116
- });
1117
- }
1118
- });
1119
-
1120
- return config;
1121
- });
1122
-
1123
- const withMainActivity: ExpoPlugin = (config) =>
1124
- Expo.withMainActivity(config, (config) => {
1125
- const { modResults } = config;
1126
- const { language } = modResults;
1127
-
1128
- const withImports = addImports(
1129
- modResults.contents.replace(
1130
- /(\/\/ )?setTheme\(R\.style\.AppTheme\)/,
1131
- "// setTheme(R.style.AppTheme)",
1132
- ),
1133
- ["android.os.Bundle", "com.zoontek.rnbootsplash.RNBootSplash"],
1134
- language === "java",
1135
- );
1136
-
1137
- // indented with 4 spaces
1138
- const withInit = mergeContents({
1139
- src: withImports,
1140
- comment: " //",
1141
- tag: "bootsplash-init",
1142
- offset: 0,
1143
- anchor: /super\.onCreate\(null\)/,
1144
- newSrc:
1145
- " RNBootSplash.init(this, R.style.BootTheme)" +
1146
- (language === "java" ? ";" : ""),
1147
- });
1148
-
1149
- return {
1150
- ...config,
1151
- modResults: {
1152
- ...modResults,
1153
- contents: withInit.contents,
1154
- },
1155
- };
1156
- });
1157
-
1158
- const withAndroidStyles: ExpoPlugin = (config, props) =>
1159
- Expo.withAndroidStyles(config, async (config) => {
1160
- const { assetsDir = "assets/bootsplash", edgeToEdge = false } = props;
1161
-
1162
- const manifest = (await hfs.json(
1163
- path.resolve(workingPath, assetsDir, "manifest.json"),
1164
- )) as Manifest;
1165
-
1166
- const item = [
1167
- {
1168
- $: { name: "postBootSplashTheme" },
1169
- _: "@style/AppTheme",
1170
- },
1171
- {
1172
- $: { name: "bootSplashBackground" },
1173
- _: "@color/bootsplash_background",
1174
- },
1175
- {
1176
- $: { name: "bootSplashLogo" },
1177
- _: "@drawable/bootsplash_logo",
1178
- },
1179
- ];
1180
-
1181
- if (manifest.brand != null) {
1182
- item.push({
1183
- $: { name: "bootSplashBrand" },
1184
- _: "@drawable/bootsplash_brand",
1185
- });
1186
- }
1187
-
1188
- config.modResults.resources.style
1189
- ?.filter(({ $ }) => $.name !== "BootTheme")
1190
- .push({
1191
- item,
1192
- $: {
1193
- name: "BootTheme",
1194
- parent: edgeToEdge
1195
- ? "Theme.BootSplash"
1196
- : "Theme.BootSplash.EdgeToEdge",
1197
- },
1198
- });
1199
-
1200
- return config;
1201
- });
1202
-
1203
- const withAndroidColors: ExpoPlugin = (config, props) =>
1204
- Expo.withAndroidColors(config, async (config) => {
1205
- const { assetsDir = "assets/bootsplash" } = props;
1206
-
1207
- const manifest = (await hfs.json(
1208
- path.resolve(workingPath, assetsDir, "manifest.json"),
1209
- )) as Manifest;
1210
-
1211
- config.modResults = assignColorValue(config.modResults, {
1212
- name: "bootsplash_background",
1213
- value: manifest.background,
1214
- });
1215
-
1216
- return config;
1217
- });
1218
-
1219
- const withAndroidColorsNight: ExpoPlugin = (config, props) =>
1220
- Expo.withAndroidColorsNight(config, async (config) => {
1221
- const { assetsDir = "assets/bootsplash" } = props;
1222
-
1223
- const manifest = (await hfs.json(
1224
- path.resolve(workingPath, assetsDir, "manifest.json"),
1225
- )) as Manifest;
1226
-
1227
- if (manifest.darkBackground != null) {
1228
- config.modResults = assignColorValue(config.modResults, {
1229
- name: "bootsplash_background",
1230
- value: manifest.darkBackground,
1231
- });
1232
- }
1233
-
1234
- return config;
1235
- });
1236
-
1237
- const withIOSAssets: ExpoPlugin = (config, props) =>
1238
- Expo.withDangerousMod(config, [
1239
- "ios",
1240
- (config) => {
1241
- const { assetsDir = "assets/bootsplash" } = props;
1242
- const { platformProjectRoot, projectName = "" } = config.modRequest;
1243
-
1244
- const srcDir = path.resolve(workingPath, assetsDir, "ios");
1245
- const destDir = path.resolve(platformProjectRoot, projectName);
1246
-
1247
- cleanIOS(destDir);
1248
-
1249
- hfs.copy(
1250
- path.join(srcDir, "BootSplash.storyboard"),
1251
- path.join(destDir, "BootSplash.storyboard"),
1252
- );
1253
-
1254
- for (const xcassetsDir of ["Colors.xcassets", "Images.xcassets"]) {
1255
- const srcXcassetsDir = path.join(srcDir, xcassetsDir);
1256
- const destXcassetsDir = path.join(destDir, xcassetsDir);
1257
-
1258
- hfs.ensureDir(destXcassetsDir);
1259
-
1260
- for (const file of hfs.readDir(srcXcassetsDir)) {
1261
- hfs.copy(
1262
- path.join(srcXcassetsDir, file),
1263
- path.join(destXcassetsDir, file),
1264
- );
1265
- }
1266
- }
1267
-
1268
- return config;
1269
- },
1270
- ]);
1271
-
1272
- const withAppDelegate: ExpoPlugin = (config) =>
1273
- Expo.withAppDelegate(config, (config) => {
1274
- const [sdkStringVersion = ""] = config.sdkVersion?.split(".") ?? "";
1275
- const isAtLeastExpo51 = Number.parseInt(sdkStringVersion, 10) >= 51;
1276
-
1277
- const { modResults } = config;
1278
- const { language } = modResults;
1279
-
1280
- if (language !== "objc" && language !== "objcpp") {
1281
- throw new Error(
1282
- `Cannot modify the project AppDelegate as it's not in a supported language: ${language}`,
1283
- );
1284
- }
1285
-
1286
- const withHeader = mergeContents({
1287
- src: modResults.contents,
1288
- comment: "//",
1289
- tag: "bootsplash-header",
1290
- offset: 1,
1291
- anchor: /#import "AppDelegate\.h"/,
1292
- newSrc: '#import "RNBootSplash.h"',
1293
- });
1294
-
1295
- const withRootView = mergeContents({
1296
- src: withHeader.contents,
1297
- comment: "//",
1298
- tag: "bootsplash-init",
1299
- offset: 0,
1300
- anchor: /@end/,
1301
- newSrc: isAtLeastExpo51
1302
- ? dedent`
1303
- - (void)customizeRootView:(RCTRootView *)rootView {
1304
- [RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];
1305
- }
1306
- `
1307
- : dedent`
1308
- - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initProps:(NSDictionary *)initProps {
1309
- UIView *rootView = [super createRootViewWithBridge:bridge moduleName:moduleName initProps:initProps];
1310
- [RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];
1311
- return rootView;
1312
- }
1313
- `,
1314
- });
1315
-
1316
- return {
1317
- ...config,
1318
- modResults: {
1319
- ...modResults,
1320
- contents: withRootView.contents,
1321
- },
1322
- };
1323
- });
1324
-
1325
- const withInfoPlist: ExpoPlugin = (config) =>
1326
- Expo.withInfoPlist(config, (config) => {
1327
- config.modResults["UILaunchStoryboardName"] = "BootSplash";
1328
- return config;
1329
- });
1330
-
1331
- const withXcodeProject: ExpoPlugin = (config) =>
1332
- Expo.withXcodeProject(config, (config) => {
1333
- const { projectName = "" } = config.modRequest;
1334
-
1335
- Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
1336
- filepath: path.join(projectName, "BootSplash.storyboard"),
1337
- groupName: projectName,
1338
- project: config.modResults,
1339
- isBuildFile: true,
1340
- });
1341
-
1342
- Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
1343
- filepath: path.join(projectName, "Colors.xcassets"),
1344
- groupName: projectName,
1345
- project: config.modResults,
1346
- isBuildFile: true,
1347
- });
1348
-
1349
- return config;
1350
- });
1351
-
1352
- export const withGenerate: ExpoPlugin = (config, props = {}) => {
1353
- const plugins: ExpoPlugin[] = [];
1354
- const { platforms = [] } = config;
1355
-
1356
- if (platforms.includes("android")) {
1357
- plugins.push(
1358
- withAndroidAssets,
1359
- withAndroidManifest,
1360
- withMainActivity,
1361
- withAndroidStyles,
1362
- withAndroidColors,
1363
- withAndroidColorsNight,
1364
- );
1365
- }
1366
-
1367
- if (platforms.includes("ios")) {
1368
- plugins.push(
1369
- withIOSAssets,
1370
- withAppDelegate,
1371
- withInfoPlist,
1372
- withXcodeProject,
1373
- );
1374
- }
1375
-
1376
- return Expo.withPlugins(
1377
- config,
1378
- plugins.map((plugin) => [plugin, props]),
1379
- );
1380
- };