@umituz/react-native-onboarding 2.6.8 → 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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Screen Content Component
|
|
3
|
+
* Single Responsibility: Render onboarding screen content (header, slide, footer)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet, StatusBar } from "react-native";
|
|
8
|
+
import { LinearGradient } from "expo-linear-gradient";
|
|
9
|
+
import { useTheme } from "@umituz/react-native-design-system-theme";
|
|
10
|
+
import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
|
|
11
|
+
import { OnboardingHeader } from "./OnboardingHeader";
|
|
12
|
+
import { OnboardingSlide as OnboardingSlideComponent } from "./OnboardingSlide";
|
|
13
|
+
import { QuestionSlide } from "./QuestionSlide";
|
|
14
|
+
import { OnboardingFooter } from "./OnboardingFooter";
|
|
15
|
+
|
|
16
|
+
export interface OnboardingScreenContentProps {
|
|
17
|
+
containerStyle?: any;
|
|
18
|
+
useGradient: boolean;
|
|
19
|
+
currentSlide: OnboardingSlide | undefined;
|
|
20
|
+
isFirstSlide: boolean;
|
|
21
|
+
isLastSlide: boolean;
|
|
22
|
+
currentIndex: number;
|
|
23
|
+
totalSlides: number;
|
|
24
|
+
currentAnswer: any;
|
|
25
|
+
isAnswerValid: boolean;
|
|
26
|
+
showBackButton: boolean;
|
|
27
|
+
showSkipButton: boolean;
|
|
28
|
+
showProgressBar: boolean;
|
|
29
|
+
showDots: boolean;
|
|
30
|
+
showProgressText: boolean;
|
|
31
|
+
skipButtonText?: string;
|
|
32
|
+
nextButtonText?: string;
|
|
33
|
+
getStartedButtonText?: string;
|
|
34
|
+
onBack: () => void;
|
|
35
|
+
onSkip: () => void;
|
|
36
|
+
onNext: () => void;
|
|
37
|
+
onAnswerChange: (value: any) => void;
|
|
38
|
+
renderHeader?: (props: {
|
|
39
|
+
isFirstSlide: boolean;
|
|
40
|
+
onBack: () => void;
|
|
41
|
+
onSkip: () => void;
|
|
42
|
+
}) => React.ReactNode;
|
|
43
|
+
renderFooter?: (props: {
|
|
44
|
+
currentIndex: number;
|
|
45
|
+
totalSlides: number;
|
|
46
|
+
isLastSlide: boolean;
|
|
47
|
+
onNext: () => void;
|
|
48
|
+
onUpgrade?: () => void;
|
|
49
|
+
showPaywallOnComplete?: boolean;
|
|
50
|
+
}) => React.ReactNode;
|
|
51
|
+
renderSlide?: (slide: OnboardingSlide) => React.ReactNode;
|
|
52
|
+
onUpgrade?: () => void;
|
|
53
|
+
showPaywallOnComplete?: boolean;
|
|
54
|
+
SliderComponent?: React.ComponentType<{
|
|
55
|
+
style?: any;
|
|
56
|
+
minimumValue: number;
|
|
57
|
+
maximumValue: number;
|
|
58
|
+
value: number;
|
|
59
|
+
onValueChange: (value: number) => void;
|
|
60
|
+
minimumTrackTintColor?: string;
|
|
61
|
+
maximumTrackTintColor?: string;
|
|
62
|
+
thumbTintColor?: string;
|
|
63
|
+
step?: number;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = ({
|
|
68
|
+
containerStyle,
|
|
69
|
+
useGradient,
|
|
70
|
+
currentSlide,
|
|
71
|
+
isFirstSlide,
|
|
72
|
+
isLastSlide,
|
|
73
|
+
currentIndex,
|
|
74
|
+
totalSlides,
|
|
75
|
+
currentAnswer,
|
|
76
|
+
isAnswerValid,
|
|
77
|
+
showBackButton,
|
|
78
|
+
showSkipButton,
|
|
79
|
+
showProgressBar,
|
|
80
|
+
showDots,
|
|
81
|
+
showProgressText,
|
|
82
|
+
skipButtonText,
|
|
83
|
+
nextButtonText,
|
|
84
|
+
getStartedButtonText,
|
|
85
|
+
onBack,
|
|
86
|
+
onSkip,
|
|
87
|
+
onNext,
|
|
88
|
+
onAnswerChange,
|
|
89
|
+
renderHeader,
|
|
90
|
+
renderFooter,
|
|
91
|
+
renderSlide,
|
|
92
|
+
onUpgrade,
|
|
93
|
+
showPaywallOnComplete,
|
|
94
|
+
SliderComponent,
|
|
95
|
+
}) => {
|
|
96
|
+
const { themeMode } = useTheme();
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<View style={[styles.container, containerStyle]}>
|
|
100
|
+
<StatusBar barStyle={themeMode === "dark" ? "light-content" : "dark-content"} />
|
|
101
|
+
{useGradient && currentSlide && (
|
|
102
|
+
<LinearGradient
|
|
103
|
+
colors={currentSlide.gradient as [string, string, ...string[]]}
|
|
104
|
+
start={{ x: 0, y: 0 }}
|
|
105
|
+
end={{ x: 1, y: 1 }}
|
|
106
|
+
style={StyleSheet.absoluteFill}
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
{renderHeader ? (
|
|
110
|
+
renderHeader({
|
|
111
|
+
isFirstSlide,
|
|
112
|
+
onBack,
|
|
113
|
+
onSkip,
|
|
114
|
+
})
|
|
115
|
+
) : (
|
|
116
|
+
<OnboardingHeader
|
|
117
|
+
isFirstSlide={isFirstSlide}
|
|
118
|
+
onBack={onBack}
|
|
119
|
+
onSkip={onSkip}
|
|
120
|
+
showBackButton={showBackButton}
|
|
121
|
+
showSkipButton={showSkipButton}
|
|
122
|
+
skipButtonText={skipButtonText}
|
|
123
|
+
useGradient={useGradient}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
{currentSlide &&
|
|
127
|
+
(renderSlide ? (
|
|
128
|
+
renderSlide(currentSlide)
|
|
129
|
+
) : currentSlide.type === "question" && currentSlide.question ? (
|
|
130
|
+
<QuestionSlide
|
|
131
|
+
slide={currentSlide}
|
|
132
|
+
value={currentAnswer}
|
|
133
|
+
onChange={onAnswerChange}
|
|
134
|
+
useGradient={useGradient}
|
|
135
|
+
SliderComponent={SliderComponent}
|
|
136
|
+
/>
|
|
137
|
+
) : (
|
|
138
|
+
<OnboardingSlideComponent slide={currentSlide} useGradient={useGradient} />
|
|
139
|
+
))}
|
|
140
|
+
{renderFooter ? (
|
|
141
|
+
renderFooter({
|
|
142
|
+
currentIndex,
|
|
143
|
+
totalSlides,
|
|
144
|
+
isLastSlide,
|
|
145
|
+
onNext,
|
|
146
|
+
onUpgrade,
|
|
147
|
+
showPaywallOnComplete,
|
|
148
|
+
})
|
|
149
|
+
) : (
|
|
150
|
+
<OnboardingFooter
|
|
151
|
+
currentIndex={currentIndex}
|
|
152
|
+
totalSlides={totalSlides}
|
|
153
|
+
isLastSlide={isLastSlide}
|
|
154
|
+
onNext={onNext}
|
|
155
|
+
showProgressBar={showProgressBar}
|
|
156
|
+
showDots={showDots}
|
|
157
|
+
showProgressText={showProgressText}
|
|
158
|
+
nextButtonText={nextButtonText}
|
|
159
|
+
getStartedButtonText={getStartedButtonText}
|
|
160
|
+
disabled={!isAnswerValid}
|
|
161
|
+
useGradient={useGradient}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
</View>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const styles = StyleSheet.create({
|
|
169
|
+
container: {
|
|
170
|
+
flex: 1,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
@@ -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
|
+
|
|
@@ -3,26 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Main onboarding screen component with theme-aware colors
|
|
5
5
|
* Generic and reusable across hundreds of apps
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* This component only handles UI coordination - no business logic
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React
|
|
11
|
-
import {
|
|
12
|
-
import { LinearGradient } from "expo-linear-gradient";
|
|
13
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
14
|
-
import { useAppDesignTokens, useTheme } from "@umituz/react-native-design-system-theme";
|
|
10
|
+
import React from "react";
|
|
11
|
+
import { StyleSheet } from "react-native";
|
|
15
12
|
import type { OnboardingOptions } from "../../domain/entities/OnboardingOptions";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore";
|
|
19
|
-
import { OnboardingSlideService } from "../../infrastructure/services/OnboardingSlideService";
|
|
20
|
-
import { OnboardingValidationService } from "../../infrastructure/services/OnboardingValidationService";
|
|
21
|
-
import { shouldUseGradient } from "../../infrastructure/utils/gradientUtils";
|
|
22
|
-
import { OnboardingHeader } from "../components/OnboardingHeader";
|
|
23
|
-
import { OnboardingSlide as OnboardingSlideComponent } from "../components/OnboardingSlide";
|
|
24
|
-
import { QuestionSlide } from "../components/QuestionSlide";
|
|
25
|
-
import { OnboardingFooter } from "../components/OnboardingFooter";
|
|
13
|
+
import { useOnboardingScreenState } from "../hooks/useOnboardingScreenState";
|
|
14
|
+
import { OnboardingScreenContent } from "../components/OnboardingScreenContent";
|
|
26
15
|
|
|
27
16
|
export interface OnboardingScreenProps extends OnboardingOptions {
|
|
28
17
|
/**
|
|
@@ -81,11 +70,6 @@ export interface OnboardingScreenProps extends OnboardingOptions {
|
|
|
81
70
|
}>;
|
|
82
71
|
}
|
|
83
72
|
|
|
84
|
-
/**
|
|
85
|
-
* Onboarding Screen Component
|
|
86
|
-
*
|
|
87
|
-
* Displays onboarding flow with theme-aware colors, animations, and navigation
|
|
88
|
-
*/
|
|
89
73
|
export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
90
74
|
slides,
|
|
91
75
|
onComplete,
|
|
@@ -108,192 +92,63 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
108
92
|
useGradient: globalUseGradient = false,
|
|
109
93
|
SliderComponent,
|
|
110
94
|
}) => {
|
|
111
|
-
const insets = useSafeAreaInsets();
|
|
112
|
-
const tokens = useAppDesignTokens();
|
|
113
|
-
const { themeMode } = useTheme();
|
|
114
|
-
const onboardingStore = useOnboardingStore();
|
|
115
|
-
|
|
116
|
-
// Filter slides using service
|
|
117
|
-
const filteredSlides = useMemo(() => {
|
|
118
|
-
// Safety check: if slides is undefined or empty, return empty array
|
|
119
|
-
if (!slides || !Array.isArray(slides) || slides.length === 0) {
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
const userData = onboardingStore.getUserData();
|
|
123
|
-
return OnboardingSlideService.filterSlides(slides, userData);
|
|
124
|
-
}, [slides, onboardingStore]);
|
|
125
|
-
|
|
126
|
-
// Navigation hook
|
|
127
95
|
const {
|
|
96
|
+
filteredSlides,
|
|
97
|
+
currentSlide,
|
|
128
98
|
currentIndex,
|
|
129
|
-
goToNext,
|
|
130
|
-
goToPrevious,
|
|
131
|
-
complete: completeOnboarding,
|
|
132
|
-
skip: skipOnboarding,
|
|
133
|
-
isLastSlide,
|
|
134
99
|
isFirstSlide,
|
|
135
|
-
|
|
136
|
-
filteredSlides.length,
|
|
137
|
-
async () => {
|
|
138
|
-
await onboardingStore.complete(storageKey);
|
|
139
|
-
if (onComplete) {
|
|
140
|
-
await onComplete();
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
async () => {
|
|
144
|
-
await onboardingStore.skip(storageKey);
|
|
145
|
-
if (onSkip) {
|
|
146
|
-
await onSkip();
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
// Get current slide
|
|
152
|
-
const currentSlide = useMemo(
|
|
153
|
-
() => OnboardingSlideService.getSlideAtIndex(filteredSlides, currentIndex),
|
|
154
|
-
[filteredSlides, currentIndex],
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// Answer management hook
|
|
158
|
-
const {
|
|
100
|
+
isLastSlide,
|
|
159
101
|
currentAnswer,
|
|
102
|
+
isAnswerValid,
|
|
103
|
+
useGradient,
|
|
104
|
+
containerStyle,
|
|
105
|
+
handleNext,
|
|
106
|
+
handlePrevious,
|
|
107
|
+
handleSkip,
|
|
160
108
|
setCurrentAnswer,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (isLastSlide) {
|
|
170
|
-
await completeOnboarding();
|
|
171
|
-
} else {
|
|
172
|
-
goToNext();
|
|
173
|
-
const nextSlide = OnboardingSlideService.getSlideAtIndex(
|
|
174
|
-
filteredSlides,
|
|
175
|
-
currentIndex + 1,
|
|
176
|
-
);
|
|
177
|
-
loadAnswerForSlide(nextSlide);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Handle previous slide
|
|
182
|
-
const handlePrevious = () => {
|
|
183
|
-
goToPrevious();
|
|
184
|
-
const prevSlide = OnboardingSlideService.getSlideAtIndex(
|
|
185
|
-
filteredSlides,
|
|
186
|
-
currentIndex - 1,
|
|
187
|
-
);
|
|
188
|
-
loadAnswerForSlide(prevSlide);
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// Handle skip
|
|
192
|
-
const handleSkip = async () => {
|
|
193
|
-
await skipOnboarding();
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// Check if gradient should be used
|
|
197
|
-
const useGradient = shouldUseGradient(currentSlide, globalUseGradient);
|
|
198
|
-
|
|
199
|
-
// Validate answer using service
|
|
200
|
-
const isAnswerValid = useMemo(() => {
|
|
201
|
-
if (!currentSlide?.question) {
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
return OnboardingValidationService.validateAnswer(
|
|
205
|
-
currentSlide.question,
|
|
206
|
-
currentAnswer,
|
|
207
|
-
);
|
|
208
|
-
}, [currentSlide, currentAnswer]);
|
|
209
|
-
|
|
210
|
-
const styles = useMemo(
|
|
211
|
-
() => getStyles(insets, tokens, useGradient),
|
|
212
|
-
[insets, tokens, useGradient],
|
|
213
|
-
);
|
|
109
|
+
} = useOnboardingScreenState({
|
|
110
|
+
slides,
|
|
111
|
+
storageKey,
|
|
112
|
+
onComplete,
|
|
113
|
+
onSkip,
|
|
114
|
+
globalUseGradient,
|
|
115
|
+
});
|
|
214
116
|
|
|
215
117
|
return (
|
|
216
|
-
<
|
|
217
|
-
|
|
218
|
-
{useGradient
|
|
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
|
-
renderSlide(currentSlide)
|
|
246
|
-
) : currentSlide.type === "question" && currentSlide.question ? (
|
|
247
|
-
<QuestionSlide
|
|
248
|
-
slide={currentSlide}
|
|
249
|
-
value={currentAnswer}
|
|
250
|
-
onChange={setCurrentAnswer}
|
|
251
|
-
useGradient={useGradient}
|
|
252
|
-
SliderComponent={SliderComponent}
|
|
253
|
-
/>
|
|
254
|
-
) : (
|
|
255
|
-
<OnboardingSlideComponent slide={currentSlide} useGradient={useGradient} />
|
|
256
|
-
)
|
|
257
|
-
)}
|
|
258
|
-
{renderFooter ? (
|
|
259
|
-
renderFooter({
|
|
260
|
-
currentIndex,
|
|
261
|
-
totalSlides: filteredSlides.length,
|
|
262
|
-
isLastSlide,
|
|
263
|
-
onNext: handleNext,
|
|
264
|
-
onUpgrade,
|
|
265
|
-
showPaywallOnComplete,
|
|
266
|
-
})
|
|
267
|
-
) : (
|
|
268
|
-
<OnboardingFooter
|
|
269
|
-
currentIndex={currentIndex}
|
|
270
|
-
totalSlides={filteredSlides.length}
|
|
271
|
-
isLastSlide={isLastSlide}
|
|
272
|
-
onNext={handleNext}
|
|
273
|
-
showProgressBar={showProgressBar}
|
|
274
|
-
showDots={showDots}
|
|
275
|
-
showProgressText={showProgressText}
|
|
276
|
-
nextButtonText={nextButtonText}
|
|
277
|
-
getStartedButtonText={getStartedButtonText}
|
|
278
|
-
disabled={!isAnswerValid}
|
|
279
|
-
useGradient={useGradient}
|
|
280
|
-
/>
|
|
281
|
-
)}
|
|
282
|
-
</View>
|
|
118
|
+
<OnboardingScreenContent
|
|
119
|
+
containerStyle={[styles.container, containerStyle]}
|
|
120
|
+
useGradient={useGradient}
|
|
121
|
+
currentSlide={currentSlide}
|
|
122
|
+
isFirstSlide={isFirstSlide}
|
|
123
|
+
isLastSlide={isLastSlide}
|
|
124
|
+
currentIndex={currentIndex}
|
|
125
|
+
totalSlides={filteredSlides.length}
|
|
126
|
+
currentAnswer={currentAnswer}
|
|
127
|
+
isAnswerValid={isAnswerValid}
|
|
128
|
+
showBackButton={showBackButton}
|
|
129
|
+
showSkipButton={showSkipButton}
|
|
130
|
+
showProgressBar={showProgressBar}
|
|
131
|
+
showDots={showDots}
|
|
132
|
+
showProgressText={showProgressText}
|
|
133
|
+
skipButtonText={skipButtonText}
|
|
134
|
+
nextButtonText={nextButtonText}
|
|
135
|
+
getStartedButtonText={getStartedButtonText}
|
|
136
|
+
onBack={handlePrevious}
|
|
137
|
+
onSkip={handleSkip}
|
|
138
|
+
onNext={handleNext}
|
|
139
|
+
onAnswerChange={setCurrentAnswer}
|
|
140
|
+
renderHeader={renderHeader}
|
|
141
|
+
renderFooter={renderFooter}
|
|
142
|
+
renderSlide={renderSlide}
|
|
143
|
+
onUpgrade={onUpgrade}
|
|
144
|
+
showPaywallOnComplete={showPaywallOnComplete}
|
|
145
|
+
SliderComponent={SliderComponent}
|
|
146
|
+
/>
|
|
283
147
|
);
|
|
284
148
|
};
|
|
285
149
|
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
)
|
|
291
|
-
StyleSheet.create({
|
|
292
|
-
container: {
|
|
293
|
-
flex: 1,
|
|
294
|
-
paddingTop: insets.top,
|
|
295
|
-
// Use transparent background when gradient is used, otherwise use theme background
|
|
296
|
-
backgroundColor: useGradient ? 'transparent' : tokens.colors.backgroundPrimary,
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
|
|
150
|
+
const styles = StyleSheet.create({
|
|
151
|
+
container: {
|
|
152
|
+
flex: 1,
|
|
153
|
+
},
|
|
154
|
+
});
|