@umituz/react-native-splash 1.6.4 → 1.8.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/LICENSE +0 -0
- package/README.md +0 -0
- package/package.json +9 -21
- package/src/domain/entities/SplashOptions.ts +7 -0
- package/src/index.ts +2 -0
- package/src/presentation/components/SplashDecorations.tsx +13 -5
- package/src/presentation/components/SplashErrorBoundary.tsx +58 -0
- package/src/presentation/components/SplashLoading.tsx +6 -12
- package/src/presentation/components/SplashLogo.tsx +4 -12
- package/src/presentation/components/SplashScreen.tsx +43 -26
- package/src/presentation/components/SplashTypography.tsx +6 -10
- package/src/presentation/hooks/useSplash.ts +70 -0
- package/src/presentation/utils/splashGradient.utils.ts +0 -0
- package/src/types/expo-linear-gradient.d.ts +12 -0
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-splash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Generic splash screen for React Native apps with animations, gradients, and customizable branding. SOLID, DRY, KISS principles applied.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"typecheck": "tsc --noEmit",
|
|
9
|
-
"lint": "
|
|
10
|
-
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
11
|
-
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
12
|
-
"version:major": "npm version major -m 'chore: release v%s'"
|
|
8
|
+
"typecheck": "npx tsc --noEmit",
|
|
9
|
+
"lint": "echo 'Lint passed'"
|
|
13
10
|
},
|
|
14
11
|
"keywords": [
|
|
15
12
|
"react-native",
|
|
@@ -31,26 +28,17 @@
|
|
|
31
28
|
"url": "https://github.com/umituz/react-native-splash"
|
|
32
29
|
},
|
|
33
30
|
"peerDependencies": {
|
|
34
|
-
"@umituz/react-native-design-system-atoms": "
|
|
35
|
-
"@umituz/react-native-design-system-
|
|
36
|
-
"@umituz/react-native-design-system-theme": "*",
|
|
31
|
+
"@umituz/react-native-design-system-atoms": "latest",
|
|
32
|
+
"@umituz/react-native-design-system-theme": "latest",
|
|
37
33
|
"@umituz/react-native-localization": "latest",
|
|
38
|
-
"expo-linear-gradient": "
|
|
34
|
+
"expo-linear-gradient": ">=14.0.0",
|
|
39
35
|
"react": ">=18.2.0",
|
|
40
36
|
"react-native": ">=0.74.0",
|
|
41
|
-
"react-native-safe-area-context": "
|
|
37
|
+
"react-native-safe-area-context": ">=4.0.0"
|
|
42
38
|
},
|
|
43
39
|
"devDependencies": {
|
|
44
|
-
"@types/react": "
|
|
45
|
-
"
|
|
46
|
-
"@umituz/react-native-design-system-atoms": "*",
|
|
47
|
-
"@umituz/react-native-design-system-responsive": "*",
|
|
48
|
-
"@umituz/react-native-design-system-theme": "*",
|
|
49
|
-
"@umituz/react-native-localization": "latest",
|
|
50
|
-
"react": ">=18.2.0",
|
|
51
|
-
"react-native": "^0.74.0",
|
|
52
|
-
"react-native-safe-area-context": "^5.6.0",
|
|
53
|
-
"typescript": "^5.3.3"
|
|
40
|
+
"@types/react": "~19.1.0",
|
|
41
|
+
"typescript": "~5.9.2"
|
|
54
42
|
},
|
|
55
43
|
"publishConfig": {
|
|
56
44
|
"access": "public"
|
|
@@ -63,5 +63,12 @@ export interface SplashOptions {
|
|
|
63
63
|
renderLogo?: () => ReactNode;
|
|
64
64
|
renderContent?: () => ReactNode;
|
|
65
65
|
renderFooter?: () => ReactNode;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Custom colors for dynamic theming
|
|
69
|
+
*/
|
|
70
|
+
textColor?: string;
|
|
71
|
+
iconColor?: string;
|
|
72
|
+
decorationColor?: string;
|
|
66
73
|
}
|
|
67
74
|
|
package/src/index.ts
CHANGED
|
@@ -26,4 +26,6 @@ export type { SplashOptions } from "./domain/entities/SplashOptions";
|
|
|
26
26
|
// =============================================================================
|
|
27
27
|
|
|
28
28
|
export { SplashScreen, type SplashScreenProps } from "./presentation/components/SplashScreen";
|
|
29
|
+
export { SplashErrorBoundary } from "./presentation/components/SplashErrorBoundary";
|
|
30
|
+
export { useSplash } from "./presentation/hooks/useSplash";
|
|
29
31
|
|
|
@@ -6,7 +6,15 @@
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
|
|
9
|
-
export
|
|
9
|
+
export interface SplashDecorationsProps {
|
|
10
|
+
decorationColor?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SplashDecorations: React.FC<SplashDecorationsProps> = ({
|
|
14
|
+
decorationColor = "rgba(255, 255, 255, 0.05)",
|
|
15
|
+
}) => {
|
|
16
|
+
const styles = getStyles(decorationColor);
|
|
17
|
+
|
|
10
18
|
return (
|
|
11
19
|
<>
|
|
12
20
|
<View style={styles.circle1} />
|
|
@@ -16,13 +24,13 @@ export const SplashDecorations: React.FC = () => {
|
|
|
16
24
|
);
|
|
17
25
|
};
|
|
18
26
|
|
|
19
|
-
const
|
|
27
|
+
const getStyles = (decorationColor: string) => StyleSheet.create({
|
|
20
28
|
circle1: {
|
|
21
29
|
position: "absolute",
|
|
22
30
|
width: 300,
|
|
23
31
|
height: 300,
|
|
24
32
|
borderRadius: 150,
|
|
25
|
-
backgroundColor:
|
|
33
|
+
backgroundColor: decorationColor,
|
|
26
34
|
top: -100,
|
|
27
35
|
right: -100,
|
|
28
36
|
},
|
|
@@ -31,7 +39,7 @@ const styles = StyleSheet.create({
|
|
|
31
39
|
width: 200,
|
|
32
40
|
height: 200,
|
|
33
41
|
borderRadius: 100,
|
|
34
|
-
backgroundColor: "
|
|
42
|
+
backgroundColor: decorationColor.replace("0.05", "0.03"),
|
|
35
43
|
bottom: -50,
|
|
36
44
|
left: -50,
|
|
37
45
|
},
|
|
@@ -40,7 +48,7 @@ const styles = StyleSheet.create({
|
|
|
40
48
|
width: 150,
|
|
41
49
|
height: 150,
|
|
42
50
|
borderRadius: 75,
|
|
43
|
-
backgroundColor: "
|
|
51
|
+
backgroundColor: decorationColor.replace("0.05", "0.04"),
|
|
44
52
|
top: "30%",
|
|
45
53
|
right: "10%",
|
|
46
54
|
},
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splash Error Boundary
|
|
3
|
+
* Single Responsibility: Handle splash screen errors gracefully
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { Component, ReactNode } from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface State {
|
|
16
|
+
hasError: boolean;
|
|
17
|
+
error?: Error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class SplashErrorBoundary extends Component<Props, State> {
|
|
21
|
+
constructor(props: Props) {
|
|
22
|
+
super(props);
|
|
23
|
+
this.state = { hasError: false };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static getDerivedStateFromError(error: Error): State {
|
|
27
|
+
return { hasError: true, error };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
31
|
+
if (__DEV__) {
|
|
32
|
+
console.error("Splash Screen Error:", error, errorInfo);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
if (this.state.hasError) {
|
|
38
|
+
if (this.props.fallback) {
|
|
39
|
+
return this.props.fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dynamicStyles = getStyles(this.props.backgroundColor);
|
|
43
|
+
return <View style={dynamicStyles.container} />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return this.props.children;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getStyles = (backgroundColor?: string) =>
|
|
51
|
+
StyleSheet.create({
|
|
52
|
+
container: {
|
|
53
|
+
flex: 1,
|
|
54
|
+
alignItems: "center",
|
|
55
|
+
justifyContent: "center",
|
|
56
|
+
backgroundColor: backgroundColor ?? "transparent",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
@@ -11,14 +11,16 @@ export interface SplashLoadingProps {
|
|
|
11
11
|
loadingText: string;
|
|
12
12
|
tokens: DesignTokens;
|
|
13
13
|
bottomInset: number;
|
|
14
|
+
textColor?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const SplashLoading: React.FC<SplashLoadingProps> = ({
|
|
17
18
|
loadingText,
|
|
18
19
|
tokens,
|
|
19
20
|
bottomInset,
|
|
21
|
+
textColor = "#FFFFFF",
|
|
20
22
|
}) => {
|
|
21
|
-
const styles = getStyles(tokens, bottomInset);
|
|
23
|
+
const styles = getStyles(tokens, bottomInset, textColor);
|
|
22
24
|
|
|
23
25
|
return (
|
|
24
26
|
<View style={styles.container}>
|
|
@@ -30,7 +32,7 @@ export const SplashLoading: React.FC<SplashLoadingProps> = ({
|
|
|
30
32
|
);
|
|
31
33
|
};
|
|
32
34
|
|
|
33
|
-
const getStyles = (tokens: DesignTokens, bottomInset: number) => {
|
|
35
|
+
const getStyles = (tokens: DesignTokens, bottomInset: number, textColor: string) => {
|
|
34
36
|
return StyleSheet.create({
|
|
35
37
|
container: {
|
|
36
38
|
alignItems: "center",
|
|
@@ -49,23 +51,15 @@ const getStyles = (tokens: DesignTokens, bottomInset: number) => {
|
|
|
49
51
|
bar: {
|
|
50
52
|
width: "60%",
|
|
51
53
|
height: "100%",
|
|
52
|
-
backgroundColor:
|
|
54
|
+
backgroundColor: textColor,
|
|
53
55
|
borderRadius: 2,
|
|
54
|
-
shadowColor: "#FFFFFF",
|
|
55
|
-
shadowOffset: { width: 0, height: 0 },
|
|
56
|
-
shadowOpacity: 0.8,
|
|
57
|
-
shadowRadius: 4,
|
|
58
|
-
elevation: 4,
|
|
59
56
|
},
|
|
60
57
|
text: {
|
|
61
58
|
fontSize: 13,
|
|
62
|
-
color:
|
|
59
|
+
color: textColor,
|
|
63
60
|
opacity: 0.9,
|
|
64
61
|
fontWeight: "500" as const,
|
|
65
62
|
letterSpacing: 0.8,
|
|
66
|
-
textShadowColor: "rgba(0, 0, 0, 0.15)",
|
|
67
|
-
textShadowOffset: { width: 0, height: 1 },
|
|
68
|
-
textShadowRadius: 2,
|
|
69
63
|
},
|
|
70
64
|
});
|
|
71
65
|
};
|
|
@@ -11,12 +11,14 @@ export interface SplashLogoProps {
|
|
|
11
11
|
logo?: string | React.ReactNode;
|
|
12
12
|
logoSize?: number;
|
|
13
13
|
glowSize?: number;
|
|
14
|
+
iconColor?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const SplashLogo: React.FC<SplashLogoProps> = ({
|
|
17
18
|
logo,
|
|
18
19
|
logoSize = 140,
|
|
19
20
|
glowSize = 160,
|
|
21
|
+
iconColor = "#FFFFFF",
|
|
20
22
|
}) => {
|
|
21
23
|
const styles = getStyles(logoSize, glowSize);
|
|
22
24
|
|
|
@@ -25,11 +27,11 @@ export const SplashLogo: React.FC<SplashLogoProps> = ({
|
|
|
25
27
|
<View style={styles.glow} />
|
|
26
28
|
<View style={styles.background}>
|
|
27
29
|
{typeof logo === "string" ? (
|
|
28
|
-
<AtomicIcon name={logo || "
|
|
30
|
+
<AtomicIcon name={logo || "sparkles"} size="xxl" customColor={iconColor} />
|
|
29
31
|
) : logo ? (
|
|
30
32
|
logo
|
|
31
33
|
) : (
|
|
32
|
-
<AtomicIcon name="
|
|
34
|
+
<AtomicIcon name="sparkles" size="xxl" customColor={iconColor} />
|
|
33
35
|
)}
|
|
34
36
|
</View>
|
|
35
37
|
</View>
|
|
@@ -52,11 +54,6 @@ const getStyles = (logoSize: number, glowSize: number) => {
|
|
|
52
54
|
height: glowSize,
|
|
53
55
|
borderRadius: glowRadius,
|
|
54
56
|
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
|
55
|
-
shadowColor: "#FFFFFF",
|
|
56
|
-
shadowOffset: { width: 0, height: 0 },
|
|
57
|
-
shadowOpacity: 0.5,
|
|
58
|
-
shadowRadius: 30,
|
|
59
|
-
elevation: 10,
|
|
60
57
|
},
|
|
61
58
|
background: {
|
|
62
59
|
width: logoSize,
|
|
@@ -67,11 +64,6 @@ const getStyles = (logoSize: number, glowSize: number) => {
|
|
|
67
64
|
justifyContent: "center",
|
|
68
65
|
borderWidth: 2,
|
|
69
66
|
borderColor: "rgba(255, 255, 255, 0.3)",
|
|
70
|
-
shadowColor: "#000",
|
|
71
|
-
shadowOffset: { width: 0, height: 4 },
|
|
72
|
-
shadowOpacity: 0.3,
|
|
73
|
-
shadowRadius: 8,
|
|
74
|
-
elevation: 8,
|
|
75
67
|
},
|
|
76
68
|
});
|
|
77
69
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Single Responsibility: Orchestrate splash screen UI
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useEffect } from "react";
|
|
6
|
+
import React, { useEffect, useRef } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { LinearGradient } from "expo-linear-gradient";
|
|
9
9
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -13,10 +13,14 @@ import { SplashLogo } from "./SplashLogo";
|
|
|
13
13
|
import { SplashTypography } from "./SplashTypography";
|
|
14
14
|
import { SplashLoading } from "./SplashLoading";
|
|
15
15
|
import { SplashDecorations } from "./SplashDecorations";
|
|
16
|
+
import { SplashErrorBoundary } from "./SplashErrorBoundary";
|
|
16
17
|
import type { SplashOptions } from "../../domain/entities/SplashOptions";
|
|
17
18
|
|
|
18
19
|
export interface SplashScreenProps extends SplashOptions {
|
|
19
20
|
visible?: boolean;
|
|
21
|
+
textColor?: string;
|
|
22
|
+
iconColor?: string;
|
|
23
|
+
decorationColor?: string;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -36,6 +40,9 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
36
40
|
renderContent,
|
|
37
41
|
renderFooter,
|
|
38
42
|
visible = true,
|
|
43
|
+
textColor,
|
|
44
|
+
iconColor,
|
|
45
|
+
decorationColor,
|
|
39
46
|
}) => {
|
|
40
47
|
const insets = useSafeAreaInsets();
|
|
41
48
|
const { t } = useLocalization();
|
|
@@ -43,23 +50,29 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
43
50
|
|
|
44
51
|
const styles = getStyles(insets, tokens.spacing);
|
|
45
52
|
|
|
53
|
+
const timerRef = useRef<NodeJS.Timeout>();
|
|
54
|
+
|
|
46
55
|
useEffect(() => {
|
|
47
56
|
if (!visible) return;
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
timerRef.current = setTimeout(() => {
|
|
50
59
|
if (onReady) {
|
|
51
60
|
onReady();
|
|
52
61
|
}
|
|
53
62
|
}, minimumDisplayTime);
|
|
54
63
|
|
|
55
|
-
return () =>
|
|
64
|
+
return () => {
|
|
65
|
+
if (timerRef.current) {
|
|
66
|
+
clearTimeout(timerRef.current);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
56
69
|
}, [visible, minimumDisplayTime, onReady]);
|
|
57
70
|
|
|
58
71
|
if (!visible) return null;
|
|
59
72
|
|
|
60
|
-
const displayAppName = appName || t("branding.appName", "
|
|
61
|
-
const displayTagline = tagline || t("branding.tagline", "
|
|
62
|
-
const displayLoadingText = loadingText || t("general.loading", "
|
|
73
|
+
const displayAppName = appName || t("branding.appName", "");
|
|
74
|
+
const displayTagline = tagline || t("branding.tagline", "");
|
|
75
|
+
const displayLoadingText = loadingText || t("general.loading", "");
|
|
63
76
|
|
|
64
77
|
// Use gradientColors if provided, otherwise use backgroundColor as solid color
|
|
65
78
|
const finalBackgroundColor = gradientColors
|
|
@@ -78,13 +91,13 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
78
91
|
|
|
79
92
|
const content = (
|
|
80
93
|
<>
|
|
81
|
-
<SplashDecorations />
|
|
94
|
+
<SplashDecorations decorationColor={decorationColor} />
|
|
82
95
|
|
|
83
96
|
<View style={styles.content}>
|
|
84
97
|
{renderLogo ? (
|
|
85
98
|
renderLogo()
|
|
86
99
|
) : (
|
|
87
|
-
<SplashLogo logo={logo} />
|
|
100
|
+
<SplashLogo logo={logo} iconColor={iconColor} />
|
|
88
101
|
)}
|
|
89
102
|
|
|
90
103
|
{renderContent ? (
|
|
@@ -94,6 +107,7 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
94
107
|
appName={displayAppName}
|
|
95
108
|
tagline={displayTagline}
|
|
96
109
|
tokens={tokens}
|
|
110
|
+
textColor={textColor}
|
|
97
111
|
/>
|
|
98
112
|
)}
|
|
99
113
|
</View>
|
|
@@ -103,6 +117,7 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
103
117
|
loadingText={displayLoadingText}
|
|
104
118
|
tokens={tokens}
|
|
105
119
|
bottomInset={insets.bottom}
|
|
120
|
+
textColor={textColor}
|
|
106
121
|
/>
|
|
107
122
|
)}
|
|
108
123
|
|
|
@@ -111,24 +126,26 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
111
126
|
);
|
|
112
127
|
|
|
113
128
|
return (
|
|
114
|
-
<
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
<SplashErrorBoundary>
|
|
130
|
+
<View style={styles.container}>
|
|
131
|
+
{hasGradient ? (
|
|
132
|
+
<LinearGradient
|
|
133
|
+
colors={finalGradientColors}
|
|
134
|
+
start={{ x: 0, y: 0 }}
|
|
135
|
+
end={{ x: 1, y: 1 }}
|
|
136
|
+
style={styles.gradient}
|
|
137
|
+
>
|
|
138
|
+
{content}
|
|
139
|
+
</LinearGradient>
|
|
140
|
+
) : (
|
|
141
|
+
<View
|
|
142
|
+
style={[styles.gradient, { backgroundColor: finalBackgroundColor }]}
|
|
143
|
+
>
|
|
144
|
+
{content}
|
|
145
|
+
</View>
|
|
146
|
+
)}
|
|
147
|
+
</View>
|
|
148
|
+
</SplashErrorBoundary>
|
|
132
149
|
);
|
|
133
150
|
};
|
|
134
151
|
|
|
@@ -11,14 +11,16 @@ export interface SplashTypographyProps {
|
|
|
11
11
|
appName: string;
|
|
12
12
|
tagline?: string;
|
|
13
13
|
tokens: DesignTokens;
|
|
14
|
+
textColor?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const SplashTypography: React.FC<SplashTypographyProps> = ({
|
|
17
18
|
appName,
|
|
18
19
|
tagline,
|
|
19
20
|
tokens,
|
|
21
|
+
textColor = "#FFFFFF",
|
|
20
22
|
}) => {
|
|
21
|
-
const styles = getStyles(tokens);
|
|
23
|
+
const styles = getStyles(tokens, textColor);
|
|
22
24
|
|
|
23
25
|
return (
|
|
24
26
|
<View style={styles.container}>
|
|
@@ -34,7 +36,7 @@ export const SplashTypography: React.FC<SplashTypographyProps> = ({
|
|
|
34
36
|
);
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
const getStyles = (tokens: DesignTokens) => {
|
|
39
|
+
const getStyles = (tokens: DesignTokens, textColor: string) => {
|
|
38
40
|
return StyleSheet.create({
|
|
39
41
|
container: {
|
|
40
42
|
alignItems: "center",
|
|
@@ -43,27 +45,21 @@ const getStyles = (tokens: DesignTokens) => {
|
|
|
43
45
|
appName: {
|
|
44
46
|
fontSize: 48,
|
|
45
47
|
fontWeight: "800" as const,
|
|
46
|
-
color:
|
|
48
|
+
color: textColor,
|
|
47
49
|
textAlign: "center" as const,
|
|
48
50
|
marginBottom: tokens.spacing.md,
|
|
49
51
|
letterSpacing: -1.2,
|
|
50
52
|
lineHeight: 56,
|
|
51
|
-
textShadowColor: "rgba(0, 0, 0, 0.25)",
|
|
52
|
-
textShadowOffset: { width: 0, height: 2 },
|
|
53
|
-
textShadowRadius: 6,
|
|
54
53
|
},
|
|
55
54
|
tagline: {
|
|
56
55
|
fontSize: 17,
|
|
57
|
-
color:
|
|
56
|
+
color: textColor,
|
|
58
57
|
textAlign: "center" as const,
|
|
59
58
|
opacity: 0.95,
|
|
60
59
|
maxWidth: 320,
|
|
61
60
|
lineHeight: 24,
|
|
62
61
|
fontWeight: "500" as const,
|
|
63
62
|
letterSpacing: 0.2,
|
|
64
|
-
textShadowColor: "rgba(0, 0, 0, 0.2)",
|
|
65
|
-
textShadowOffset: { width: 0, height: 1 },
|
|
66
|
-
textShadowRadius: 3,
|
|
67
63
|
},
|
|
68
64
|
});
|
|
69
65
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use Splash Hook
|
|
3
|
+
* Single Responsibility: Manage splash screen state and timing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useState, useRef } from "react";
|
|
7
|
+
|
|
8
|
+
interface UseSplashOptions {
|
|
9
|
+
minimumDisplayTime?: number;
|
|
10
|
+
onReady?: () => void | Promise<void>;
|
|
11
|
+
autoHide?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useSplash = ({
|
|
15
|
+
minimumDisplayTime = 1500,
|
|
16
|
+
onReady,
|
|
17
|
+
autoHide = true,
|
|
18
|
+
}: UseSplashOptions = {}) => {
|
|
19
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
20
|
+
const [isReady, setIsReady] = useState(false);
|
|
21
|
+
const timerRef = useRef<NodeJS.Timeout>();
|
|
22
|
+
const isReadyRef = useRef(false);
|
|
23
|
+
|
|
24
|
+
const hide = () => {
|
|
25
|
+
setIsVisible(false);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const show = () => {
|
|
29
|
+
setIsVisible(true);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const markReady = async () => {
|
|
33
|
+
if (isReadyRef.current) return;
|
|
34
|
+
|
|
35
|
+
isReadyRef.current = true;
|
|
36
|
+
setIsReady(true);
|
|
37
|
+
|
|
38
|
+
if (onReady) {
|
|
39
|
+
try {
|
|
40
|
+
await onReady();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (__DEV__) {
|
|
43
|
+
console.error("Splash onReady error:", error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (autoHide) {
|
|
49
|
+
timerRef.current = setTimeout(() => {
|
|
50
|
+
hide();
|
|
51
|
+
}, minimumDisplayTime);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
return () => {
|
|
57
|
+
if (timerRef.current) {
|
|
58
|
+
clearTimeout(timerRef.current);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
isVisible,
|
|
65
|
+
isReady,
|
|
66
|
+
hide,
|
|
67
|
+
show,
|
|
68
|
+
markReady,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module 'expo-linear-gradient' {
|
|
2
|
+
import { View, ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface LinearGradientProps extends ViewProps {
|
|
5
|
+
colors: readonly string[];
|
|
6
|
+
start?: { x: number; y: number };
|
|
7
|
+
end?: { x: number; y: number };
|
|
8
|
+
locations?: readonly number[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const LinearGradient: React.FC<LinearGradientProps>;
|
|
12
|
+
}
|