@umituz/react-native-onboarding 2.6.9 → 2.6.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-onboarding",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.10",
|
|
4
4
|
"description": "Advanced onboarding flow for React Native apps with personalization questions, theme-aware colors, animations, and customizable slides. SOLID, DRY, KISS principles applied.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useOnboardingScreenState Hook
|
|
3
|
+
* Single Responsibility: Manage onboarding screen state and handlers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
|
|
10
|
+
import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore";
|
|
11
|
+
import { useOnboardingNavigation } from "../../infrastructure/hooks/useOnboardingNavigation";
|
|
12
|
+
import { useOnboardingAnswers } from "../../infrastructure/hooks/useOnboardingAnswers";
|
|
13
|
+
import { OnboardingSlideService } from "../../infrastructure/services/OnboardingSlideService";
|
|
14
|
+
import { OnboardingValidationService } from "../../infrastructure/services/OnboardingValidationService";
|
|
15
|
+
import { shouldUseGradient } from "../../infrastructure/utils/gradientUtils";
|
|
16
|
+
|
|
17
|
+
export interface UseOnboardingScreenStateProps {
|
|
18
|
+
slides: OnboardingSlide[] | undefined;
|
|
19
|
+
storageKey?: string;
|
|
20
|
+
onComplete?: () => void | Promise<void>;
|
|
21
|
+
onSkip?: () => void | Promise<void>;
|
|
22
|
+
globalUseGradient?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseOnboardingScreenStateReturn {
|
|
26
|
+
filteredSlides: OnboardingSlide[];
|
|
27
|
+
currentSlide: OnboardingSlide | undefined;
|
|
28
|
+
currentIndex: number;
|
|
29
|
+
isFirstSlide: boolean;
|
|
30
|
+
isLastSlide: boolean;
|
|
31
|
+
currentAnswer: any;
|
|
32
|
+
isAnswerValid: boolean;
|
|
33
|
+
useGradient: boolean;
|
|
34
|
+
containerStyle: any;
|
|
35
|
+
handleNext: () => Promise<void>;
|
|
36
|
+
handlePrevious: () => void;
|
|
37
|
+
handleSkip: () => Promise<void>;
|
|
38
|
+
setCurrentAnswer: (value: any) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useOnboardingScreenState({
|
|
42
|
+
slides,
|
|
43
|
+
storageKey,
|
|
44
|
+
onComplete,
|
|
45
|
+
onSkip,
|
|
46
|
+
globalUseGradient = false,
|
|
47
|
+
}: UseOnboardingScreenStateProps): UseOnboardingScreenStateReturn {
|
|
48
|
+
const insets = useSafeAreaInsets();
|
|
49
|
+
const tokens = useAppDesignTokens();
|
|
50
|
+
const onboardingStore = useOnboardingStore();
|
|
51
|
+
|
|
52
|
+
// Filter slides using service
|
|
53
|
+
const filteredSlides = useMemo(() => {
|
|
54
|
+
if (!slides || !Array.isArray(slides) || slides.length === 0) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const userData = onboardingStore.getUserData();
|
|
58
|
+
return OnboardingSlideService.filterSlides(slides, userData);
|
|
59
|
+
}, [slides, onboardingStore]);
|
|
60
|
+
|
|
61
|
+
// Navigation hook
|
|
62
|
+
const {
|
|
63
|
+
currentIndex,
|
|
64
|
+
goToNext,
|
|
65
|
+
goToPrevious,
|
|
66
|
+
complete: completeOnboarding,
|
|
67
|
+
skip: skipOnboarding,
|
|
68
|
+
isLastSlide,
|
|
69
|
+
isFirstSlide,
|
|
70
|
+
} = useOnboardingNavigation(
|
|
71
|
+
filteredSlides.length,
|
|
72
|
+
async () => {
|
|
73
|
+
await onboardingStore.complete(storageKey);
|
|
74
|
+
if (onComplete) {
|
|
75
|
+
await onComplete();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async () => {
|
|
79
|
+
await onboardingStore.skip(storageKey);
|
|
80
|
+
if (onSkip) {
|
|
81
|
+
await onSkip();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Get current slide
|
|
87
|
+
const currentSlide = useMemo(
|
|
88
|
+
() => OnboardingSlideService.getSlideAtIndex(filteredSlides, currentIndex),
|
|
89
|
+
[filteredSlides, currentIndex],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Answer management hook
|
|
93
|
+
const {
|
|
94
|
+
currentAnswer,
|
|
95
|
+
setCurrentAnswer,
|
|
96
|
+
loadAnswerForSlide,
|
|
97
|
+
saveCurrentAnswer,
|
|
98
|
+
} = useOnboardingAnswers(currentSlide);
|
|
99
|
+
|
|
100
|
+
// Handle next slide
|
|
101
|
+
const handleNext = async () => {
|
|
102
|
+
await saveCurrentAnswer(currentSlide);
|
|
103
|
+
if (isLastSlide) {
|
|
104
|
+
await completeOnboarding();
|
|
105
|
+
} else {
|
|
106
|
+
goToNext();
|
|
107
|
+
const nextSlide = OnboardingSlideService.getSlideAtIndex(
|
|
108
|
+
filteredSlides,
|
|
109
|
+
currentIndex + 1,
|
|
110
|
+
);
|
|
111
|
+
loadAnswerForSlide(nextSlide);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Handle previous slide
|
|
116
|
+
const handlePrevious = () => {
|
|
117
|
+
goToPrevious();
|
|
118
|
+
const prevSlide = OnboardingSlideService.getSlideAtIndex(
|
|
119
|
+
filteredSlides,
|
|
120
|
+
currentIndex - 1,
|
|
121
|
+
);
|
|
122
|
+
loadAnswerForSlide(prevSlide);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Handle skip
|
|
126
|
+
const handleSkip = async () => {
|
|
127
|
+
await skipOnboarding();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Check if gradient should be used
|
|
131
|
+
const useGradient = shouldUseGradient(currentSlide, globalUseGradient);
|
|
132
|
+
|
|
133
|
+
// Validate answer using service
|
|
134
|
+
const isAnswerValid = useMemo(() => {
|
|
135
|
+
if (!currentSlide?.question) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return OnboardingValidationService.validateAnswer(
|
|
139
|
+
currentSlide.question,
|
|
140
|
+
currentAnswer,
|
|
141
|
+
);
|
|
142
|
+
}, [currentSlide, currentAnswer]);
|
|
143
|
+
|
|
144
|
+
// Container style
|
|
145
|
+
const containerStyle = useMemo(
|
|
146
|
+
() => [
|
|
147
|
+
{
|
|
148
|
+
paddingTop: insets.top,
|
|
149
|
+
backgroundColor: useGradient ? "transparent" : tokens.colors.backgroundPrimary,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
[insets.top, useGradient, tokens.colors.backgroundPrimary],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
filteredSlides,
|
|
157
|
+
currentSlide,
|
|
158
|
+
currentIndex,
|
|
159
|
+
isFirstSlide,
|
|
160
|
+
isLastSlide,
|
|
161
|
+
currentAnswer,
|
|
162
|
+
isAnswerValid,
|
|
163
|
+
useGradient,
|
|
164
|
+
containerStyle,
|
|
165
|
+
handleNext,
|
|
166
|
+
handlePrevious,
|
|
167
|
+
handleSkip,
|
|
168
|
+
setCurrentAnswer,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
@@ -7,17 +7,10 @@
|
|
|
7
7
|
* This component only handles UI coordination - no business logic
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React
|
|
10
|
+
import React from "react";
|
|
11
11
|
import { StyleSheet } from "react-native";
|
|
12
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
14
12
|
import type { OnboardingOptions } from "../../domain/entities/OnboardingOptions";
|
|
15
|
-
import {
|
|
16
|
-
import { useOnboardingAnswers } from "../../infrastructure/hooks/useOnboardingAnswers";
|
|
17
|
-
import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore";
|
|
18
|
-
import { OnboardingSlideService } from "../../infrastructure/services/OnboardingSlideService";
|
|
19
|
-
import { OnboardingValidationService } from "../../infrastructure/services/OnboardingValidationService";
|
|
20
|
-
import { shouldUseGradient } from "../../infrastructure/utils/gradientUtils";
|
|
13
|
+
import { useOnboardingScreenState } from "../hooks/useOnboardingScreenState";
|
|
21
14
|
import { OnboardingScreenContent } from "../components/OnboardingScreenContent";
|
|
22
15
|
|
|
23
16
|
export interface OnboardingScreenProps extends OnboardingOptions {
|
|
@@ -99,116 +92,31 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
99
92
|
useGradient: globalUseGradient = false,
|
|
100
93
|
SliderComponent,
|
|
101
94
|
}) => {
|
|
102
|
-
const insets = useSafeAreaInsets();
|
|
103
|
-
const tokens = useAppDesignTokens();
|
|
104
|
-
const onboardingStore = useOnboardingStore();
|
|
105
|
-
|
|
106
|
-
// Filter slides using service
|
|
107
|
-
const filteredSlides = useMemo(() => {
|
|
108
|
-
if (!slides || !Array.isArray(slides) || slides.length === 0) {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
const userData = onboardingStore.getUserData();
|
|
112
|
-
return OnboardingSlideService.filterSlides(slides, userData);
|
|
113
|
-
}, [slides, onboardingStore]);
|
|
114
|
-
|
|
115
|
-
// Navigation hook
|
|
116
95
|
const {
|
|
96
|
+
filteredSlides,
|
|
97
|
+
currentSlide,
|
|
117
98
|
currentIndex,
|
|
118
|
-
goToNext,
|
|
119
|
-
goToPrevious,
|
|
120
|
-
complete: completeOnboarding,
|
|
121
|
-
skip: skipOnboarding,
|
|
122
|
-
isLastSlide,
|
|
123
99
|
isFirstSlide,
|
|
124
|
-
|
|
125
|
-
filteredSlides.length,
|
|
126
|
-
async () => {
|
|
127
|
-
await onboardingStore.complete(storageKey);
|
|
128
|
-
if (onComplete) {
|
|
129
|
-
await onComplete();
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
async () => {
|
|
133
|
-
await onboardingStore.skip(storageKey);
|
|
134
|
-
if (onSkip) {
|
|
135
|
-
await onSkip();
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// Get current slide
|
|
141
|
-
const currentSlide = useMemo(
|
|
142
|
-
() => OnboardingSlideService.getSlideAtIndex(filteredSlides, currentIndex),
|
|
143
|
-
[filteredSlides, currentIndex],
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// Answer management hook
|
|
147
|
-
const {
|
|
100
|
+
isLastSlide,
|
|
148
101
|
currentAnswer,
|
|
102
|
+
isAnswerValid,
|
|
103
|
+
useGradient,
|
|
104
|
+
containerStyle,
|
|
105
|
+
handleNext,
|
|
106
|
+
handlePrevious,
|
|
107
|
+
handleSkip,
|
|
149
108
|
setCurrentAnswer,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (isLastSlide) {
|
|
158
|
-
await completeOnboarding();
|
|
159
|
-
} else {
|
|
160
|
-
goToNext();
|
|
161
|
-
const nextSlide = OnboardingSlideService.getSlideAtIndex(
|
|
162
|
-
filteredSlides,
|
|
163
|
-
currentIndex + 1,
|
|
164
|
-
);
|
|
165
|
-
loadAnswerForSlide(nextSlide);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Handle previous slide
|
|
170
|
-
const handlePrevious = () => {
|
|
171
|
-
goToPrevious();
|
|
172
|
-
const prevSlide = OnboardingSlideService.getSlideAtIndex(
|
|
173
|
-
filteredSlides,
|
|
174
|
-
currentIndex - 1,
|
|
175
|
-
);
|
|
176
|
-
loadAnswerForSlide(prevSlide);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Handle skip
|
|
180
|
-
const handleSkip = async () => {
|
|
181
|
-
await skipOnboarding();
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Check if gradient should be used
|
|
185
|
-
const useGradient = shouldUseGradient(currentSlide, globalUseGradient);
|
|
186
|
-
|
|
187
|
-
// Validate answer using service
|
|
188
|
-
const isAnswerValid = useMemo(() => {
|
|
189
|
-
if (!currentSlide?.question) {
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
return OnboardingValidationService.validateAnswer(
|
|
193
|
-
currentSlide.question,
|
|
194
|
-
currentAnswer,
|
|
195
|
-
);
|
|
196
|
-
}, [currentSlide, currentAnswer]);
|
|
197
|
-
|
|
198
|
-
const containerStyle = useMemo(
|
|
199
|
-
() => [
|
|
200
|
-
styles.container,
|
|
201
|
-
{
|
|
202
|
-
paddingTop: insets.top,
|
|
203
|
-
backgroundColor: useGradient ? "transparent" : tokens.colors.backgroundPrimary,
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
[insets.top, useGradient, tokens.colors.backgroundPrimary],
|
|
207
|
-
);
|
|
109
|
+
} = useOnboardingScreenState({
|
|
110
|
+
slides,
|
|
111
|
+
storageKey,
|
|
112
|
+
onComplete,
|
|
113
|
+
onSkip,
|
|
114
|
+
globalUseGradient,
|
|
115
|
+
});
|
|
208
116
|
|
|
209
117
|
return (
|
|
210
118
|
<OnboardingScreenContent
|
|
211
|
-
containerStyle={containerStyle}
|
|
119
|
+
containerStyle={[styles.container, containerStyle]}
|
|
212
120
|
useGradient={useGradient}
|
|
213
121
|
currentSlide={currentSlide}
|
|
214
122
|
isFirstSlide={isFirstSlide}
|