react-native-bootsplash 4.7.4 → 5.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 (54) hide show
  1. package/README.md +214 -107
  2. package/RNBootSplash.podspec +11 -11
  3. package/android/.npmignore +11 -0
  4. package/android/build.gradle +0 -1
  5. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplash.java +3 -2
  6. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashDialog.java +49 -0
  7. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashModuleImpl.java +157 -87
  8. package/android/src/main/res/anim/bootsplash_fade_in.xml +7 -0
  9. package/android/src/main/res/anim/bootsplash_fade_out.xml +7 -0
  10. package/android/src/main/res/drawable/compat_splash_screen.xml +19 -0
  11. package/android/src/main/res/values/attrs.xml +8 -0
  12. package/android/src/main/res/values/public.xml +7 -0
  13. package/android/src/main/res/values/styles.xml +29 -0
  14. package/android/src/main/res/values-v31/styles.xml +7 -0
  15. package/android/src/newarch/com/zoontek/rnbootsplash/RNBootSplashModule.java +11 -4
  16. package/android/src/oldarch/com/zoontek/rnbootsplash/RNBootSplashModule.java +13 -4
  17. package/dist/commonjs/NativeRNBootSplash.js.map +1 -1
  18. package/dist/commonjs/addon/index.js +3 -0
  19. package/dist/commonjs/addon/index.js.map +1 -0
  20. package/dist/commonjs/generate.js +289 -194
  21. package/dist/commonjs/generate.js.map +1 -1
  22. package/dist/commonjs/index.js +115 -7
  23. package/dist/commonjs/index.js.map +1 -1
  24. package/dist/module/NativeRNBootSplash.js.map +1 -1
  25. package/dist/module/addon/index.js +3 -0
  26. package/dist/module/addon/index.js.map +1 -0
  27. package/dist/module/generate.js +287 -195
  28. package/dist/module/generate.js.map +1 -1
  29. package/dist/module/index.js +113 -6
  30. package/dist/module/index.js.map +1 -1
  31. package/dist/typescript/NativeRNBootSplash.d.ts +6 -3
  32. package/dist/typescript/NativeRNBootSplash.d.ts.map +1 -1
  33. package/dist/typescript/addon/index.d.ts +3 -0
  34. package/dist/typescript/addon/index.d.ts.map +1 -0
  35. package/dist/typescript/generate.d.ts +47 -14
  36. package/dist/typescript/generate.d.ts.map +1 -1
  37. package/dist/typescript/index.d.ts +33 -5
  38. package/dist/typescript/index.d.ts.map +1 -1
  39. package/example/android/app/debug.keystore +0 -0
  40. package/example/vendor/bundle/ruby/2.7.0/gems/ffi-1.15.5/ext/ffi_c/libffi/.travis/compile +351 -0
  41. package/example/vendor/bundle/ruby/2.7.0/gems/ffi-1.15.5/ext/ffi_c/libffi/testsuite/libffi.bhaible/Makefile +28 -0
  42. package/ios/.DS_Store +0 -0
  43. package/ios/.npmignore +8 -0
  44. package/ios/RNBootSplash.mm +101 -95
  45. package/package.json +22 -15
  46. package/react-native.config.js +74 -76
  47. package/src/.DS_Store +0 -0
  48. package/src/.npmignore +1 -0
  49. package/src/NativeRNBootSplash.ts +3 -4
  50. package/src/addon/index.ts +788 -0
  51. package/src/generate.ts +457 -232
  52. package/src/index.ts +205 -8
  53. package/RNBootSplash.res +0 -29
  54. package/bsconfig.json +0 -14
package/src/generate.ts CHANGED
@@ -1,57 +1,92 @@
1
+ import {
2
+ AndroidProjectConfig,
3
+ CommandFunction,
4
+ IOSProjectConfig,
5
+ } from "@react-native-community/cli-types";
1
6
  import fs from "fs-extra";
2
7
  import path from "path";
3
8
  import pc from "picocolors";
9
+ import type { Sharp } from "sharp";
4
10
  import sharp from "sharp";
