@umituz/react-native-splash 1.6.4 → 1.7.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/lib/__tests__/mocks/expoLinearGradient.js +7 -0
- package/lib/__tests__/mocks/reactNative.js +16 -0
- package/lib/__tests__/setup.ts +57 -0
- package/lib/__tests__/utils/testUtils.tsx +86 -0
- package/lib/domain/entities/SplashOptions.ts +74 -0
- package/lib/index.ts +31 -0
- package/lib/presentation/components/SplashDecorations.tsx +56 -0
- package/lib/presentation/components/SplashErrorBoundary.tsx +63 -0
- package/lib/presentation/components/SplashLoading.tsx +74 -0
- package/lib/presentation/components/SplashLogo.tsx +80 -0
- package/lib/presentation/components/SplashScreen.tsx +175 -0
- package/lib/presentation/components/SplashTypography.tsx +72 -0
- package/lib/presentation/hooks/useSplash.ts +70 -0
- package/lib/presentation/utils/splashGradient.utils.ts +47 -0
- package/lib/types/expo-linear-gradient.d.ts +12 -0
- package/package.json +18 -5
- package/src/__tests__/SplashScreen.test.tsx +161 -0
- package/src/__tests__/accessibility/Accessibility.test.tsx +264 -0
- package/src/__tests__/basic/Basic.test.tsx +106 -0
- package/src/__tests__/basic/Simple.test.tsx +32 -0
- package/src/__tests__/edge-cases/EdgeCases.test.tsx +446 -0
- package/src/__tests__/integration/SplashScreen.integration.test.tsx +200 -0
- package/src/__tests__/mocks/expoLinearGradient.js +7 -0
- package/src/__tests__/mocks/reactNative.js +16 -0
- package/src/__tests__/performance/Performance.test.tsx +297 -0
- package/src/__tests__/setup.ts +57 -0
- package/src/__tests__/useSplash.test.tsx +123 -0
- package/src/__tests__/utils/testUtils.tsx +86 -0
- package/src/__tests__/visual/VisualRegression.test.tsx +338 -0
- 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 +63 -0
- package/src/presentation/components/SplashLoading.tsx +7 -5
- package/src/presentation/components/SplashLogo.tsx +4 -2
- package/src/presentation/components/SplashScreen.tsx +43 -26
- package/src/presentation/components/SplashTypography.tsx +6 -4
- 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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Regression Tests - UI Consistency
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render } from '../utils/testUtils';
|
|
7
|
+
import { SplashScreen } from '../../presentation/components/SplashScreen';
|
|
8
|
+
|
|
9
|
+
describe('Visual Regression Tests', () => {
|
|
10
|
+
const baseProps = {
|
|
11
|
+
appName: 'Visual Test App',
|
|
12
|
+
tagline: 'Testing visual consistency',
|
|
13
|
+
visible: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('Basic Layout Consistency', () => {
|
|
17
|
+
it('maintains consistent layout structure', () => {
|
|
18
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
19
|
+
|
|
20
|
+
const splashContainer = getByTestId('splash-screen');
|
|
21
|
+
expect(splashContainer).toBeTruthy();
|
|
22
|
+
|
|
23
|
+
// Check for consistent structure
|
|
24
|
+
expect(splashContainer.children).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders logo consistently', () => {
|
|
28
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
29
|
+
|
|
30
|
+
const logoElement = getByTestId('splash-logo');
|
|
31
|
+
expect(logoElement).toBeTruthy();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders typography consistently', () => {
|
|
35
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
36
|
+
|
|
37
|
+
const typographyElement = getByTestId('splash-typography');
|
|
38
|
+
expect(typographyElement).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renders loading indicator consistently', () => {
|
|
42
|
+
const props = { ...baseProps, showLoading: true };
|
|
43
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
44
|
+
|
|
45
|
+
const loadingElement = getByTestId('splash-loading');
|
|
46
|
+
expect(loadingElement).toBeTruthy();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Color Consistency', () => {
|
|
51
|
+
it('applies text color consistently', () => {
|
|
52
|
+
const props = {
|
|
53
|
+
...baseProps,
|
|
54
|
+
textColor: '#FF0000',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
58
|
+
|
|
59
|
+
const appNameElement = getByText('Visual Test App');
|
|
60
|
+
expect(appNameElement.props.style).toContainEqual({
|
|
61
|
+
color: '#FF0000',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('applies icon color consistently', () => {
|
|
66
|
+
const props = {
|
|
67
|
+
...baseProps,
|
|
68
|
+
iconColor: '#00FF00',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
72
|
+
|
|
73
|
+
const iconElement = getByTestId('splash-icon');
|
|
74
|
+
expect(iconElement.props.customColor).toBe('#00FF00');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('applies decoration color consistently', () => {
|
|
78
|
+
const props = {
|
|
79
|
+
...baseProps,
|
|
80
|
+
decorationColor: 'rgba(255, 0, 0, 0.1)',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
84
|
+
|
|
85
|
+
const decorationElement = getByTestId('splash-decorations');
|
|
86
|
+
expect(decorationElement).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('handles gradient colors consistently', () => {
|
|
90
|
+
const props = {
|
|
91
|
+
...baseProps,
|
|
92
|
+
gradientColors: ['#FF0000', '#00FF00', '#0000FF'],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
96
|
+
|
|
97
|
+
const gradientElement = getByTestId('linear-gradient');
|
|
98
|
+
expect(gradientElement.props.colors).toEqual([
|
|
99
|
+
'#FF0000',
|
|
100
|
+
'#00FF00',
|
|
101
|
+
'#0000FF',
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('Typography Consistency', () => {
|
|
107
|
+
it('maintains consistent font sizes', () => {
|
|
108
|
+
const { getByText } = render(<SplashScreen {...baseProps} />);
|
|
109
|
+
|
|
110
|
+
const appNameElement = getByText('Visual Test App');
|
|
111
|
+
expect(appNameElement.props.style).toContainEqual({
|
|
112
|
+
fontSize: 48,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('maintains consistent font weights', () => {
|
|
117
|
+
const { getByText } = render(<SplashScreen {...baseProps} />);
|
|
118
|
+
|
|
119
|
+
const appNameElement = getByText('Visual Test App');
|
|
120
|
+
expect(appNameElement.props.style).toContainEqual({
|
|
121
|
+
fontWeight: '800',
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('maintains consistent text alignment', () => {
|
|
126
|
+
const { getByText } = render(<SplashScreen {...baseProps} />);
|
|
127
|
+
|
|
128
|
+
const appNameElement = getByText('Visual Test App');
|
|
129
|
+
expect(appNameElement.props.style).toContainEqual({
|
|
130
|
+
textAlign: 'center',
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles long text consistently', () => {
|
|
135
|
+
const longAppName = 'Very Long Application Name That Might Wrap';
|
|
136
|
+
const props = {
|
|
137
|
+
...baseProps,
|
|
138
|
+
appName: longAppName,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
142
|
+
|
|
143
|
+
const appNameElement = getByText(longAppName);
|
|
144
|
+
expect(appNameElement.props.numberOfLines).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('Spacing Consistency', () => {
|
|
149
|
+
it('maintains consistent padding', () => {
|
|
150
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
151
|
+
|
|
152
|
+
const contentElement = getByTestId('splash-content');
|
|
153
|
+
expect(contentElement.props.style).toContainEqual({
|
|
154
|
+
paddingHorizontal: 24, // xl spacing
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('maintains consistent margins', () => {
|
|
159
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
160
|
+
|
|
161
|
+
const typographyElement = getByTestId('splash-typography');
|
|
162
|
+
expect(typographyElement.props.style).toContainEqual({
|
|
163
|
+
marginTop: 24, // xl spacing
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('Responsive Consistency', () => {
|
|
169
|
+
it('maintains consistency across different safe areas', () => {
|
|
170
|
+
// Mock different safe area insets
|
|
171
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
172
|
+
|
|
173
|
+
const splashContainer = getByTestId('splash-screen');
|
|
174
|
+
expect(splashContainer).toBeTruthy();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('handles different screen sizes consistently', () => {
|
|
178
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
179
|
+
|
|
180
|
+
const splashContainer = getByTestId('splash-screen');
|
|
181
|
+
expect(splashContainer.props.style).toContainEqual({
|
|
182
|
+
flex: 1,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('State Consistency', () => {
|
|
188
|
+
it('maintains visual consistency when hidden', () => {
|
|
189
|
+
const props = { ...baseProps, visible: false };
|
|
190
|
+
const { queryByText } = render(<SplashScreen {...props} />);
|
|
191
|
+
|
|
192
|
+
expect(queryByText('Visual Test App')).toBeFalsy();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('maintains visual consistency when shown', () => {
|
|
196
|
+
const props = { ...baseProps, visible: true };
|
|
197
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
198
|
+
|
|
199
|
+
expect(getByText('Visual Test App')).toBeTruthy();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('maintains consistency during loading state', () => {
|
|
203
|
+
const props = {
|
|
204
|
+
...baseProps,
|
|
205
|
+
showLoading: true,
|
|
206
|
+
loadingText: 'Custom loading text',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
210
|
+
|
|
211
|
+
expect(getByText('Custom loading text')).toBeTruthy();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('Custom Render Consistency', () => {
|
|
216
|
+
it('maintains consistency with custom logo', () => {
|
|
217
|
+
const CustomLogo = () => <div testID="custom-logo">Custom Logo</div>;
|
|
218
|
+
const props = {
|
|
219
|
+
...baseProps,
|
|
220
|
+
renderLogo: () => <CustomLogo />,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
224
|
+
|
|
225
|
+
expect(getByTestId('custom-logo')).toBeTruthy();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('maintains consistency with custom content', () => {
|
|
229
|
+
const CustomContent = () => <div testID="custom-content">Custom Content</div>;
|
|
230
|
+
const props = {
|
|
231
|
+
...baseProps,
|
|
232
|
+
renderContent: () => <CustomContent />,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
236
|
+
|
|
237
|
+
expect(getByTestId('custom-content')).toBeTruthy();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('maintains consistency with custom footer', () => {
|
|
241
|
+
const CustomFooter = () => <div testID="custom-footer">Custom Footer</div>;
|
|
242
|
+
const props = {
|
|
243
|
+
...baseProps,
|
|
244
|
+
renderFooter: () => <CustomFooter />,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
248
|
+
|
|
249
|
+
expect(getByTestId('custom-footer')).toBeTruthy();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('Animation Consistency', () => {
|
|
254
|
+
it('maintains consistent animation states', () => {
|
|
255
|
+
const { getByTestId } = render(<SplashScreen {...baseProps} />);
|
|
256
|
+
|
|
257
|
+
const splashContainer = getByTestId('splash-screen');
|
|
258
|
+
expect(splashContainer).toBeTruthy();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('Theme Consistency', () => {
|
|
263
|
+
it('maintains consistency with light theme', () => {
|
|
264
|
+
const props = {
|
|
265
|
+
...baseProps,
|
|
266
|
+
textColor: '#000000',
|
|
267
|
+
backgroundColor: '#FFFFFF',
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
271
|
+
|
|
272
|
+
const appNameElement = getByText('Visual Test App');
|
|
273
|
+
expect(appNameElement.props.style).toContainEqual({
|
|
274
|
+
color: '#000000',
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('maintains consistency with dark theme', () => {
|
|
279
|
+
const props = {
|
|
280
|
+
...baseProps,
|
|
281
|
+
textColor: '#FFFFFF',
|
|
282
|
+
backgroundColor: '#000000',
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
286
|
+
|
|
287
|
+
const appNameElement = getByText('Visual Test App');
|
|
288
|
+
expect(appNameElement.props.style).toContainEqual({
|
|
289
|
+
color: '#FFFFFF',
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('Error State Consistency', () => {
|
|
295
|
+
it('maintains consistent error display', () => {
|
|
296
|
+
const ErrorComponent = () => {
|
|
297
|
+
throw new Error('Test error');
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const props = {
|
|
301
|
+
...baseProps,
|
|
302
|
+
renderLogo: () => <ErrorComponent />,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
306
|
+
|
|
307
|
+
expect(getByText('Something went wrong')).toBeTruthy();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('Performance Consistency', () => {
|
|
312
|
+
it('maintains consistent render performance', async () => {
|
|
313
|
+
const { measureRenderTime } = require('../utils/testUtils');
|
|
314
|
+
|
|
315
|
+
const renderTime = await measureRenderTime(<SplashScreen {...baseProps} />);
|
|
316
|
+
|
|
317
|
+
// Should maintain consistent performance
|
|
318
|
+
expect(renderTime).toBeLessThan(100); // 100ms
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('maintains consistency with complex props', async () => {
|
|
322
|
+
const { measureRenderTime } = require('../utils/testUtils');
|
|
323
|
+
|
|
324
|
+
const complexProps = {
|
|
325
|
+
...baseProps,
|
|
326
|
+
gradientColors: ['#FF0000', '#00FF00', '#0000FF'],
|
|
327
|
+
textColor: '#FFFFFF',
|
|
328
|
+
iconColor: '#FFFF00',
|
|
329
|
+
decorationColor: 'rgba(255, 255, 255, 0.1)',
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const renderTime = await measureRenderTime(<SplashScreen {...complexProps} />);
|
|
333
|
+
|
|
334
|
+
// Should maintain performance even with complex props
|
|
335
|
+
expect(renderTime).toBeLessThan(150); // 150ms
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|
|
@@ -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,63 @@
|
|
|
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, Text, StyleSheet } from "react-native";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface State {
|
|
15
|
+
hasError: boolean;
|
|
16
|
+
error?: Error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class SplashErrorBoundary extends Component<Props, State> {
|
|
20
|
+
constructor(props: Props) {
|
|
21
|
+
super(props);
|
|
22
|
+
this.state = { hasError: false };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static getDerivedStateFromError(error: Error): State {
|
|
26
|
+
return { hasError: true, error };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
30
|
+
if (__DEV__) {
|
|
31
|
+
console.error("Splash Screen Error:", error, errorInfo);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
if (this.state.hasError) {
|
|
37
|
+
if (this.props.fallback) {
|
|
38
|
+
return this.props.fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={styles.container}>
|
|
43
|
+
<Text style={styles.text}>Something went wrong</Text>
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return this.props.children;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const styles = StyleSheet.create({
|
|
53
|
+
container: {
|
|
54
|
+
flex: 1,
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
justifyContent: "center",
|
|
57
|
+
backgroundColor: "#000000",
|
|
58
|
+
},
|
|
59
|
+
text: {
|
|
60
|
+
color: "#FFFFFF",
|
|
61
|
+
fontSize: 16,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -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,9 +51,9 @@ 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:
|
|
56
|
+
shadowColor: textColor,
|
|
55
57
|
shadowOffset: { width: 0, height: 0 },
|
|
56
58
|
shadowOpacity: 0.8,
|
|
57
59
|
shadowRadius: 4,
|
|
@@ -59,7 +61,7 @@ const getStyles = (tokens: DesignTokens, bottomInset: number) => {
|
|
|
59
61
|
},
|
|
60
62
|
text: {
|
|
61
63
|
fontSize: 13,
|
|
62
|
-
color:
|
|
64
|
+
color: textColor,
|
|
63
65
|
opacity: 0.9,
|
|
64
66
|
fontWeight: "500" as const,
|
|
65
67
|
letterSpacing: 0.8,
|
|
@@ -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>
|
|
@@ -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
|
|