@rubixscript/react-native-onboarding 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +383 -383
- package/dist/components/NavigationButtons.d.ts +23 -0
- package/dist/components/NavigationButtons.d.ts.map +1 -0
- package/dist/components/NavigationButtons.js +106 -0
- package/dist/components/Onboarding.d.ts +11 -0
- package/dist/components/Onboarding.d.ts.map +1 -0
- package/dist/components/Onboarding.js +219 -0
- package/dist/components/Pagination.d.ts +5 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +269 -0
- package/dist/components/SimpleOnboardingScreen.d.ts +54 -0
- package/dist/components/SimpleOnboardingScreen.d.ts.map +1 -0
- package/dist/components/SimpleOnboardingScreen.js +184 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +5 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/presets/index.d.ts +27 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +370 -0
- package/dist/slides/FormSlide.d.ts +12 -0
- package/dist/slides/FormSlide.d.ts.map +1 -0
- package/dist/slides/FormSlide.js +227 -0
- package/dist/slides/IconSlide.d.ts +10 -0
- package/dist/slides/IconSlide.d.ts.map +1 -0
- package/dist/slides/IconSlide.js +133 -0
- package/dist/slides/ImageSlide.d.ts +10 -0
- package/dist/slides/ImageSlide.d.ts.map +1 -0
- package/dist/slides/ImageSlide.js +99 -0
- package/dist/slides/VideoSlide.d.ts +10 -0
- package/dist/slides/VideoSlide.d.ts.map +1 -0
- package/dist/slides/VideoSlide.js +101 -0
- package/dist/slides/index.d.ts +14 -0
- package/dist/slides/index.d.ts.map +1 -0
- package/dist/slides/index.js +25 -0
- package/dist/themes/index.d.ts +35 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +547 -0
- package/dist/types/index.d.ts +191 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +73 -60
- package/src/components/NavigationButtons.tsx +198 -198
- package/src/components/Onboarding.tsx +337 -340
- package/src/components/Pagination.tsx +337 -337
- package/src/components/SimpleOnboardingScreen.tsx +266 -0
- package/src/components/index.ts +7 -5
- package/src/index.ts +69 -65
- package/src/presets/index.ts +391 -394
- package/src/slides/FormSlide.tsx +314 -314
- package/src/slides/IconSlide.tsx +166 -166
- package/src/slides/ImageSlide.tsx +132 -132
- package/src/slides/VideoSlide.tsx +146 -146
- package/src/slides/{index.ts → index.tsx} +37 -44
- package/src/themes/index.ts +576 -574
- package/src/types/index.ts +247 -247
|
@@ -1,340 +1,337 @@
|
|
|
1
|
-
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
SafeAreaView,
|
|
6
|
-
Platform,
|
|
7
|
-
Modal,
|
|
8
|
-
ViewStyle,
|
|
9
|
-
Animated,
|
|
10
|
-
} from 'react-native';
|
|
11
|
-
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
setHasCompletedOnboarding(
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
export default Onboarding;
|
|
1
|
+
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
SafeAreaView,
|
|
6
|
+
Platform,
|
|
7
|
+
Modal,
|
|
8
|
+
ViewStyle,
|
|
9
|
+
Animated,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
12
|
+
import FlatList from 'react-native-reanimated';
|
|
13
|
+
import { BlurView } from 'expo-blur';
|
|
14
|
+
import { LinearGradient } from 'expo-linear-gradient';
|
|
15
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
16
|
+
|
|
17
|
+
import { OnboardingProps, OnboardingConfig } from '../types';
|
|
18
|
+
import { mergeTheme, getPreset } from '../themes';
|
|
19
|
+
import { SlideRenderer } from '../slides';
|
|
20
|
+
import { Pagination, NavigationButtons } from './index';
|
|
21
|
+
|
|
22
|
+
// Type assertion for AnimatedFlatList from react-native-reanimated
|
|
23
|
+
const AnimatedFlatListImplemented = FlatList as any;
|
|
24
|
+
|
|
25
|
+
export const Onboarding: React.FC<OnboardingProps> = ({
|
|
26
|
+
visible,
|
|
27
|
+
slides = [],
|
|
28
|
+
theme: customTheme,
|
|
29
|
+
navigation: customNavigation,
|
|
30
|
+
animation: customAnimation,
|
|
31
|
+
storage,
|
|
32
|
+
onboardingComplete,
|
|
33
|
+
onSlideChange,
|
|
34
|
+
onSkip,
|
|
35
|
+
initialSlide = 0,
|
|
36
|
+
swipeEnabled = true,
|
|
37
|
+
containerStyle,
|
|
38
|
+
safeAreaEnabled = true,
|
|
39
|
+
darkMode = false,
|
|
40
|
+
}) => {
|
|
41
|
+
// State
|
|
42
|
+
const [currentIndex, setCurrentIndex] = useState(initialSlide);
|
|
43
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
44
|
+
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
45
|
+
|
|
46
|
+
//Refs
|
|
47
|
+
const flatListRef = React.useRef<any>(null);
|
|
48
|
+
|
|
49
|
+
// Merge theme with preset
|
|
50
|
+
const preset = useMemo(() => {
|
|
51
|
+
if (!customTheme) return getPreset('modern');
|
|
52
|
+
const presetKey = Object.keys(getPreset('modern')).find(
|
|
53
|
+
key => (customTheme as any)[key]
|
|
54
|
+
);
|
|
55
|
+
return presetKey ? getPreset(presetKey) : getPreset('modern');
|
|
56
|
+
}, [customTheme]);
|
|
57
|
+
|
|
58
|
+
const theme = useMemo(
|
|
59
|
+
() => mergeTheme(preset.theme, customTheme),
|
|
60
|
+
[preset, customTheme]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const navigationConfig = useMemo(
|
|
64
|
+
() => ({ ...preset.navigation, ...customNavigation }),
|
|
65
|
+
[preset, customNavigation]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const animationConfig = useMemo(
|
|
69
|
+
() => ({ ...preset.animation, ...customAnimation }),
|
|
70
|
+
[preset, customAnimation]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Check storage on mount
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (storage?.enabled) {
|
|
76
|
+
checkOnboardingStatus();
|
|
77
|
+
}
|
|
78
|
+
}, [storage]);
|
|
79
|
+
|
|
80
|
+
const checkOnboardingStatus = async () => {
|
|
81
|
+
try {
|
|
82
|
+
const key = storage?.key || '@onboarding_complete';
|
|
83
|
+
const completed = await AsyncStorage.getItem(key);
|
|
84
|
+
if (completed && !visible) {
|
|
85
|
+
// Already completed, don't show
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn('Error checking onboarding status:', error);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const saveOnboardingComplete = async () => {
|
|
94
|
+
if (storage?.enabled) {
|
|
95
|
+
try {
|
|
96
|
+
const key = storage?.key || '@onboarding_complete';
|
|
97
|
+
await AsyncStorage.setItem(key, 'true');
|
|
98
|
+
if (storage.onComplete) {
|
|
99
|
+
await storage.onComplete();
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.warn('Error saving onboarding status:', error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Handlers
|
|
108
|
+
const handleNext = useCallback(async () => {
|
|
109
|
+
const currentSlide = slides[currentIndex];
|
|
110
|
+
|
|
111
|
+
// Check if form slide and validate
|
|
112
|
+
if (currentSlide.type === 'form') {
|
|
113
|
+
const formSlide = currentSlide as any;
|
|
114
|
+
const requiredFields = formSlide.fields.filter((f: any) => f.required);
|
|
115
|
+
const isValid = requiredFields.every((field: any) => formData[field.key]);
|
|
116
|
+
|
|
117
|
+
if (!isValid) {
|
|
118
|
+
return; // Form validation failed
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Submit form data
|
|
122
|
+
if (formSlide.onSubmit) {
|
|
123
|
+
setIsSubmitting(true);
|
|
124
|
+
try {
|
|
125
|
+
await formSlide.onSubmit(formData);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.warn('Form submit error:', error);
|
|
128
|
+
}
|
|
129
|
+
setIsSubmitting(false);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (currentIndex < slides.length - 1) {
|
|
134
|
+
flatListRef.current?.scrollToIndex({ index: currentIndex + 1, animated: true });
|
|
135
|
+
} else {
|
|
136
|
+
// Complete onboarding
|
|
137
|
+
await saveOnboardingComplete();
|
|
138
|
+
if (onboardingComplete) {
|
|
139
|
+
await onboardingComplete(formData);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}, [currentIndex, slides, formData, onboardingComplete, storage]);
|
|
143
|
+
|
|
144
|
+
const handleBack = useCallback(() => {
|
|
145
|
+
if (currentIndex > 0) {
|
|
146
|
+
flatListRef.current?.scrollToIndex({ index: currentIndex - 1, animated: true });
|
|
147
|
+
}
|
|
148
|
+
}, [currentIndex]);
|
|
149
|
+
|
|
150
|
+
const handleSkip = useCallback(async () => {
|
|
151
|
+
await saveOnboardingComplete();
|
|
152
|
+
if (onSkip) {
|
|
153
|
+
await onSkip();
|
|
154
|
+
} else if (onboardingComplete) {
|
|
155
|
+
await onboardingComplete();
|
|
156
|
+
}
|
|
157
|
+
}, [onSkip, onboardingComplete, storage]);
|
|
158
|
+
|
|
159
|
+
const handleMomentumScrollEnd = useCallback(
|
|
160
|
+
(event: any) => {
|
|
161
|
+
const index = Math.round(event.nativeEvent.contentOffset.x / event.nativeEvent.layoutMeasurement.width);
|
|
162
|
+
if (index !== currentIndex) {
|
|
163
|
+
setCurrentIndex(index);
|
|
164
|
+
if (onSlideChange) {
|
|
165
|
+
onSlideChange(index);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
[currentIndex, onSlideChange]
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const handleFormDataChange = useCallback((data: Record<string, any>) => {
|
|
173
|
+
setFormData(prev => ({ ...prev, ...data }));
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
// Renderers
|
|
177
|
+
const renderSlide = useCallback(
|
|
178
|
+
({ item, index }: { item: any; index: number }) => {
|
|
179
|
+
return (
|
|
180
|
+
<View style={[styles.slide, { width: '100%' }]}>
|
|
181
|
+
<SlideRenderer
|
|
182
|
+
data={item}
|
|
183
|
+
theme={theme}
|
|
184
|
+
darkMode={darkMode}
|
|
185
|
+
onSubmit={handleFormDataChange}
|
|
186
|
+
isSubmitting={isSubmitting}
|
|
187
|
+
/>
|
|
188
|
+
</View>
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
[theme, darkMode, isSubmitting, handleFormDataChange]
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const getKey = useCallback((item: any, index: number) => item.id || `slide-${index}`, []);
|
|
195
|
+
|
|
196
|
+
// Computed values
|
|
197
|
+
const isLastSlide = currentIndex === slides.length - 1;
|
|
198
|
+
const currentSlide = slides[currentIndex];
|
|
199
|
+
|
|
200
|
+
// Determine if we should show navigation (not for form slides that handle their own)
|
|
201
|
+
const showNavigation = currentSlide?.type !== 'form';
|
|
202
|
+
|
|
203
|
+
if (!visible) return null;
|
|
204
|
+
|
|
205
|
+
const content = (
|
|
206
|
+
<View style={[styles.container, containerStyle]}>
|
|
207
|
+
{/* Background Gradient if applicable */}
|
|
208
|
+
{currentSlide?.gradientColors && currentSlide.gradientColors.length > 0 && (
|
|
209
|
+
<LinearGradient
|
|
210
|
+
colors={currentSlide.gradientColors as any}
|
|
211
|
+
style={StyleSheet.absoluteFillObject}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
{/* Pagination */}
|
|
216
|
+
{showNavigation && (
|
|
217
|
+
<Pagination
|
|
218
|
+
currentIndex={currentIndex}
|
|
219
|
+
totalSlides={slides.length}
|
|
220
|
+
theme={theme}
|
|
221
|
+
config={navigationConfig}
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{/* Slides */}
|
|
226
|
+
<AnimatedFlatListImplemented
|
|
227
|
+
ref={flatListRef as any}
|
|
228
|
+
data={slides}
|
|
229
|
+
renderItem={renderSlide}
|
|
230
|
+
keyExtractor={getKey}
|
|
231
|
+
horizontal
|
|
232
|
+
pagingEnabled
|
|
233
|
+
showsHorizontalScrollIndicator={false}
|
|
234
|
+
scrollEventThrottle={32}
|
|
235
|
+
onMomentumScrollEnd={handleMomentumScrollEnd}
|
|
236
|
+
scrollEnabled={swipeEnabled}
|
|
237
|
+
bounces={false}
|
|
238
|
+
initialScrollIndex={initialSlide}
|
|
239
|
+
onScrollToIndexFailed={(info: any) => {
|
|
240
|
+
// Retry if scroll to index fails
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
flatListRef.current?.scrollToIndex({
|
|
243
|
+
index: info.index,
|
|
244
|
+
animated: true,
|
|
245
|
+
});
|
|
246
|
+
}, 100);
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
{/* Navigation Buttons */}
|
|
251
|
+
{showNavigation && (
|
|
252
|
+
<NavigationButtons
|
|
253
|
+
currentIndex={currentIndex}
|
|
254
|
+
totalSlides={slides.length}
|
|
255
|
+
theme={theme}
|
|
256
|
+
config={navigationConfig}
|
|
257
|
+
onNext={handleNext}
|
|
258
|
+
onBack={handleBack}
|
|
259
|
+
onSkip={handleSkip}
|
|
260
|
+
isLastSlide={isLastSlide}
|
|
261
|
+
isLoading={isSubmitting}
|
|
262
|
+
darkMode={darkMode}
|
|
263
|
+
/>
|
|
264
|
+
)}
|
|
265
|
+
</View>
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (safeAreaEnabled) {
|
|
269
|
+
return (
|
|
270
|
+
<SafeAreaView style={styles.safeArea}>
|
|
271
|
+
<SafeAreaProvider>{content}</SafeAreaProvider>
|
|
272
|
+
</SafeAreaView>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return content;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Hook for checking onboarding status
|
|
280
|
+
export const useOnboarding = (storageKey: string = '@onboarding_complete') => {
|
|
281
|
+
const [hasCompletedOnboarding, setHasCompletedOnboarding] = React.useState<boolean | null>(null);
|
|
282
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
283
|
+
|
|
284
|
+
React.useEffect(() => {
|
|
285
|
+
checkStatus();
|
|
286
|
+
}, []);
|
|
287
|
+
|
|
288
|
+
const checkStatus = async () => {
|
|
289
|
+
try {
|
|
290
|
+
const completed = await AsyncStorage.getItem(storageKey);
|
|
291
|
+
setHasCompletedOnboarding(completed === 'true');
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.warn('Error checking onboarding:', error);
|
|
294
|
+
setHasCompletedOnboarding(false);
|
|
295
|
+
} finally {
|
|
296
|
+
setIsLoading(false);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const markComplete = async () => {
|
|
301
|
+
try {
|
|
302
|
+
await AsyncStorage.setItem(storageKey, 'true');
|
|
303
|
+
setHasCompletedOnboarding(true);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.warn('Error marking onboarding complete:', error);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const reset = async () => {
|
|
310
|
+
try {
|
|
311
|
+
await AsyncStorage.removeItem(storageKey);
|
|
312
|
+
setHasCompletedOnboarding(false);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.warn('Error resetting onboarding:', error);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return { hasCompletedOnboarding, isLoading, markComplete, reset };
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const styles = StyleSheet.create({
|
|
322
|
+
safeArea: {
|
|
323
|
+
flex: 1,
|
|
324
|
+
backgroundColor: '#FFFFFF',
|
|
325
|
+
},
|
|
326
|
+
container: {
|
|
327
|
+
flex: 1,
|
|
328
|
+
backgroundColor: '#FFFFFF',
|
|
329
|
+
},
|
|
330
|
+
slide: {
|
|
331
|
+
flex: 1,
|
|
332
|
+
justifyContent: 'center',
|
|
333
|
+
alignItems: 'center',
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
export default Onboarding;
|