@croacroa/react-native-template 1.0.0 → 2.0.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.
Files changed (70) hide show
  1. package/.github/workflows/ci.yml +187 -184
  2. package/.github/workflows/eas-build.yml +55 -55
  3. package/.github/workflows/eas-update.yml +50 -50
  4. package/CHANGELOG.md +106 -106
  5. package/CONTRIBUTING.md +377 -377
  6. package/README.md +399 -399
  7. package/__tests__/components/snapshots.test.tsx +131 -0
  8. package/__tests__/integration/auth-api.test.tsx +227 -0
  9. package/__tests__/performance/VirtualizedList.perf.test.tsx +362 -0
  10. package/app/(public)/onboarding.tsx +5 -5
  11. package/app.config.ts +45 -2
  12. package/assets/images/.gitkeep +7 -7
  13. package/components/onboarding/OnboardingScreen.tsx +370 -370
  14. package/components/onboarding/index.ts +2 -2
  15. package/components/providers/SuspenseBoundary.tsx +357 -0
  16. package/components/providers/index.ts +21 -0
  17. package/components/ui/Avatar.tsx +316 -316
  18. package/components/ui/Badge.tsx +416 -416
  19. package/components/ui/BottomSheet.tsx +307 -307
  20. package/components/ui/Checkbox.tsx +261 -261
  21. package/components/ui/OptimizedImage.tsx +369 -369
  22. package/components/ui/Select.tsx +240 -240
  23. package/components/ui/VirtualizedList.tsx +285 -0
  24. package/components/ui/index.ts +23 -18
  25. package/constants/config.ts +97 -54
  26. package/docs/adr/001-state-management.md +79 -79
  27. package/docs/adr/002-styling-approach.md +130 -130
  28. package/docs/adr/003-data-fetching.md +155 -155
  29. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  30. package/docs/adr/README.md +78 -78
  31. package/hooks/index.ts +27 -25
  32. package/hooks/useApi.ts +102 -5
  33. package/hooks/useAuth.tsx +82 -0
  34. package/hooks/useBiometrics.ts +295 -295
  35. package/hooks/useDeepLinking.ts +256 -256
  36. package/hooks/useMFA.ts +499 -0
  37. package/hooks/useNotifications.ts +39 -0
  38. package/hooks/useOffline.ts +60 -6
  39. package/hooks/usePerformance.ts +434 -434
  40. package/hooks/useTheme.tsx +76 -0
  41. package/hooks/useUpdates.ts +358 -358
  42. package/i18n/index.ts +194 -77
  43. package/i18n/locales/ar.json +101 -0
  44. package/i18n/locales/de.json +101 -0
  45. package/i18n/locales/en.json +101 -101
  46. package/i18n/locales/es.json +101 -0
  47. package/i18n/locales/fr.json +101 -101
  48. package/jest.config.js +4 -4
  49. package/maestro/README.md +113 -113
  50. package/maestro/config.yaml +35 -35
  51. package/maestro/flows/login.yaml +62 -62
  52. package/maestro/flows/mfa-login.yaml +92 -0
  53. package/maestro/flows/mfa-setup.yaml +86 -0
  54. package/maestro/flows/navigation.yaml +68 -68
  55. package/maestro/flows/offline-conflict.yaml +101 -0
  56. package/maestro/flows/offline-sync.yaml +128 -0
  57. package/maestro/flows/offline.yaml +60 -60
  58. package/maestro/flows/register.yaml +94 -94
  59. package/package.json +175 -170
  60. package/services/analytics.ts +428 -428
  61. package/services/api.ts +340 -340
  62. package/services/authAdapter.ts +333 -333
  63. package/services/backgroundSync.ts +626 -0
  64. package/services/index.ts +54 -22
  65. package/services/security.ts +286 -0
  66. package/tailwind.config.js +47 -47
  67. package/utils/accessibility.ts +446 -446
  68. package/utils/index.ts +52 -43
  69. package/utils/validation.ts +2 -1
  70. package/utils/withAccessibility.tsx +272 -0