11
+ import type { Manifest } from ".";
12
+
13
+ export type Color = {
14
+ hex: string;
15
+ rgb: {
16
+ R: string;
17
+ G: string;
18
+ B: string;
19
+ };
20
+ };
21
+
22
+ export const androidColorRegex =
23
+ /<color name="bootsplash_background">#\w+<\/color>/g;
24
+
25
+ const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
26
+
27
+ const parseColor = (value: string): Color => {
28
+ const up = value.toUpperCase().replace(/[^0-9A-F]/g, "");
29
+ const hex = "#" + (up.length === 3 ? up + up : up);
30
+
31
+ if (hex.length !== 7) {
32
+ log.error("--background-color value is not a valid hexadecimal color.");
33
+ process.exit(1);
34
+ }
35
+
36
+ const rgb: Color["rgb"] = {
37
+ R: (parseInt("" + hex[1] + hex[2], 16) / 255).toPrecision(15),
38
+ G: (parseInt("" + hex[3] + hex[4], 16) / 255).toPrecision(15),
39
+ B: (parseInt("" + hex[5] + hex[6], 16) / 255).toPrecision(15),
40
+ };
5
41
 
6
- const logoFileName = "bootsplash_logo";
7
- const xcassetName = "BootSplashLogo";
8
- const androidColorName = "bootsplash_background";
9
- const androidColorRegex = /<color name="bootsplash_background">#\w+<\/color>/g;
42
+ return { hex, rgb };
43
+ };
10
44
 
11
45
  const ContentsJson = `{
12
46
  "images": [
13
47
  {
14
48
  "idiom": "universal",
15
- "filename": "${logoFileName}.png",
49
+ "filename": "bootsplash_logo.png",
16
50
  "scale": "1x"
17
51
  },
18
52
  {
19
53
  "idiom": "universal",
20
- "filename": "${logoFileName}@2x.png",
54
+ "filename": "bootsplash_logo@2x.png",
21
55
  "scale": "2x"
22
56
  },
23
57
  {
24
58
  "idiom": "universal",
25
- "filename": "${logoFileName}@3x.png",
59
+ "filename": "bootsplash_logo@3x.png",
26
60
  "scale": "3x"
27
61
  }
28
62
  ],
29
63
  "info": {
30
- "version": 1,
31
- "author": "xcode"
64
+ "author": "xcode",
65
+ "version": 1
32
66
  }
33
67
  }
34
68
  `;
35
69
 
36
70
  const getStoryboard = ({
37
- height,
38
- width,
39
- backgroundColor: hex,
71
+ logoHeight,
72
+ logoWidth,
73
+ background: { R, G, B },
40
74
  }: {
41
- height: number;
42
- width: number;
43
- backgroundColor: string;
75
+ logoHeight: number;
76
+ logoWidth: number;
77
+ background: Color["rgb"];
44
78
  }) => {
45
- const r = (parseInt("" + hex[1] + hex[2], 16) / 255).toPrecision(15);
46
- const g = (parseInt("" + hex[3] + hex[4], 16) / 255).toPrecision(15);
47
- const b = (parseInt("" + hex[5] + hex[6], 16) / 255).toPrecision(15);
79
+ const frameWidth = 375;
80
+ const frameHeight = 667;
81
+ const logoX = (frameWidth - logoWidth) / 2;
82
+ const logoY = (frameHeight - logoHeight) / 2;
48
83
 
49
84
  return `<?xml version="1.0" encoding="UTF-8"?>
50
- <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
85
+ <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">
51
86
  <device id="retina4_7" orientation="portrait" appearance="light"/>
52
87
  <dependencies>
53
88
  <deployment identifier="iOS"/>
54
- <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
89
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
55
90
  <capability name="Safe area layout guides" minToolsVersion="9.0"/>
56
91
  <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
57
92
  </dependencies>
@@ -60,24 +95,19 @@ const getStoryboard = ({
60
95
  <scene sceneID="EHf-IW-A2E">
61
96
  <objects>
62
97
  <viewController modalTransitionStyle="crossDissolve" id="01J-lp-oVM" sceneMemberID="viewController">
63
- <view key="view" autoresizesSubviews="NO" userInteractionEnabled="NO" contentMode="scaleToFill" id="Ze5-6b-2t3">
64
- <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
65
- <autoresizingMask key="autoresizingMask"/>
98
+ <view key="view" autoresizesSubviews="NO" contentMode="scaleToFill" id="Ze5-6b-2t3">
99
+ <rect key="frame" x="0.0" y="0.0" width="${frameWidth}" height="${frameHeight}"/>
100
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
66
101
  <subviews>
67
102
  <imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
68
- <rect key="frame" x="${(375 - width) / 2}" y="${
69
- (667 - height) / 2
70
- }" width="${width}" height="${height}"/>
103
+ <rect key="frame" x="${logoX}" y="${logoY}" width="${logoWidth}" height="${logoHeight}"/>
71
104
  <accessibility key="accessibilityConfiguration">
72
105
  <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
73
106
  </accessibility>
74
107
  </imageView>
75
108
  </subviews>
76
109
  <viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
77
- <color key="backgroundColor" red="${r}" green="${g}" blue="${b}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
78
- <accessibility key="accessibilityConfiguration">
79
- <accessibilityTraits key="traits" notEnabled="YES"/>
80
- </accessibility>
110
+ <color key="backgroundColor" red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
81
111
  <constraints>
82
112
  <constraint firstItem="3lX-Ut-9ad" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="Fh9-Fy-1nT"/>
83
113
  <constraint firstItem="3lX-Ut-9ad" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="nvB-Ic-PnI"/>
@@ -90,164 +120,284 @@ const getStoryboard = ({
90
120
  </scene>
91
121
  </scenes>
92
122
  <resources>
93
- <image name="${xcassetName}" width="${width}" height="${height}"/>
123
+ <image name="BootSplashLogo" width="${logoWidth}" height="${logoHeight}"/>
94
124
  </resources>
95
125
  </document>
96
126
  `;
97
127
  };
