react-native-bootsplash 7.0.2 → 7.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.plugin.js +1 -1
- package/cli.js +1 -1
- package/dist/commonjs/extras/addon/index.js +97 -0
- package/dist/commonjs/extras/addon/index.js.map +1 -0
- package/dist/commonjs/{expo.js → extras/expo.js} +29 -32
- package/dist/commonjs/extras/expo.js.map +1 -0
- package/dist/commonjs/extras/generate.js +274 -0
- package/dist/commonjs/extras/generate.js.map +1 -0
- package/dist/commonjs/{generate.js → extras/utils.js} +126 -355
- package/dist/commonjs/extras/utils.js.map +1 -0
- package/dist/commonjs/package.json +1 -0
- package/dist/module/extras/addon/index.js +97 -0
- package/dist/module/extras/addon/index.js.map +1 -0
- package/dist/module/{expo.js → extras/expo.js} +7 -11
- package/dist/module/extras/expo.js.map +1 -0
- package/dist/module/extras/generate.js +267 -0
- package/dist/module/extras/generate.js.map +1 -0
- package/dist/module/{generate.js → extras/utils.js} +115 -343
- package/dist/module/extras/utils.js.map +1 -0
- package/dist/module/package.json +1 -0
- package/dist/typescript/{addon → commonjs/extras/addon}/index.d.ts +1 -1
- package/dist/typescript/commonjs/extras/addon/index.d.ts.map +1 -0
- package/dist/typescript/commonjs/extras/expo.d.ts +8 -0
- package/dist/typescript/commonjs/extras/expo.d.ts.map +1 -0
- package/dist/typescript/commonjs/extras/generate.d.ts +17 -0
- package/dist/typescript/commonjs/extras/generate.d.ts.map +1 -0
- package/dist/typescript/{generate.d.ts → commonjs/extras/utils.d.ts} +67 -37
- package/dist/typescript/commonjs/extras/utils.d.ts.map +1 -0
- package/dist/typescript/commonjs/index.d.ts.map +1 -0
- package/dist/typescript/commonjs/package.json +1 -0
- package/dist/typescript/commonjs/specs/NativeRNBootSplash.d.ts.map +1 -0
- package/dist/typescript/commonjs/specs/NativeRNBootSplash.web.d.ts.map +1 -0
- package/dist/typescript/module/extras/addon/index.d.ts +26 -0
- package/dist/typescript/module/extras/addon/index.d.ts.map +1 -0
- package/dist/typescript/module/extras/expo.d.ts +8 -0
- package/dist/typescript/module/extras/expo.d.ts.map +1 -0
- package/dist/typescript/module/extras/generate.d.ts +17 -0
- package/dist/typescript/module/extras/generate.d.ts.map +1 -0
- package/dist/typescript/module/extras/utils.d.ts +192 -0
- package/dist/typescript/module/extras/utils.d.ts.map +1 -0
- package/dist/typescript/module/index.d.ts +61 -0
- package/dist/typescript/module/index.d.ts.map +1 -0
- package/dist/typescript/module/package.json +1 -0
- package/dist/typescript/module/specs/NativeRNBootSplash.d.ts +14 -0
- package/dist/typescript/module/specs/NativeRNBootSplash.d.ts.map +1 -0
- package/dist/typescript/module/specs/NativeRNBootSplash.web.d.ts +9 -0
- package/dist/typescript/module/specs/NativeRNBootSplash.web.d.ts.map +1 -0
- package/package.json +49 -19
- package/src/{expo.ts → extras/expo.ts} +31 -40
- package/src/extras/generate.ts +410 -0
- package/src/{generate.ts → extras/utils.ts} +180 -494
- package/dist/commonjs/addon/index.js +0 -97
- package/dist/commonjs/addon/index.js.map +0 -1
- package/dist/commonjs/expo.js.map +0 -1
- package/dist/commonjs/generate.js.map +0 -1
- package/dist/module/addon/index.js +0 -97
- package/dist/module/addon/index.js.map +0 -1
- package/dist/module/expo.js.map +0 -1
- package/dist/module/generate.js.map +0 -1
- package/dist/typescript/addon/index.d.ts.map +0 -1
- package/dist/typescript/expo.d.ts +0 -6
- package/dist/typescript/expo.d.ts.map +0 -1
- package/dist/typescript/generate.d.ts.map +0 -1
- package/dist/typescript/index.d.ts.map +0 -1
- package/dist/typescript/specs/NativeRNBootSplash.d.ts.map +0 -1
- package/dist/typescript/specs/NativeRNBootSplash.web.d.ts.map +0 -1
- /package/dist/typescript/{index.d.ts → commonjs/index.d.ts} +0 -0
- /package/dist/typescript/{specs → commonjs/specs}/NativeRNBootSplash.d.ts +0 -0
- /package/dist/typescript/{specs → commonjs/specs}/NativeRNBootSplash.web.d.ts +0 -0
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import * as Expo from "@expo/config-plugins";
|
|
2
|
-
import ExpoPlist from "@expo/plist";
|
|
3
2
|
import crypto from "crypto";
|
|
4
3
|
import detectIndent, { type Indent } from "detect-indent";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import fs from "fs-extra";
|
|
8
|
-
import { HTMLElement, parse as parseHtml } from "node-html-parser";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { parse as parseHtml } from "node-html-parser";
|
|
9
6
|
import path from "path";
|
|
10
7
|
import pc from "picocolors";
|
|
11
8
|
import type { Options as PrettierOptions } from "prettier";
|
|
@@ -16,36 +13,100 @@ import semver from "semver";
|
|
|
16
13
|
import sharp, { type Sharp } from "sharp";
|
|
17
14
|
import { dedent } from "ts-dedent";
|
|
18
15
|
import formatXml, { type XMLFormatterOptions } from "xml-formatter";
|
|
19
|
-
import type { Manifest } from "
|
|
16
|
+
import type { Manifest } from "..";
|
|
20
17
|
|
|
21
18
|
export const PACKAGE_NAME = "react-native-bootsplash";
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
export type BootSplashPluginConfig = {
|
|
21
|
+
/**
|
|
22
|
+
* Logo file path (PNG or SVG)
|
|
23
|
+
*/
|
|
24
|
+
logo: string;
|
|
25
|
+
/**
|
|
26
|
+
* Background color (in hexadecimal format)
|
|
27
|
+
* @default #fff
|
|
28
|
+
*/
|
|
29
|
+
background?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Logo width at @1x (in dp - we recommend approximately ~100)
|
|
32
|
+
* @default 100
|
|
33
|
+
*/
|
|
34
|
+
logoWidth?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Assets output directory path
|
|
37
|
+
* @default assets/bootsplash
|
|
38
|
+
*/
|
|
39
|
+
assetsOutput?: string;
|
|
40
|
+
/**
|
|
41
|
+
* License key to enable brand and dark mode assets generation
|
|
42
|
+
*/
|
|
43
|
+
licenseKey?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Brand file path (PNG or SVG)
|
|
46
|
+
*/
|
|
47
|
+
brand?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Brand width at @1x (in dp - we recommend approximately ~80)
|
|
50
|
+
* @default 80
|
|
51
|
+
*/
|
|
52
|
+
brandWidth?: number;
|
|
53
|
+
/**
|
|
54
|
+
* [dark mode] Background color (in hexadecimal format)
|
|
55
|
+
*/
|
|
56
|
+
darkBackground?: string;
|
|
57
|
+
/**
|
|
58
|
+
* [dark mode] Logo file path (PNG or SVG)
|
|
59
|
+
*/
|
|
60
|
+
darkLogo?: string;
|
|
61
|
+
/**
|
|
62
|
+
* [dark mode] Brand file path (PNG or SVG)
|
|
63
|
+
*/
|
|
64
|
+
darkBrand?: string;
|
|
65
|
+
|
|
66
|
+
android?: {
|
|
67
|
+
/**
|
|
68
|
+
* Enforce system bars style
|
|
69
|
+
* @default `undefined`
|
|
70
|
+
*/
|
|
71
|
+
darkContentBarsStyle?: boolean;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type LoggerMode = { type: "plugin" } | { type: "cli"; cwd: string };
|
|
76
|
+
let loggerMode: LoggerMode = { type: "plugin" };
|
|
24
77
|
|
|
25
|
-
export const
|
|
26
|
-
|
|
78
|
+
export const setLoggerMode = (value: LoggerMode) => {
|
|
79
|
+
loggerMode = value;
|
|
27
80
|
};
|
|
28
81
|
|
|
29
82
|
export const log = {
|
|
30
83
|
error: (text: string) => {
|
|
31
84
|
console.log(
|
|
32
|
-
pc.red(
|
|
85
|
+
pc.red(
|
|
86
|
+
loggerMode.type === "plugin"
|
|
87
|
+
? `❌ [${PACKAGE_NAME}] ${text}`
|
|
88
|
+
: `❌ ${text}`,
|
|
89
|
+
),
|
|
33
90
|
);
|
|
34
91
|
},
|
|
35
92
|
title: (emoji: string, text: string) => {
|
|
36
|
-
if (
|
|
93
|
+
if (loggerMode.type === "cli") {
|
|
37
94
|
console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`);
|
|
38
95
|
}
|
|
39
96
|
},
|
|
40
97
|
warn: (text: string) => {
|
|
41
98
|
console.log(
|
|
42
|
-
pc.yellow(
|
|
99
|
+
pc.yellow(
|
|
100
|
+
loggerMode.type === "plugin"
|
|
101
|
+
? `⚠️ [${PACKAGE_NAME}] ${text}`
|
|
102
|
+
: `⚠️ ${text}`,
|
|
103
|
+
),
|
|
43
104
|
);
|
|
44
105
|
},
|
|
45
106
|
write: (filePath: string, dimensions?: { width: number; height: number }) => {
|
|
46
|
-
if (
|
|
107
|
+
if (loggerMode.type === "cli") {
|
|
47
108
|
console.log(
|
|
48
|
-
` ${path.relative(
|
|
109
|
+
` ${path.relative(loggerMode.cwd, filePath)}` +
|
|
49
110
|
(dimensions != null
|
|
50
111
|
? ` (${dimensions.width}x${dimensions.height})`
|
|
51
112
|
: ""),
|
|
@@ -54,100 +115,10 @@ export const log = {
|
|
|
54
115
|
},
|
|
55
116
|
};
|
|
56
117
|
|
|
57
|
-
const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
|
|
58
|
-
const packagePath = findUp.sync("package.json", { cwd: workingPath });
|
|
59
|
-
|
|
60
|
-
if (!packagePath) {
|
|
61
|
-
log.error("We couldn't find a package.json in your project.");
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const projectRoot = path.dirname(packagePath);
|
|
66
|
-
|
|
67
|
-
const parseColor = (value: string) => {
|
|
68
|
-
const up = value.toUpperCase().replace(/[^0-9A-F]/g, "");
|
|
69
|
-
|
|
70
|
-
if (up.length !== 3 && up.length !== 6) {
|
|
71
|
-
log.error(`"${value}" value is not a valid hexadecimal color.`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const hex =
|
|
76
|
-
up.length === 3
|
|
77
|
-
? "#" + up[0] + up[0] + up[1] + up[1] + up[2] + up[2]
|
|
78
|
-
: "#" + up;
|
|
79
|
-
|
|
80
|
-
const rgb = {
|
|
81
|
-
R: (Number.parseInt("" + hex[1] + hex[2], 16) / 255).toPrecision(15),
|
|
82
|
-
G: (Number.parseInt("" + hex[3] + hex[4], 16) / 255).toPrecision(15),
|
|
83
|
-
B: (Number.parseInt("" + hex[5] + hex[6], 16) / 255).toPrecision(15),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return { hex: hex.toLowerCase(), rgb };
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const getStoryboard = (props: Props) => {
|
|
90
|
-
const { background, logo, fileNameSuffix } = props;
|
|
91
|
-
|
|
92
|
-
const { R, G, B } = background.rgb;
|
|
93
|
-
const frameWidth = 375;
|
|
94
|
-
const frameHeight = 667;
|
|
95
|
-
|
|
96
|
-
return dedent`
|
|
97
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
98
|
-
<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">
|
|
99
|
-
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
|
100
|
-
<dependencies>
|
|
101
|
-
<deployment identifier="iOS"/>
|
|
102
|
-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
|
103
|
-
<capability name="Named colors" minToolsVersion="9.0"/>
|
|
104
|
-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
105
|
-
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
106
|
-
</dependencies>
|
|
107
|
-
<scenes>
|
|
108
|
-
<!--View Controller-->
|
|
109
|
-
<scene sceneID="EHf-IW-A2E">
|
|
110
|
-
<objects>
|
|
111
|
-
<viewController modalTransitionStyle="crossDissolve" id="01J-lp-oVM" sceneMemberID="viewController">
|
|
112
|
-
<view key="view" autoresizesSubviews="NO" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
113
|
-
<rect key="frame" x="0.0" y="0.0" width="${frameWidth}" height="${frameHeight}"/>
|
|
114
|
-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
115
|
-
<subviews>
|
|
116
|
-
<imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo-${fileNameSuffix}" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
|
|
117
|
-
<rect key="frame" x="${(frameWidth - logo.width) / 2}" y="${(frameHeight - logo.height) / 2}" width="${logo.width}" height="${logo.height}"/>
|
|
118
|
-
<accessibility key="accessibilityConfiguration">
|
|
119
|
-
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
|
|
120
|
-
</accessibility>
|
|
121
|
-
</imageView>
|
|
122
|
-
</subviews>
|
|
123
|
-
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
124
|
-
<color key="backgroundColor" name="BootSplashBackground-${fileNameSuffix}"/>
|
|
125
|
-
<constraints>
|
|
126
|
-
<constraint firstItem="3lX-Ut-9ad" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="Fh9-Fy-1nT"/>
|
|
127
|
-
<constraint firstItem="3lX-Ut-9ad" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="nvB-Ic-PnI"/>
|
|
128
|
-
</constraints>
|
|
129
|
-
</view>
|
|
130
|
-
</viewController>
|
|
131
|
-
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
132
|
-
</objects>
|
|
133
|
-
<point key="canvasLocation" x="0.0" y="0.0"/>
|
|
134
|
-
</scene>
|
|
135
|
-
</scenes>
|
|
136
|
-
<resources>
|
|
137
|
-
<image name="BootSplashLogo-${fileNameSuffix}" width="${logo.width}" height="${logo.height}"/>
|
|
138
|
-
<namedColor name="BootSplashBackground-${fileNameSuffix}">
|
|
139
|
-
<color red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
140
|
-
</namedColor>
|
|
141
|
-
</resources>
|
|
142
|
-
</document>
|
|
143
|
-
`;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
118
|
// Freely inspired by https://github.com/humanwhocodes/humanfs
|
|
147
119
|
export const hfs = {
|
|
148
120
|
buffer: (path: string) => fs.readFileSync(path),
|
|
149
121
|
exists: (path: string) => fs.existsSync(path),
|
|
150
|
-
isDir: (path: string) => fs.lstatSync(path).isDirectory(),
|
|
151
122
|
json: (path: string) => JSON.parse(fs.readFileSync(path, "utf-8")) as unknown,
|
|
152
123
|
readDir: (path: string) => fs.readdirSync(path, "utf-8"),
|
|
153
124
|
realPath: (path: string) => fs.realpathSync(path, "utf-8"),
|
|
@@ -239,117 +210,6 @@ export const writeXmlLike = async (
|
|
|
239
210
|
}
|
|
240
211
|
};
|
|
241
212
|
|
|
242
|
-
const getAndroidOutputPath = ({ flavor }: { flavor: string }) => {
|
|
243
|
-
const sourceDir = path.join(projectRoot, "android");
|
|
244
|
-
|
|
245
|
-
if (!fs.existsSync(sourceDir)) {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const appDir = path.join(sourceDir, "app");
|
|
250
|
-
|
|
251
|
-
const androidOutputPath = path.resolve(
|
|
252
|
-
sourceDir,
|
|
253
|
-
fs.existsSync(appDir) ? "app" : "",
|
|
254
|
-
"src",
|
|
255
|
-
flavor,
|
|
256
|
-
"res",
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
if (hfs.exists(androidOutputPath)) {
|
|
260
|
-
return androidOutputPath;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
log.warn(
|
|
264
|
-
`No ${path.relative(
|
|
265
|
-
workingPath,
|
|
266
|
-
androidOutputPath,
|
|
267
|
-
)} directory found. Skipping Android assets generation…`,
|
|
268
|
-
);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const getIOSOutputPath = () => {
|
|
272
|
-
const podfile = glob
|
|
273
|
-
.sync("**/Podfile", {
|
|
274
|
-
cwd: projectRoot.replace(/^([a-zA-Z]+:|\.\/)/, ""),
|
|
275
|
-
deep: 10,
|
|
276
|
-
ignore: ["**/@(Pods|node_modules|Carthage|vendor|android)/**"],
|
|
277
|
-
})
|
|
278
|
-
.find((project) => {
|
|
279
|
-
return path.dirname(project) === "ios";
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
if (!podfile) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const sourceDir = path.dirname(path.join(projectRoot, podfile));
|
|
287
|
-
|
|
288
|
-
const xcodeProjectName = fs
|
|
289
|
-
.readdirSync(sourceDir)
|
|
290
|
-
.sort()
|
|
291
|
-
.reverse()
|
|
292
|
-
.find((fileName) => {
|
|
293
|
-
const ext = path.extname(fileName);
|
|
294
|
-
return ext === ".xcworkspace" || ext === ".xcodeproj";
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
if (xcodeProjectName == null) {
|
|
298
|
-
log.warn("No Xcode project found. Skipping iOS assets generation…");
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const iosOutputPath = path
|
|
303
|
-
.resolve(sourceDir, xcodeProjectName)
|
|
304
|
-
.replace(/\.(xcodeproj|xcworkspace)$/, "");
|
|
305
|
-
|
|
306
|
-
if (hfs.exists(iosOutputPath)) {
|
|
307
|
-
return iosOutputPath;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
log.warn(
|
|
311
|
-
`No ${path.relative(
|
|
312
|
-
workingPath,
|
|
313
|
-
iosOutputPath,
|
|
314
|
-
)} directory found. Skipping iOS assets generation…`,
|
|
315
|
-
);
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
const getHtmlTemplatePath = ({ html }: { html: string }) => {
|
|
319
|
-
const htmlTemplatePath = path.resolve(workingPath, html);
|
|
320
|
-
|
|
321
|
-
if (hfs.exists(htmlTemplatePath)) {
|
|
322
|
-
return htmlTemplatePath;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
log.warn(
|
|
326
|
-
`No ${path.relative(
|
|
327
|
-
workingPath,
|
|
328
|
-
htmlTemplatePath,
|
|
329
|
-
)} found. Skipping HTML + CSS generation…`,
|
|
330
|
-
);
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const getInfoPlistPath = ({
|
|
334
|
-
iosOutputPath,
|
|
335
|
-
plist,
|
|
336
|
-
}: {
|
|
337
|
-
iosOutputPath: string;
|
|
338
|
-
plist: string | undefined;
|
|
339
|
-
}) => {
|
|
340
|
-
if (plist != null) {
|
|
341
|
-
const infoPlistPath = path.resolve(workingPath, plist);
|
|
342
|
-
|
|
343
|
-
if (!hfs.exists(infoPlistPath)) {
|
|
344
|
-
return log.warn(`No ${path.relative(workingPath, infoPlistPath)} found`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return infoPlistPath;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return path.resolve(iosOutputPath, "Info.plist");
|
|
351
|
-
};
|
|
352
|
-
|
|
353
213
|
export type Asset = {
|
|
354
214
|
path: string;
|
|
355
215
|
image: Sharp;
|
|
@@ -394,51 +254,65 @@ const toAsset = async (filePath: string, width: number): Promise<Asset> => {
|
|
|
394
254
|
};
|
|
395
255
|
};
|
|
396
256
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
background: string;
|
|
400
|
-
logoWidth: number;
|
|
401
|
-
assetsOutput: string;
|
|
257
|
+
const parseColor = (value: string) => {
|
|
258
|
+
const up = value.toUpperCase().replace(/[^0-9A-F]/g, "");
|
|
402
259
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
darkLogo?: string;
|
|
408
|
-
darkBrand?: string;
|
|
260
|
+
if (up.length !== 3 && up.length !== 6) {
|
|
261
|
+
log.error(`"${value}" value is not a valid hexadecimal color.`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
409
264
|
|
|
410
|
-
|
|
411
|
-
|
|
265
|
+
const hex =
|
|
266
|
+
up.length === 3
|
|
267
|
+
? "#" + up[0] + up[0] + up[1] + up[1] + up[2] + up[2]
|
|
268
|
+
: "#" + up;
|
|
269
|
+
|
|
270
|
+
const rgb = {
|
|
271
|
+
R: (Number.parseInt("" + hex[1] + hex[2], 16) / 255).toPrecision(15),
|
|
272
|
+
G: (Number.parseInt("" + hex[3] + hex[4], 16) / 255).toPrecision(15),
|
|
273
|
+
B: (Number.parseInt("" + hex[5] + hex[6], 16) / 255).toPrecision(15),
|
|
412
274
|
};
|
|
275
|
+
|
|
276
|
+
return { hex: hex.toLowerCase(), rgb };
|
|
413
277
|
};
|
|
414
278
|
|
|
415
279
|
export const transformProps = async (
|
|
416
280
|
rootPath: string,
|
|
417
|
-
{ android = {}, licenseKey, ...rawProps }:
|
|
281
|
+
{ android = {}, licenseKey, ...rawProps }: BootSplashPluginConfig,
|
|
418
282
|
) => {
|
|
419
283
|
if (semver.lt(process.versions.node, "20.0.0")) {
|
|
420
284
|
log.error("Requires Node 20 (or higher)");
|
|
421
285
|
process.exit(1);
|
|
422
286
|
}
|
|
423
287
|
|
|
424
|
-
const
|
|
425
|
-
|
|
288
|
+
const withDefaults = {
|
|
289
|
+
assetsOutput: "assets/bootsplash",
|
|
290
|
+
background: "#fff",
|
|
291
|
+
brandWidth: 80,
|
|
292
|
+
logoWidth: 100,
|
|
293
|
+
...rawProps,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const assetsOutputPath = path.resolve(rootPath, withDefaults.assetsOutput);
|
|
297
|
+
const logoPath = path.resolve(rootPath, withDefaults.logo);
|
|
426
298
|
|
|
427
299
|
const darkLogoPath =
|
|
428
|
-
|
|
429
|
-
? path.resolve(rootPath,
|
|
300
|
+
withDefaults.darkLogo != null
|
|
301
|
+
? path.resolve(rootPath, withDefaults.darkLogo)
|
|
430
302
|
: undefined;
|
|
431
303
|
|
|
432
304
|
const brandPath =
|
|
433
|
-
|
|
305
|
+
withDefaults.brand != null
|
|
306
|
+
? path.resolve(rootPath, withDefaults.brand)
|
|
307
|
+
: undefined;
|
|
434
308
|
|
|
435
309
|
const darkBrandPath =
|
|
436
|
-
|
|
437
|
-
? path.resolve(rootPath,
|
|
310
|
+
withDefaults.darkBrand != null
|
|
311
|
+
? path.resolve(rootPath, withDefaults.darkBrand)
|
|
438
312
|
: undefined;
|
|
439
313
|
|
|
440
|
-
const logoWidth =
|
|
441
|
-
const brandWidth =
|
|
314
|
+
const logoWidth = withDefaults.logoWidth - (withDefaults.logoWidth % 2);
|
|
315
|
+
const brandWidth = withDefaults.brandWidth - (withDefaults.brandWidth % 2);
|
|
442
316
|
|
|
443
317
|
const [logo, darkLogo, brand, darkBrand] = await Promise.all([
|
|
444
318
|
toAsset(logoPath, logoWidth),
|
|
@@ -447,11 +321,11 @@ export const transformProps = async (
|
|
|
447
321
|
darkBrandPath != null ? toAsset(darkBrandPath, brandWidth) : undefined,
|
|
448
322
|
]);
|
|
449
323
|
|
|
450
|
-
const background = parseColor(
|
|
324
|
+
const background = parseColor(withDefaults.background);
|
|
451
325
|
|
|
452
326
|
const darkBackground =
|
|
453
|
-
|
|
454
|
-
? parseColor(
|
|
327
|
+
withDefaults.darkBackground != null
|
|
328
|
+
? parseColor(withDefaults.darkBackground)
|
|
455
329
|
: undefined;
|
|
456
330
|
|
|
457
331
|
const executeAddon =
|
|
@@ -466,11 +340,13 @@ export const transformProps = async (
|
|
|
466
340
|
);
|
|
467
341
|
}
|
|
468
342
|
|
|
343
|
+
const isPluginLoggerMode = loggerMode.type === "plugin";
|
|
344
|
+
|
|
469
345
|
const optionNames = {
|
|
470
|
-
brand:
|
|
471
|
-
darkBackground:
|
|
472
|
-
darkLogo:
|
|
473
|
-
darkBrand:
|
|
346
|
+
brand: isPluginLoggerMode ? "brand" : "--brand",
|
|
347
|
+
darkBackground: isPluginLoggerMode ? "darkBackground" : "--dark-background",
|
|
348
|
+
darkLogo: isPluginLoggerMode ? "darkLogo" : "--dark-logo",
|
|
349
|
+
darkBrand: isPluginLoggerMode ? "darkBrand" : "--dark-brand",
|
|
474
350
|
};
|
|
475
351
|
|
|
476
352
|
if (licenseKey == null && executeAddon) {
|
|
@@ -495,12 +371,12 @@ export const transformProps = async (
|
|
|
495
371
|
process.exit(1);
|
|
496
372
|
}
|
|
497
373
|
|
|
498
|
-
if (logoWidth <
|
|
374
|
+
if (logoWidth < withDefaults.logoWidth) {
|
|
499
375
|
log.warn(
|
|
500
376
|
`Logo width must be a multiple of 2. It has been rounded to ${logoWidth}dp.`,
|
|
501
377
|
);
|
|
502
378
|
}
|
|
503
|
-
if (brandWidth <
|
|
379
|
+
if (brandWidth < withDefaults.brandWidth) {
|
|
504
380
|
log.warn(
|
|
505
381
|
`Brand width must be a multiple of 2. It has been rounded to ${brandWidth}dp.`,
|
|
506
382
|
);
|
|
@@ -623,6 +499,63 @@ export const writeAndroidAssets = async ({
|
|
|
623
499
|
);
|
|
624
500
|
};
|
|
625
501
|
|
|
502
|
+
const getStoryboard = (props: Props) => {
|
|
503
|
+
const { background, logo, fileNameSuffix } = props;
|
|
504
|
+
|
|
505
|
+
const { R, G, B } = background.rgb;
|
|
506
|
+
const frameWidth = 375;
|
|
507
|
+
const frameHeight = 667;
|
|
508
|
+
|
|
509
|
+
return dedent`
|
|
510
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
511
|
+
<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">
|
|
512
|
+
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
|
513
|
+
<dependencies>
|
|
514
|
+
<deployment identifier="iOS"/>
|
|
515
|
+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
|
516
|
+
<capability name="Named colors" minToolsVersion="9.0"/>
|
|
517
|
+
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
518
|
+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
519
|
+
</dependencies>
|
|
520
|
+
<scenes>
|
|
521
|
+
<!--View Controller-->
|
|
522
|
+
<scene sceneID="EHf-IW-A2E">
|
|
523
|
+
<objects>
|
|
524
|
+
<viewController modalTransitionStyle="crossDissolve" id="01J-lp-oVM" sceneMemberID="viewController">
|
|
525
|
+
<view key="view" autoresizesSubviews="NO" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
526
|
+
<rect key="frame" x="0.0" y="0.0" width="${frameWidth}" height="${frameHeight}"/>
|
|
527
|
+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
528
|
+
<subviews>
|
|
529
|
+
<imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo-${fileNameSuffix}" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
|
|
530
|
+
<rect key="frame" x="${(frameWidth - logo.width) / 2}" y="${(frameHeight - logo.height) / 2}" width="${logo.width}" height="${logo.height}"/>
|
|
531
|
+
<accessibility key="accessibilityConfiguration">
|
|
532
|
+
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
|
|
533
|
+
</accessibility>
|
|
534
|
+
</imageView>
|
|
535
|
+
</subviews>
|
|
536
|
+
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
537
|
+
<color key="backgroundColor" name="BootSplashBackground-${fileNameSuffix}"/>
|
|
538
|
+
<constraints>
|
|
539
|
+
<constraint firstItem="3lX-Ut-9ad" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="Fh9-Fy-1nT"/>
|
|
540
|
+
<constraint firstItem="3lX-Ut-9ad" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="nvB-Ic-PnI"/>
|
|
541
|
+
</constraints>
|
|
542
|
+
</view>
|
|
543
|
+
</viewController>
|
|
544
|
+
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
545
|
+
</objects>
|
|
546
|
+
<point key="canvasLocation" x="0.0" y="0.0"/>
|
|
547
|
+
</scene>
|
|
548
|
+
</scenes>
|
|
549
|
+
<resources>
|
|
550
|
+
<image name="BootSplashLogo-${fileNameSuffix}" width="${logo.width}" height="${logo.height}"/>
|
|
551
|
+
<namedColor name="BootSplashBackground-${fileNameSuffix}">
|
|
552
|
+
<color red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
553
|
+
</namedColor>
|
|
554
|
+
</resources>
|
|
555
|
+
</document>
|
|
556
|
+
`;
|
|
557
|
+
};
|
|
558
|
+
|
|
626
559
|
export const writeIOSAssets = async ({
|
|
627
560
|
iosOutputPath,
|
|
628
561
|
props,
|
|
@@ -901,250 +834,3 @@ export const requireAddon = ({
|
|
|
901
834
|
}
|
|
902
835
|
}
|
|
903
836
|
};
|
|
904
|
-
|
|
905
|
-
export const generate = async ({
|
|
906
|
-
platforms,
|
|
907
|
-
html,
|
|
908
|
-
flavor,
|
|
909
|
-
plist,
|
|
910
|
-
...rawProps
|
|
911
|
-
}: {
|
|
912
|
-
platforms: Array<"android" | "ios" | "web">;
|
|
913
|
-
html: string;
|
|
914
|
-
flavor: string;
|
|
915
|
-
plist?: string;
|
|
916
|
-
|
|
917
|
-
logo: string;
|
|
918
|
-
background: string;
|
|
919
|
-
logoWidth: number;
|
|
920
|
-
assetsOutput: string;
|
|
921
|
-
licenseKey?: string;
|
|
922
|
-
brand?: string;
|
|
923
|
-
brandWidth: number;
|
|
924
|
-
darkBackground?: string;
|
|
925
|
-
darkLogo?: string;
|
|
926
|
-
darkBrand?: string;
|
|
927
|
-
}) => {
|
|
928
|
-
const props = await transformProps(workingPath, rawProps);
|
|
929
|
-
const addon = requireAddon(props);
|
|
930
|
-
|
|
931
|
-
const { background, brand } = props;
|
|
932
|
-
|
|
933
|
-
const androidOutputPath = platforms.includes("android")
|
|
934
|
-
? getAndroidOutputPath({ flavor })
|
|
935
|
-
: undefined;
|
|
936
|
-
|
|
937
|
-
const iosOutputPath = platforms.includes("ios")
|
|
938
|
-
? getIOSOutputPath()
|
|
939
|
-
: undefined;
|
|
940
|
-
|
|
941
|
-
const htmlTemplatePath = platforms.includes("web")
|
|
942
|
-
? getHtmlTemplatePath({ html })
|
|
943
|
-
: undefined;
|
|
944
|
-
|
|
945
|
-
if (androidOutputPath != null) {
|
|
946
|
-
await writeAndroidAssets({ androidOutputPath, props });
|
|
947
|
-
|
|
948
|
-
const manifestXmlPath = path.resolve(
|
|
949
|
-
androidOutputPath,
|
|
950
|
-
"..",
|
|
951
|
-
"AndroidManifest.xml",
|
|
952
|
-
);
|
|
953
|
-
|
|
954
|
-
if (hfs.exists(manifestXmlPath)) {
|
|
955
|
-
const manifestXml = readXmlLike(manifestXmlPath);
|
|
956
|
-
const activities = manifestXml.root.querySelectorAll("activity");
|
|
957
|
-
|
|
958
|
-
for (const activity of activities) {
|
|
959
|
-
if (activity.getAttribute("android:name") === ".MainActivity") {
|
|
960
|
-
activity.setAttribute("android:theme", "@style/BootTheme");
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
|
|
965
|
-
...manifestXml.formatOptions,
|
|
966
|
-
formatter: "prettier",
|
|
967
|
-
htmlWhitespaceSensitivity: "ignore",
|
|
968
|
-
selfClosingTags: true,
|
|
969
|
-
singleAttributePerLine: true,
|
|
970
|
-
});
|
|
971
|
-
} else {
|
|
972
|
-
log.warn("No AndroidManifest.xml found");
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
const valuesPath = path.resolve(androidOutputPath, "values");
|
|
976
|
-
hfs.ensureDir(valuesPath);
|
|
977
|
-
|
|
978
|
-
const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
|
|
979
|
-
const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
|
|
980
|
-
|
|
981
|
-
if (hfs.exists(colorsXmlPath)) {
|
|
982
|
-
const colorsXml = readXmlLike(colorsXmlPath);
|
|
983
|
-
const nextColor = parseHtml(colorsXmlEntry);
|
|
984
|
-
|
|
985
|
-
const prevColor = colorsXml.root.querySelector(
|
|
986
|
-
'color[name="bootsplash_background"]',
|
|
987
|
-
);
|
|
988
|
-
|
|
989
|
-
if (prevColor != null) {
|
|
990
|
-
prevColor.replaceWith(nextColor);
|
|
991
|
-
} else {
|
|
992
|
-
colorsXml.root.querySelector("resources")?.appendChild(nextColor);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
|
|
996
|
-
...colorsXml.formatOptions,
|
|
997
|
-
formatter: "xmlFormatter",
|
|
998
|
-
});
|
|
999
|
-
} else {
|
|
1000
|
-
await writeXmlLike(
|
|
1001
|
-
colorsXmlPath,
|
|
1002
|
-
`<resources>${colorsXmlEntry}</resources>`,
|
|
1003
|
-
{ formatter: "xmlFormatter" },
|
|
1004
|
-
);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
const stylesXmlPath = path.resolve(valuesPath, "styles.xml");
|
|
1008
|
-
|
|
1009
|
-
if (hfs.exists(stylesXmlPath)) {
|
|
1010
|
-
const stylesXml = readXmlLike(stylesXmlPath);
|
|
1011
|
-
const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
|
|
1012
|
-
const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
|
|
1013
|
-
|
|
1014
|
-
const extraItems = parseHtml(
|
|
1015
|
-
prevStyle?.text
|
|
1016
|
-
.split("\n")
|
|
1017
|
-
.map((line) => line.trim())
|
|
1018
|
-
.join("") ?? "",
|
|
1019
|
-
)
|
|
1020
|
-
.childNodes.filter((node) => {
|
|
1021
|
-
if (!(node instanceof HTMLElement)) {
|
|
1022
|
-
return true;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
const name = node.getAttribute("name");
|
|
1026
|
-
|
|
1027
|
-
return (
|
|
1028
|
-
name !== "bootSplashBackground" &&
|
|
1029
|
-
name !== "bootSplashLogo" &&
|
|
1030
|
-
name !== "bootSplashBrand" &&
|
|
1031
|
-
name !== "postBootSplashTheme"
|
|
1032
|
-
);
|
|
1033
|
-
})
|
|
1034
|
-
.map((node) => node.toString());
|
|
1035
|
-
|
|
1036
|
-
const styleItems: string[] = [
|
|
1037
|
-
...(extraItems.length > 0 ? [...extraItems, ""] : []),
|
|
1038
|
-
|
|
1039
|
-
'<item name="bootSplashBackground">@color/bootsplash_background</item>',
|
|
1040
|
-
'<item name="bootSplashLogo">@drawable/bootsplash_logo</item>',
|
|
1041
|
-
|
|
1042
|
-
...(brand != null
|
|
1043
|
-
? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>']
|
|
1044
|
-
: []),
|
|
1045
|
-
|
|
1046
|
-
'<item name="postBootSplashTheme">@style/AppTheme</item>',
|
|
1047
|
-
];
|
|
1048
|
-
|
|
1049
|
-
const nextStyle = parseHtml(dedent`
|
|
1050
|
-
<style name="BootTheme" parent="${parent}">
|
|
1051
|
-
${styleItems.join("\n")}
|
|
1052
|
-
</style>
|
|
1053
|
-
`);
|
|
1054
|
-
|
|
1055
|
-
prevStyle?.remove(); // remove the existing style
|
|
1056
|
-
stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
|
|
1057
|
-
|
|
1058
|
-
await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
|
|
1059
|
-
...stylesXml.formatOptions,
|
|
1060
|
-
formatter: "prettier",
|
|
1061
|
-
htmlWhitespaceSensitivity: "ignore",
|
|
1062
|
-
});
|
|
1063
|
-
} else {
|
|
1064
|
-
log.warn("No styles.xml found");
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
if (iosOutputPath != null) {
|
|
1069
|
-
await writeIOSAssets({ iosOutputPath, props });
|
|
1070
|
-
|
|
1071
|
-
const pbxprojectPath = Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
|
|
1072
|
-
|
|
1073
|
-
const xcodeProjectPath =
|
|
1074
|
-
Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
|
|
1075
|
-
|
|
1076
|
-
const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
|
|
1077
|
-
const projectName = path.basename(iosOutputPath);
|
|
1078
|
-
const groupName = path.parse(xcodeProjectPath).name;
|
|
1079
|
-
|
|
1080
|
-
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1081
|
-
project,
|
|
1082
|
-
filepath: path.join(projectName, "BootSplash.storyboard"),
|
|
1083
|
-
groupName,
|
|
1084
|
-
isBuildFile: true,
|
|
1085
|
-
});
|
|
1086
|
-
|
|
1087
|
-
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1088
|
-
project,
|
|
1089
|
-
filepath: path.join(projectName, "Colors.xcassets"),
|
|
1090
|
-
groupName,
|
|
1091
|
-
isBuildFile: true,
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
hfs.write(pbxprojectPath, project.writeSync());
|
|
1095
|
-
log.write(pbxprojectPath);
|
|
1096
|
-
|
|
1097
|
-
const infoPlistPath = getInfoPlistPath({ iosOutputPath, plist });
|
|
1098
|
-
|
|
1099
|
-
if (infoPlistPath != null) {
|
|
1100
|
-
const infoPlist = ExpoPlist.parse(hfs.text(infoPlistPath)) as Record<
|
|
1101
|
-
string,
|
|
1102
|
-
unknown
|
|
1103
|
-
>;
|
|
1104
|
-
|
|
1105
|
-
infoPlist["UILaunchStoryboardName"] = "BootSplash";
|
|
1106
|
-
|
|
1107
|
-
const formatted = formatXml(ExpoPlist.build(infoPlist), {
|
|
1108
|
-
collapseContent: true,
|
|
1109
|
-
forceSelfClosingEmptyTag: false,
|
|
1110
|
-
indentation: "\t",
|
|
1111
|
-
lineSeparator: "\n",
|
|
1112
|
-
whiteSpaceAtEndOfSelfclosingTag: false,
|
|
1113
|
-
})
|
|
1114
|
-
.replace(/<string\/>/gm, "<string></string>")
|
|
1115
|
-
.replace(/^\t/gm, "");
|
|
1116
|
-
|
|
1117
|
-
hfs.write(infoPlistPath, formatted);
|
|
1118
|
-
log.write(infoPlistPath);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
if (htmlTemplatePath != null) {
|
|
1123
|
-
await writeWebAssets({ htmlTemplatePath, props });
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
await writeGenericAssets({ props });
|
|
1127
|
-
|
|
1128
|
-
if (addon != null) {
|
|
1129
|
-
await addon.execute({
|
|
1130
|
-
props,
|
|
1131
|
-
androidOutputPath,
|
|
1132
|
-
iosOutputPath,
|
|
1133
|
-
htmlTemplatePath,
|
|
1134
|
-
});
|
|
1135
|
-
} else {
|
|
1136
|
-
console.log(`
|
|
1137
|
-
${pc.blue("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")}
|
|
1138
|
-
${pc.blue("┃")} 🔑 ${pc.bold(
|
|
1139
|
-
"Get a license key for brand image / dark mode support",
|
|
1140
|
-
)} ${pc.blue("┃")}
|
|
1141
|
-
${pc.blue("┃")} ${pc.underline(
|
|
1142
|
-
"https://zoontek.gumroad.com/l/bootsplash-generator",
|
|
1143
|
-
)} ${pc.blue("┃")}
|
|
1144
|
-
${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")}`);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
console.log(
|
|
1148
|
-
`\n💖 Thanks for using ${pc.underline("react-native-bootsplash")}`,
|
|
1149
|
-
);
|
|
1150
|
-
};
|