@@ -1,369 +1,369 @@
1
- import { useState, useCallback } from "react";
2
- import { View, StyleSheet } from "react-native";
3
- import { Image, ImageProps, ImageContentFit } from "expo-image";
4
- import Animated, {
5
- useAnimatedStyle,
6
- withTiming,
7
- interpolate,
8
- useSharedValue,
9
- } from "react-native-reanimated";
10
- import { useTheme } from "@/hooks/useTheme";
11
- import { cn } from "@/utils/cn";
12
-
13
- const AnimatedImage = Animated.createAnimatedComponent(Image);
14
-
15
- type ImagePriority = "low" | "normal" | "high";
16
-
17
- interface OptimizedImageProps {
18
- /**
19
- * Image source URL
20
- */
21
- source: string | number;
22
-
23
- /**
24
- * Alt text for accessibility
25
- */
26
- alt?: string;
27
-
28
- /**
29
- * Image width
30
- */
31
- width?: number | string;
32
-
33
- /**
34
- * Image height
35
- */
36
- height?: number | string;
37
-
38
- /**
39
- * Aspect ratio (e.g., 16/9, 1, 4/3)
40
- */
41
- aspectRatio?: number;
42
-
43
- /**
44
- * How to fit the image in the container
45
- */
46
- contentFit?: ImageContentFit;
47
-
48
- /**
49
- * Blur hash or thumbhash for placeholder
50
- */
51
- placeholder?: string | number;
52
-
53
- /**
54
- * Loading priority
55
- */
56
- priority?: ImagePriority;
57
-
58
- /**
59
- * Whether to enable caching
60
- */
61
- cachePolicy?: "none" | "disk" | "memory" | "memory-disk";
62
-
63
- /**
64
- * Transition duration in ms
65
- */
66
- transitionDuration?: number;
67
-
68
- /**
69
- * Additional class name for container
70
- */
71
- className?: string;
72
-
73
- /**
74
- * Whether to show loading skeleton
75
- */
76
- showSkeleton?: boolean;
77
-
78
- /**
79
- * Border radius
80
- */
81
- borderRadius?: number;
82
-
83
- /**
84
- * Called when image loads successfully
85
- */
86
- onLoad?: () => void;
87
-
88
- /**
89
- * Called when image fails to load
90
- */
91
- onError?: (error: Error) => void;
92
-
93
- /**
94
- * Style override
95
- */
96
- style?: ImageProps["style"];
97
- }
98
-
99
- export function OptimizedImage({
100
- source,
101
- alt,
102
- width,
103
- height,
104
- aspectRatio,
105
- contentFit = "cover",
106
- placeholder,
107
- priority = "normal",
108
- cachePolicy = "memory-disk",
109
- transitionDuration = 300,
110
- className,
111
- showSkeleton = true,
112
- borderRadius = 0,
113
- onLoad,
114
- onError,
115
- style,
116
- }: OptimizedImageProps) {
117
- const { isDark } = useTheme();
118
- const [isLoading, setIsLoading] = useState(true);
119
- const [hasError, setHasError] = useState(false);
120
- const opacity = useSharedValue(0);
121
-
122
- const handleLoad = useCallback(() => {
123
- setIsLoading(false);
124
- opacity.value = withTiming(1, { duration: transitionDuration });
125
- onLoad?.();
126
- }, [opacity, transitionDuration, onLoad]);
127
-
128
- const handleError = useCallback(
129
- (error: { error: string }) => {
130
- setIsLoading(false);
131
- setHasError(true);
132
- onError?.(new Error(error.error));
133
- },
134
- [onError]
135
- );
136
-
137
- const animatedStyle = useAnimatedStyle(() => {
138
- return {
139
- opacity: interpolate(opacity.value, [0, 1], [0, 1]),
140
- };
141
- });
142
-
143
- // Determine the source
144
- const imageSource = typeof source === "string" ? { uri: source } : source;
145
-
146
- // Map priority to expo-image priority
147
- const imagePriority =
148
- priority === "high" ? "high" : priority === "low" ? "low" : "normal";
149
-
150
- return (
151
- <View
152
- className={cn("overflow-hidden", className)}
153
- style={[
154
- {
155
- width,
156
- height,
157
- aspectRatio,
158
- borderRadius,
159
- backgroundColor: isDark ? "#1e293b" : "#f1f5f9",
160
- },
161
- ]}
162
- >
163
- {/* Skeleton loader */}
164
- {showSkeleton && isLoading && !hasError && (
165
- <SkeletonLoader borderRadius={borderRadius} />
166
- )}
167
-
168
- {/* Error state */}
169
- {hasError && (
170
- <View
171
- style={[styles.errorContainer, { borderRadius }]}
172
- className={isDark ? "bg-gray-800" : "bg-gray-100"}
173
- >
174
- <View className="items-center justify-center">
175
- <View
176
- className={cn(
177
- "w-12 h-12 rounded-full items-center justify-center mb-2",
178
- isDark ? "bg-gray-700" : "bg-gray-200"
179
- )}
180
- >
181
- <ErrorIcon isDark={isDark} />
182
- </View>
183
- </View>
184
- </View>
185
- )}
186
-
187
- {/* Actual image */}
188
- {!hasError && (
189
- <AnimatedImage
190
- source={imageSource}
191
- contentFit={contentFit}
192
- placeholder={placeholder}
193
- placeholderContentFit="cover"
194
- transition={transitionDuration}
195
- priority={imagePriority}
196
- cachePolicy={cachePolicy}
197
- onLoad={handleLoad}
198
- onError={handleError}
199
- accessibilityLabel={alt}
200
- style={[styles.image, { borderRadius }, animatedStyle, style]}
201
- />
202
- )}
203
- </View>
204
- );
205
- }
206
-
207
- /**
208
- * Skeleton loader with shimmer effect
209
- */
210
- function SkeletonLoader({ borderRadius }: { borderRadius: number }) {
211
- const { isDark } = useTheme();
212
- const shimmer = useSharedValue(0);
213
-
214
- // Start shimmer animation
215
- useState(() => {
216
- shimmer.value = withTiming(1, { duration: 1500 }, () => {
217
- shimmer.value = 0;
218
- });
219
- });
220
-
221
- const shimmerStyle = useAnimatedStyle(() => {
222
- return {
223
- opacity: interpolate(shimmer.value, [0, 0.5, 1], [0.3, 0.6, 0.3]),
224
- };
225
- });
226
-
227
- return (
228
- <Animated.View
229
- style={[
230
- styles.skeleton,
231
- { borderRadius },
232
- shimmerStyle,
233
- { backgroundColor: isDark ? "#334155" : "#e2e8f0" },
234
- ]}
235
- />
236
- );
237
- }
238
-
239
- /**
240
- * Error icon component
241
- */
242
- function ErrorIcon({ isDark }: { isDark: boolean }) {
243
- return (
244
- <View
245
- style={{
246
- width: 24,
247
- height: 24,
248
- borderRadius: 12,
249
- backgroundColor: isDark ? "#475569" : "#cbd5e1",
250
- alignItems: "center",
251
- justifyContent: "center",
252
- }}
253
- >
254
- <View
255
- style={{
256
- width: 12,
257
- height: 2,
258
- backgroundColor: isDark ? "#94a3b8" : "#64748b",
259
- transform: [{ rotate: "45deg" }],
260
- }}
261
- />
262
- </View>
263
- );
264
- }
265
-
266
- const styles = StyleSheet.create({
267
- image: {
268
- width: "100%",
269
- height: "100%",
270
- position: "absolute",
271
- top: 0,
272
- left: 0,
273
- },
274
- skeleton: {
275
- ...StyleSheet.absoluteFillObject,
276
- },
277
- errorContainer: {
278
- ...StyleSheet.absoluteFillObject,
279
- alignItems: "center",
280
- justifyContent: "center",
281
- },
282
- });
283
-
284
- /**
285
- * Background Image component
286
- */
287
- interface BackgroundImageProps extends OptimizedImageProps {
288
- children?: React.ReactNode;
289
- overlayColor?: string;
290
- overlayOpacity?: number;
291
- }
292
-
293
- export function BackgroundImage({
294
- children,
295
- overlayColor = "#000000",
296
- overlayOpacity = 0.4,
297
- ...props
298
- }: BackgroundImageProps) {
299
- return (
300
- <View style={styles.backgroundContainer}>
301
- <OptimizedImage {...props} style={StyleSheet.absoluteFill} />
302
- {overlayOpacity > 0 && (
303
- <View
304
- style={[
305
- StyleSheet.absoluteFill,
306
- {
307
- backgroundColor: overlayColor,
308
- opacity: overlayOpacity,
309
- },
310
- ]}
311
- />
312
- )}
313
- <View style={styles.backgroundContent}>{children}</View>
314
- </View>
315
- );
316
- }
317
-
318
- /**
319
- * Image with progressive loading (blur to sharp)
320
- */
321
- interface ProgressiveImageProps extends OptimizedImageProps {
322
- /**
323
- * Low-quality placeholder image
324
- */
325
- thumbnail?: string;
326
- }
327
-
328
- export function ProgressiveImage({
329
- thumbnail,
330
- source,
331
- ...props
332
- }: ProgressiveImageProps) {
333
- const [isFullLoaded, setIsFullLoaded] = useState(false);
334
-
335
- return (
336
- <View style={{ position: "relative", overflow: "hidden" }}>
337
- {/* Thumbnail (blurred) */}
338
- {thumbnail && !isFullLoaded && (
339
- <Image
340
- source={{ uri: thumbnail }}
341
- style={[StyleSheet.absoluteFill, { opacity: 0.5 }]}
342
- contentFit="cover"
343
- blurRadius={10}
344
- />
345
- )}
346
-
347
- {/* Full resolution image */}
348
- <OptimizedImage
349
- {...props}
350
- source={source}
351
- onLoad={() => {
352
- setIsFullLoaded(true);
353
- props.onLoad?.();
354
- }}
355
- showSkeleton={!thumbnail}
356
- />
357
- </View>
358
- );
359
- }
360
-
361
- Object.assign(styles, {
362
- backgroundContainer: {
363
- flex: 1,
364
- },
365
- backgroundContent: {
366
- flex: 1,
367
- zIndex: 1,
368
- },
369
- });
1
+ import { useState, useCallback } from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { Image, ImageProps, ImageContentFit } from "expo-image";
4
+ import Animated, {
5
+ useAnimatedStyle,
6
+ withTiming,
7
+ interpolate,
8
+ useSharedValue,
9
+ } from "react-native-reanimated";
10
+ import { useTheme } from "@/hooks/useTheme";
11
+ import { cn } from "@/utils/cn";
12
+
13
+ const AnimatedImage = Animated.createAnimatedComponent(Image);
14
+
15
+ type ImagePriority = "low" | "normal" | "high";
16
+
17
+ interface OptimizedImageProps {
18
+ /**
19
+ * Image source URL
20
+ */
21
+ source: string | number;
22
+
23
+ /**
24
+ * Alt text for accessibility
25
+ */
26
+ alt?: string;
27
+
28
+ /**
29
+ * Image width
30
+ */
31
+ width?: number | string;
32
+
33
+ /**
34
+ * Image height
35
+ */
36
+ height?: number | string;
37
+
38
+ /**
39
+ * Aspect ratio (e.g., 16/9, 1, 4/3)
40
+ */
41
+ aspectRatio?: number;
42
+
43
+ /**
44
+ * How to fit the image in the container
45
+ */
46
+ contentFit?: ImageContentFit;
47
+
48
+ /**
49
+ * Blur hash or thumbhash for placeholder
50
+ */
51
+ placeholder?: string | number;
52
+
53
+ /**
54
+ * Loading priority
55
+ */
56
+ priority?: ImagePriority;
57
+
58
+ /**
59
+ * Whether to enable caching
60
+ */
61
+ cachePolicy?: "none" | "disk" | "memory" | "memory-disk";
62
+
63
+ /**
64
+ * Transition duration in ms
65
+ */
66
+ transitionDuration?: number;
67
+
68
+ /**
69
+ * Additional class name for container
70
+ */
71
+ className?: string;
72
+
73
+ /**
74
+ * Whether to show loading skeleton
75
+ */
76
+ showSkeleton?: boolean;
77
+
78
+ /**
79
+ * Border radius
80
+ */
81
+ borderRadius?: number;
82
+
83
+ /**
84
+ * Called when image loads successfully
85
+ */
86
+ onLoad?: () => void;
87
+
88
+ /**
89
+ * Called when image fails to load
90
+ */
91
+ onError?: (error: Error) => void;
92
+
93
+ /**
94
+ * Style override
95
+ */
96
+ style?: ImageProps["style"];
97
+ }
98
+
99
+ export function OptimizedImage({
100
+ source,
101
+ alt,
102
+ width,
103
+ height,
104
+ aspectRatio,
105
+ contentFit = "cover",
106
+ placeholder,
107
+ priority = "normal",
108
+ cachePolicy = "memory-disk",
109
+ transitionDuration = 300,
110
+ className,
111
+ showSkeleton = true,
112
+ borderRadius = 0,
113
+ onLoad,
114
+ onError,
115
+ style,
116
+ }: OptimizedImageProps) {
117
+ const { isDark } = useTheme();
118
+ const [isLoading, setIsLoading] = useState(true);
119
+ const [hasError, setHasError] = useState(false);
120
+ const opacity = useSharedValue(0);
121
+
122
+ const handleLoad = useCallback(() => {
123
+ setIsLoading(false);
124
+ opacity.value = withTiming(1, { duration: transitionDuration });
125
+ onLoad?.();
126
+ }, [opacity, transitionDuration, onLoad]);
127
+
128
+ const handleError = useCallback(
129
+ (error: { error: string }) => {
130
+ setIsLoading(false);
131
+ setHasError(true);
132
+ onError?.(new Error(error.error));
133
+ },
134
+ [onError]
135
+ );
136
+
137
+ const animatedStyle = useAnimatedStyle(() => {
138
+ return {
139
+ opacity: interpolate(opacity.value, [0, 1], [0, 1]),
140
+ };
141
+ });
142
+
143
+ // Determine the source
144
+ const imageSource = typeof source === "string" ? { uri: source } : source;
145
+
146
+ // Map priority to expo-image priority
147
+ const imagePriority =
148
+ priority === "high" ? "high" : priority === "low" ? "low" : "normal";
149
+
150
+ return (
151
+ <View
152
+ className={cn("overflow-hidden", className)}
153
+ style={[
154
+ {
155
+ width,
156
+ height,
157
+ aspectRatio,
158
+ borderRadius,
159
+ backgroundColor: isDark ? "#1e293b" : "#f1f5f9",
160
+ },
161
+ ]}
162
+ >
163
+ {/* Skeleton loader */}
164
+ {showSkeleton && isLoading && !hasError && (
165
+ <SkeletonLoader borderRadius={borderRadius} />
166
+ )}
167
+
168
+ {/* Error state */}
169
+ {hasError && (
170
+ <View
171
+ style={[styles.errorContainer, { borderRadius }]}
172
+ className={isDark ? "bg-gray-800" : "bg-gray-100"}
173
+ >
174
+ <View className="items-center justify-center">
175
+ <View
176
+ className={cn(
177
+ "w-12 h-12 rounded-full items-center justify-center mb-2",
178
+ isDark ? "bg-gray-700" : "bg-gray-200"
179
+ )}
180
+ >
181
+ <ErrorIcon isDark={isDark} />
182
+ </View>
183
+ </View>
184
+ </View>
185
+ )}
186
+
187
+ {/* Actual image */}
188
+ {!hasError && (
189
+ <AnimatedImage
190
+ source={imageSource}
191
+ contentFit={contentFit}
192
+ placeholder={placeholder}
193
+ placeholderContentFit="cover"
194
+ transition={transitionDuration}
195
+ priority={imagePriority}
196
+ cachePolicy={cachePolicy}
197
+ onLoad={handleLoad}
198
+ onError={handleError}
199
+ accessibilityLabel={alt}
200
+ style={[styles.image, { borderRadius }, animatedStyle, style]}
201
+ />
202
+ )}
203
+ </View>
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Skeleton loader with shimmer effect
209
+ */
210
+ function SkeletonLoader({ borderRadius }: { borderRadius: number }) {
211
+ const { isDark } = useTheme();
212
+ const shimmer = useSharedValue(0);
213
+
214
+ // Start shimmer animation
215
+ useState(() => {
216
+ shimmer.value = withTiming(1, { duration: 1500 }, () => {
217
+ shimmer.value = 0;
218
+ });
219
+ });
220
+
221
+ const shimmerStyle = useAnimatedStyle(() => {
222
+ return {
223
+ opacity: interpolate(shimmer.value, [0, 0.5, 1], [0.3, 0.6, 0.3]),
224
+ };
225
+ });
226
+
227
+ return (
228
+ <Animated.View
229
+ style={[
230
+ styles.skeleton,
231
+ { borderRadius },
232
+ shimmerStyle,
233
+ { backgroundColor: isDark ? "#334155" : "#e2e8f0" },
234
+ ]}
235
+ />
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Error icon component
241
+ */
242
+ function ErrorIcon({ isDark }: { isDark: boolean }) {
243
+ return (
244
+ <View
245
+ style={{
246
+ width: 24,
247
+ height: 24,
248
+ borderRadius: 12,
249
+ backgroundColor: isDark ? "#475569" : "#cbd5e1",
250
+ alignItems: "center",
251
+ justifyContent: "center",
252
+ }}
253
+ >
254
+ <View
255
+ style={{
256
+ width: 12,
257
+ height: 2,
258
+ backgroundColor: isDark ? "#94a3b8" : "#64748b",
259
+ transform: [{ rotate: "45deg" }],
260
+ }}
261
+ />
262
+ </View>
263
+ );
264
+ }
265
+
266
+ const styles = StyleSheet.create({
267
+ image: {
268
+ width: "100%",
269
+ height: "100%",
270
+ position: "absolute",
271
+ top: 0,
272
+ left: 0,
273
+ },
274
+ skeleton: {
275
+ ...StyleSheet.absoluteFillObject,
276
+ },
277
+ errorContainer: {
278
+ ...StyleSheet.absoluteFillObject,
279
+ alignItems: "center",
280
+ justifyContent: "center",
281
+ },
282
+ });
283
+
284
+ /**
285
+ * Background Image component
286
+ */
287
+ interface BackgroundImageProps extends OptimizedImageProps {
288
+ children?: React.ReactNode;
289
+ overlayColor?: string;
290
+ overlayOpacity?: number;
291
+ }
292
+
293
+ export function BackgroundImage({
294
+ children,
295
+ overlayColor = "#000000",
296
+ overlayOpacity = 0.4,
297
+ ...props
298
+ }: BackgroundImageProps) {
299
+ return (
300
+ <View style={styles.backgroundContainer}>
301
+ <OptimizedImage {...props} style={StyleSheet.absoluteFill} />
302
+ {overlayOpacity > 0 && (
303
+ <View
304
+ style={[
305
+ StyleSheet.absoluteFill,
306
+ {
307
+ backgroundColor: overlayColor,
308
+ opacity: overlayOpacity,
309
+ },
310
+ ]}
311
+ />
312
+ )}
313
+ <View style={styles.backgroundContent}>{children}</View>
314
+ </View>
315
+ );
316
+ }
317
+
318
+ /**
319
+ * Image with progressive loading (blur to sharp)
320
+ */
321
+ interface ProgressiveImageProps extends OptimizedImageProps {
322
+ /**
323
+ * Low-quality placeholder image
324
+ */
325
+ thumbnail?: string;
326
+ }
327
+
328
+ export function ProgressiveImage({
329
+ thumbnail,
330
+ source,
331
+ ...props
332
+ }: ProgressiveImageProps) {
333
+ const [isFullLoaded, setIsFullLoaded] = useState(false);
334
+
335
+ return (
336
+ <View style={{ position: "relative", overflow: "hidden" }}>
337
+ {/* Thumbnail (blurred) */}
338
+ {thumbnail && !isFullLoaded && (
339
+ <Image
340
+ source={{ uri: thumbnail }}
341
+ style={[StyleSheet.absoluteFill, { opacity: 0.5 }]}
342
+ contentFit="cover"
343
+ blurRadius={10}
344
+ />
345
+ )}
346
+
347
+ {/* Full resolution image */}
348
+ <OptimizedImage
349
+ {...props}
350
+ source={source}
351
+ onLoad={() => {
352
+ setIsFullLoaded(true);
353
+ props.onLoad?.();
354
+ }}
355
+ showSkeleton={!thumbnail}
356
+ />
357
+ </View>
358
+ );
359
+ }
360
+
361
+ Object.assign(styles, {
362
+ backgroundContainer: {
363
+ flex: 1,
364
+ },
365
+ backgroundContent: {
366
+ flex: 1,
367
+ zIndex: 1,
368
+ },
369
+ });