98
128
 
99
- const log = {
100
- error: (text: string) => console.log(pc.red(text)),
129
+ export const log = {
130
+ error: (text: string) => console.log(pc.red(`❌ ${text}`)),
101
131
  text: (text: string) => console.log(text),
102
- warn: (text: string) => console.log(pc.yellow(text)),
132
+ title: (emoji: string, text: string) =>
133
+ console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`),
134
+ warn: (text: string) => console.log(pc.yellow(`⚠️ ${text}`)),
103
135
  };
104
136
 
105
- const toFullHexadecimal = (hex: string) => {
106
- const up = hex.toUpperCase().replace(/[^0-9A-F]/g, "");
137
+ export const logWrite = (
138
+ filePath: string,
139
+ dimensions?: { width: number; height: number },
140
+ ) =>
141
+ console.log(
142
+ ` ${path.relative(workingPath, filePath)}` +
143
+ (dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : ""),
144
+ );
107
145
 
108
- if (up.length === 6) {
109
- return "#" + up;
110
- }
111
- if (up.length === 3) {
112
- return "#" + up[0] + up[0] + up[1] + up[1] + up[2] + up[2];
146
+ const ensureSupportedFormat = async (
147
+ name: string,
148
+ image: Sharp | undefined,
149
+ ) => {
150
+ if (image == null) {
151
+ return;
113
152
  }
114
153
 
115
- log.error("--background-color value is not a valid hexadecimal color.");
116
- process.exit(1);
154
+ const { format } = await image.metadata();
155
+
156
+ if (format !== "png" && format !== "svg") {
157
+ log.error(`${name} image file format (${format}) is not supported`);
158
+ process.exit(1);
159
+ }
117
160
  };
118
161
 
119
- export const generate = async ({
120
- android,
121
- ios,
162
+ const getAndroidResPath = (
163
+ android: AndroidProjectConfig,
164
+ {
165
+ brandHeight,
166
+ brandWidth,
167
+ flavor,
168
+ logoHeight,
169
+ logoWidth,
170
+ }: {
171
+ brandHeight: number;
172
+ brandWidth: number;
173
+ flavor: string;
174
+ logoHeight: number;
175
+ logoWidth: number;
176
+ },
177
+ ): string | undefined => {
178
+ const androidResPath = path.resolve(
179
+ android.sourceDir,
180
+ android.appName,
181
+ "src",
182
+ flavor,
183
+ "res",
184
+ );
122
185
 
123
- workingPath,
124
- logoPath,
125
- assetsPath,
186
+ if (!fs.existsSync(androidResPath)) {
187
+ log.warn(
188
+ `No ${path.relative(
189
+ workingPath,
190
+ androidResPath,
191
+ )} directory found. Skipping Android assets generation…`,
192
+ );
193
+ } else if (logoWidth > 288 || logoHeight > 288) {
194
+ log.warn(
195
+ "Logo size exceeding 288x288dp will be cropped by Android. Skipping Android assets generation…",
196
+ );
197
+ } else if (brandHeight > 80 || brandWidth > 200) {
198
+ log.warn(
199
+ "Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…",
200
+ );
201
+ } else {
202
+ if (logoWidth > 192 || logoHeight > 192) {
203
+ log.warn(`Logo size exceeds 192x192dp. It might be cropped by Android.`);
204
+ }
126
205
 
127
- backgroundColor,
128
- flavor,
129
- logoWidth,
130
- platforms,
131
- }: {
132
- android: {
133
- sourceDir: string;
134
- appName: string;
135
- } | null;
136
- ios: {
137
- projectPath: string;
138
- } | null;
139
-
140
- workingPath: string;
141
- logoPath: string;
142
- assetsPath?: string;
143
-
144
- backgroundColor: string;
145
- flavor: string;
146
- logoWidth: number;
147
- platforms: string[];
148
- }) => {
149
- const platformsIncludesAndroid = platforms.includes("android");
150
- const platformsIncludesIOS = platforms.includes("ios");
206
+ return androidResPath;
207
+ }
208
+ };
151
209
 
152
- if (!platformsIncludesAndroid && !platformsIncludesIOS) {
153
- log.error(
154
- "--platforms value does not include at least one supported platform.",
155
- );
156
- process.exit(1);
210
+ const getIOSProjectPath = (ios: IOSProjectConfig): string | undefined => {
211
+ if (ios.xcodeProject == null) {
212
+ log.warn("No Xcode project found. Skipping iOS assets generation…");
213
+ return;
157
214
  }
158
215
 
159
- const backgroundColorHex = toFullHexadecimal(backgroundColor);
160
- const image = sharp(logoPath);
161
- const { format } = await image.metadata();
216
+ const iosProjectPath = path
217
+ .resolve(ios.sourceDir, ios.xcodeProject.name)
218
+ .replace(/\.(xcodeproj|xcworkspace)$/, "");
162
219
 
163
- if (format !== "png" && format !== "svg") {
164
- log.error("Input file is an unsupported image format");
165
- process.exit(1);
220
+ if (!fs.existsSync(iosProjectPath)) {
221
+ log.warn(
222
+ `No ${path.relative(
223
+ workingPath,
224
+ iosProjectPath,
225
+ )} directory found. Skipping iOS assets generation…`,
226
+ );
227
+ } else {
228
+ return iosProjectPath;
166
229
  }
230
+ };
167
231
 
168
- const logoHeight = await image
169
- .clone()
170
- .resize(logoWidth)
171
- .toBuffer()
172
- .then((buffer) => sharp(buffer).metadata())
173
- .then(({ height = 0 }) => height);
174
-
175
- const shouldSkipAndroid = logoWidth > 288 || logoHeight > 288;
232
+ export type AddonConfig = {
233
+ licenseKey: string;
176
234
 
177
- const logAbove288 = (dimension: "height" | "width") => {
178
- const message = `⚠️ Logo ${dimension} exceed 288dp. As it will be cropped by Android, we skip generation for this platform.`;
179
- log.warn(message);
180
- };
235
+ androidResPath: string | undefined;
236
+ iosProjectPath: string | undefined;
237
+ assetsOutputPath: string | undefined;
181
238
 
182
- const logAbove192 = (dimension: "height" | "width") => {
183
- const message = `⚠️ Logo ${dimension} exceed 192dp. It might be cropped by Android.`;
184
- log.warn(message);
185
- };
239
+ logoHeight: number;
240
+ logoWidth: number;
241
+ brandHeight: number;
242
+ brandWidth: number;
186
243
 
187
- if (logoWidth > 288) {
188
- logAbove288("width");
189
- } else if (logoHeight > 288) {
190
- logAbove288("height");
191
- } else if (logoWidth > 192) {
192
- logAbove192("width");
193
- } else if (logoHeight > 192) {
194
- logAbove192("height");
195
- }
244
+ background: Color;
245
+ brand: Sharp | undefined;
196
246
 
197
- const logWrite = (
198
- emoji: string,
199
- filePath: string,
200
- dimensions?: { width: number; height: number },
201
- ) =>
202
- log.text(
203
- `${emoji} ${path.relative(workingPath, filePath)}` +
204
- (dimensions != null
205
- ? ` (${dimensions.width}x${dimensions.height})`
206
- : ""),
207
- );
247
+ darkBackground: Color | undefined;
248
+ darkLogo: Sharp | undefined;
249
+ darkBrand: Sharp | undefined;
250
+ };
208
251
 
209
- if (assetsPath) {
210
- log.text(`\n ${pc.underline("Assets")}`);
252
+ const requireAddon = ():
253
+ | { execute: (config: AddonConfig) => Promise<void> }
254
+ | undefined => {
255
+ try {
256
+ // eslint-disable-next-line
257
+ return require("./addon");
258
+ } catch {
259
+ return;
260
+ }
261
+ };
211
262
 
212
- fs.ensureDirSync(assetsPath);
263
+ export const generate: CommandFunction<{
264
+ logoWidth: number;
265
+ background: string;
266
+ assetsOutput?: string;
267
+ flavor: string;
268
+ platforms: string[];
213
269
 
214
- await Promise.all(
215
- [
216
- { ratio: 1, suffix: "" },
217
- { ratio: 1.5, suffix: "@1,5x" },
218
- { ratio: 2, suffix: "@2x" },
219
- { ratio: 3, suffix: "@3x" },
220
- { ratio: 4, suffix: "@4x" },
221
- ].map(({ ratio, suffix }) => {
222
- const fileName = `${logoFileName}${suffix}.png`;
223
- const filePath = path.resolve(assetsPath, fileName);
270
+ licenseKey?: string;
271
+ brand?: string;
272
+ brandWidth: number;
273
+ darkBackground?: string;
274
+ darkLogo?: string;
275
+ darkBrand?: string;
276
+ }> = async (
277
+ [argsLogo],
278
+ { project: { android, ios } },
279
+ { flavor, platforms, licenseKey, ...args },
280
+ ) => {
281
+ if (argsLogo == null) {
282
+ log.error("Missing required argument 'logo'");
283
+ process.exit(1);
284
+ }
224
285
 
225
- return image
226
- .clone()
227
- .resize(logoWidth * ratio)
228
- .png({ quality: 100 })
229
- .toFile(filePath)
230
- .then(({ width, height }) => {
231
- logWrite("✨", filePath, { width, height });
232
- });
233
- }),
286
+ const assetsOutputPath =
287
+ args.assetsOutput != null
288
+ ? path.resolve(workingPath, args.assetsOutput)
289
+ : undefined;
290
+
291
+ const background = parseColor(args.background);
292
+ const logo = sharp(path.resolve(workingPath, argsLogo));
293
+ const logoWidth = args.logoWidth - (args.logoWidth % 2);
294
+ const brandWidth = args.brandWidth - (args.brandWidth % 2);
295
+
296
+ const brand =
297
+ args.brand != null
298
+ ? sharp(path.resolve(workingPath, args.brand))
299
+ : undefined;
300
+
301
+ const darkBackground =
302
+ args.darkBackground != null ? parseColor(args.darkBackground) : undefined;
303
+
304
+ const darkLogo =
305
+ args.darkLogo != null
306
+ ? sharp(path.resolve(workingPath, args.darkLogo))
307
+ : undefined;
308
+
309
+ const darkBrand =
310
+ args.darkBrand != null
311
+ ? sharp(path.resolve(workingPath, args.darkBrand))
312
+ : undefined;
313
+
314
+ const executeAddon =
315
+ brand != null ||
316
+ darkBackground != null ||
317
+ darkLogo != null ||
318
+ darkBrand != null;
319
+
320
+ if (licenseKey != null && !executeAddon) {
321
+ log.warn(
322
+ `You specified a license key but none of the options that requires it.`,
234
323
  );
235
324
  }
236
325
 
237
- if (platformsIncludesAndroid && android && !shouldSkipAndroid) {
238
- log.text(`\n ${pc.underline("Android")}`);
326
+ if (licenseKey == null && executeAddon) {
327
+ const options = [
328
+ brand != null ? "brand" : "",
329
+ darkBackground != null ? "dark-background" : "",
330
+ darkLogo != null ? "dark-logo" : "",
331
+ darkBrand != null ? "dark-brand" : "",
332
+ ]
333
+ .filter((option) => option !== "")
334
+ .map((option) => `--${option}`)
335
+ .join(", ");
336
+
337
+ log.error(`You need to specify a license key in order to use ${options}.`);
338
+ process.exit(1);
339
+ }
340
+
341
+ if (brand == null && darkBrand != null) {
342
+ log.error("--dark-brand option couldn't be used without --brand.");
343
+ process.exit(1);
344
+ }
239
345
 
240
- const appPath = android.appName
241
- ? path.resolve(android.sourceDir, android.appName)
242
- : path.resolve(android.sourceDir); // @react-native-community/cli 2.x & 3.x support
346
+ await ensureSupportedFormat("Logo", logo);
347
+ await ensureSupportedFormat("Dark logo", darkLogo);
348
+ await ensureSupportedFormat("Brand", brand);
349
+ await ensureSupportedFormat("Dark brand", darkBrand);
243
350
 
244
- const resPath = path.resolve(appPath, "src", flavor, "res");
245
- const valuesPath = path.resolve(resPath, "values");
351
+ const logoHeight = await logo
352
+ .clone()
353
+ .resize(logoWidth)
354
+ .toBuffer()
355
+ .then((buffer) => sharp(buffer).metadata())
356
+ .then(({ height = 0 }) => Math.round(height));
357
+
358
+ const brandHeight =
359
+ (await brand
360
+ ?.clone()
361
+ .resize(brandWidth)
362
+ .toBuffer()
363
+ .then((buffer) => sharp(buffer).metadata())
364
+ .then(({ height = 0 }) => Math.round(height))) ?? 0;
365
+
366
+ if (logoWidth < args.logoWidth) {
367
+ log.warn(
368
+ `Logo width must be a multiple of 2. It has been rounded to ${logoWidth}dp.`,
369
+ );
370
+ }
371
+ if (brandWidth < args.brandWidth) {
372
+ log.warn(
373
+ `Brand width must be a multiple of 2. It has been rounded to ${brandWidth}dp.`,
374
+ );
375
+ }
246
376
 
377
+ const androidResPath =
378
+ platforms.includes("android") && android != null
379
+ ? getAndroidResPath(android, {
380
+ brandHeight,
381
+ brandWidth,
382
+ flavor,
383
+ logoHeight,
384
+ logoWidth,
385
+ })
386
+ : undefined;
387
+
388
+ const iosProjectPath =
389
+ platforms.includes("ios") && ios != null
390
+ ? getIOSProjectPath(ios)
391
+ : undefined;
392
+
393
+ if (androidResPath != null) {
394
+ log.title("🤖", "Android");
395
+
396
+ const valuesPath = path.resolve(androidResPath, "values");
247
397
  fs.ensureDirSync(valuesPath);
248
398
 
249
399
  const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
250
- const colorsXmlEntry = `<color name="${androidColorName}">${backgroundColorHex}</color>`;
400
+ const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
251
401
 
252
402
  if (fs.existsSync(colorsXmlPath)) {
253
403
  const colorsXml = fs.readFileSync(colorsXmlPath, "utf-8");
@@ -268,29 +418,32 @@ export const generate = async ({
268
418
  "utf-8",
269
419
  );
270
420
  }
271
-
272
- logWrite("✏️ ", colorsXmlPath);
273
421
  } else {
274
422
  fs.writeFileSync(
275
423
  colorsXmlPath,
276
424
  `<resources>\n ${colorsXmlEntry}\n</resources>\n`,
277
425
  "utf-8",
278
426
  );
279
-
280
- logWrite("✨", colorsXmlPath);
281
427
  }
282
428
 
429
+ logWrite(colorsXmlPath);
430
+
283
431
  await Promise.all(
284
432
  [
285
- { ratio: 1, directory: "mipmap-mdpi" },
286
- { ratio: 1.5, directory: "mipmap-hdpi" },
287
- { ratio: 2, directory: "mipmap-xhdpi" },
288
- { ratio: 3, directory: "mipmap-xxhdpi" },
289
- { ratio: 4, directory: "mipmap-xxxhdpi" },
290
- ].map(({ ratio, directory }) => {
291
- const fileName = `${logoFileName}.png`;
292
- const filePath = path.resolve(resPath, directory, fileName);
293
- // https://github.com/androidx/androidx/blob/androidx-main/core/core-splashscreen/src/main/res/values/dimens.xml#L22
433
+ { ratio: 1, suffix: "mdpi" },
434
+ { ratio: 1.5, suffix: "hdpi" },
435
+ { ratio: 2, suffix: "xhdpi" },
436
+ { ratio: 3, suffix: "xxhdpi" },
437
+ { ratio: 4, suffix: "xxxhdpi" },
438
+ ].map(({ ratio, suffix }) => {
439
+ const drawableDirPath = path.resolve(
440
+ androidResPath,
441
+ `drawable-${suffix}`,
442
+ );
443
+
444
+ fs.ensureDirSync(drawableDirPath);
445
+
446
+ // https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
294
447
  const canvasSize = 288 * ratio;
295
448
 
296
449
  // https://sharp.pixelplumbing.com/api-constructor
@@ -308,7 +461,9 @@ export const generate = async ({
308
461
  },
309
462
  });
310
463
 
311
- return image
464
+ const filePath = path.resolve(drawableDirPath, "bootsplash_logo.png");
465
+
466
+ return logo
312
467
  .clone()
313
468
  .resize(logoWidth * ratio)
314
469
  .toBuffer()
@@ -319,86 +474,156 @@ export const generate = async ({
319
474
  .toFile(filePath),
320
475
  )
321
476
  .then(() => {
322
- logWrite("✨", filePath, { width: canvasSize, height: canvasSize });
477
+ logWrite(filePath, {
478
+ width: canvasSize,
479
+ height: canvasSize,
480
+ });
323
481
  });
324
482
  }),
325
483
  );
326
484
  }
327
485
 
328
- if (platformsIncludesIOS && ios) {
329
- log.text(`\n ${pc.underline("iOS")}`);
486
+ if (iosProjectPath != null) {
487
+ log.title("🍏", "iOS");
330
488
 
331
- const { projectPath } = ios;
332
- const imagesPath = path.resolve(projectPath, "Images.xcassets");
489
+ const storyboardPath = path.resolve(
490
+ iosProjectPath,
491
+ "BootSplash.storyboard",
492
+ );
333
493
 
334
- if (fs.existsSync(projectPath)) {
335
- const storyboardPath = path.resolve(projectPath, "BootSplash.storyboard");
494
+ fs.writeFileSync(
495
+ storyboardPath,
496
+ getStoryboard({
497
+ logoHeight,
498
+ logoWidth,
499
+ background: background.rgb,
500
+ }),
501
+ "utf-8",
502
+ );
336
503
 
337
- fs.writeFileSync(
338
- storyboardPath,
339
- getStoryboard({
340
- height: logoHeight,
341
- width: logoWidth,
342
- backgroundColor: backgroundColorHex,
343
- }),
344
- "utf-8",
345
- );
504
+ logWrite(storyboardPath);
346
505
 
347
- logWrite("✨", storyboardPath);
348
- } else {
349
- log.text(
350
- `No "${projectPath}" directory found. Skipping iOS storyboard generation…`,
351
- );
352
- }
506
+ const imageSetPath = path.resolve(
507
+ iosProjectPath,
508
+ "Images.xcassets",
509
+ "BootSplashLogo.imageset",
510
+ );
353
511
 
354
- if (fs.existsSync(imagesPath)) {
355
- const imageSetPath = path.resolve(imagesPath, xcassetName + ".imageset");
356
- fs.ensureDirSync(imageSetPath);
512
+ fs.ensureDirSync(imageSetPath);
357
513
 
358
- fs.writeFileSync(
359
- path.resolve(imageSetPath, "Contents.json"),
360
- ContentsJson,
361
- "utf-8",
362
- );
514
+ fs.writeFileSync(
515
+ path.resolve(imageSetPath, "Contents.json"),
516
+ ContentsJson,
517
+ "utf-8",
518
+ );
363
519
 
364
- await Promise.all(
365
- [
366
- { ratio: 1, suffix: "" },
367
- { ratio: 2, suffix: "@2x" },
368
- { ratio: 3, suffix: "@3x" },
369
- ].map(({ ratio, suffix }) => {
370
- const fileName = `${logoFileName}${suffix}.png`;
371
- const filePath = path.resolve(imageSetPath, fileName);
372
-
373
- return image
374
- .clone()
375
- .resize(logoWidth * ratio)
376
- .png({ quality: 100 })
377
- .toFile(filePath)
378
- .then(({ width, height }) => {
379
- logWrite("✨", filePath, { width, height });
380
- });
381
- }),
382
- );
383
- } else {
384
- log.text(
385
- `No "${imagesPath}" directory found. Skipping iOS images generation…`,
386
- );
387
- }
520
+ await Promise.all(
521
+ [
522
+ { ratio: 1, suffix: "" },
523
+ { ratio: 2, suffix: "@2x" },
524
+ { ratio: 3, suffix: "@3x" },
525
+ ].map(({ ratio, suffix }) => {
526
+ const filePath = path.resolve(
527
+ imageSetPath,
528
+ `bootsplash_logo${suffix}.png`,
529
+ );
530
+
531
+ return logo
532
+ .clone()
533
+ .resize(logoWidth * ratio)
534
+ .png({ quality: 100 })
535
+ .toFile(filePath)
536
+ .then(({ width, height }) => {
537
+ logWrite(filePath, { width, height });
538
+ });
539
+ }),
540
+ );
388
541
  }
389
542
 
390
- log.text(`
391
- ${pc.blue("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")}
392
- ${pc.blue("┃")} 💖 ${pc.bold(
393
- "Love this library? Consider sponsoring!",
394
- )} ${pc.blue("┃")}
395
- ${pc.blue("┃")} ${pc.underline(
396
- "https://github.com/sponsors/zoontek",
397
- )} ${pc.blue("┃")}
398
- ${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")}
399
- `);
400
-
401
- log.text(
402
- `✅ Done! Thanks for using ${pc.underline("react-native-bootsplash")}.`,
403
- );
543
+ if (assetsOutputPath != null) {
544
+ log.title("📄", "Assets");
545
+
546
+ fs.ensureDirSync(assetsOutputPath);
547
+
548
+ const manifest: Manifest = {
549
+ background: background.hex,
550
+ logo: {
551
+ width: logoWidth,
552
+ height: logoHeight,
553
+ },
554
+ };
555
+
556
+ const manifestPath = path.resolve(
557
+ assetsOutputPath,
558
+ "bootsplash_manifest.json",
559
+ );
560
+
561
+ fs.writeFileSync(
562
+ manifestPath,
563
+ JSON.stringify(manifest, null, 2) + "\n",
564
+ "utf-8",
565
+ );
566
+
567
+ logWrite(manifestPath);
568
+
569
+ await Promise.all(
570
+ [
571
+ { ratio: 1, suffix: "" },
572
+ { ratio: 1.5, suffix: "@1,5x" },
573
+ { ratio: 2, suffix: "@2x" },
574
+ { ratio: 3, suffix: "@3x" },
575
+ { ratio: 4, suffix: "@4x" },
576
+ ].map(({ ratio, suffix }) => {
577
+ const filePath = path.resolve(
578
+ assetsOutputPath,
579
+ `bootsplash_logo${suffix}.png`,
580
+ );
581
+
582
+ return logo
583
+ .clone()
584
+ .resize(Math.round(logoWidth * ratio))
585
+ .png({ quality: 100 })
586
+ .toFile(filePath)
587
+ .then(({ width, height }) => {
588
+ logWrite(filePath, { width, height });
589
+ });
590
+ }),
591
+ );
592
+ }
593
+
594
+ if (licenseKey != null && executeAddon) {
595
+ const addon = requireAddon();
596
+
597
+ await addon?.execute({
598
+ licenseKey,
599
+
600
+ androidResPath,
601
+ iosProjectPath,
602
+ assetsOutputPath,
603
+
604
+ logoHeight,
605
+ logoWidth,
606
+ brandHeight,
607
+ brandWidth,
608
+
609
+ background,
610
+ brand,
611
+
612
+ darkBackground,
613
+ darkLogo,
614
+ darkBrand,
615
+ });
616
+ } else {
617
+ log.text(`
618
+ ${pc.blue("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")}
619
+ ${pc.blue("┃")} 🔑 ${pc.bold(
620
+ "Get a license key for brand image / dark mode support",
621
+ )} ${pc.blue("┃")}
622
+ ${pc.blue("┃")} ${pc.underline(
623
+ "https://zoontek.gumroad.com/l/bootsplash-generator",
624
+ )} ${pc.blue("┃")}
625
+ ${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")}`);
626
+ }
627
+
628
+ log.text(`\n💖 Thanks for using ${pc.underline("react-native-bootsplash")}`);
404
629
  };