Skip to content

Commit df766df

Browse files
authored
Merge pull request #351 from Greenstand/fix/onboarding-screen
2 parents b7adea3 + e37268d commit df766df

File tree

4 files changed

+145
-92
lines changed

4 files changed

+145
-92
lines changed

apps/native/app/_layout.tsx

+7-63
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,14 @@
11
import FontAwesome from "@expo/vector-icons/FontAwesome";
22
import { useFonts } from "expo-font";
3-
import { Stack } from "expo-router";
3+
import { Slot } from "expo-router";
44
import * as SplashScreen from "expo-splash-screen";
5-
import { useEffect } from "react";
5+
import React, { useEffect } from "react";
66
import "react-native-reanimated";
7-
import {
8-
MD3LightTheme as DefaultTheme,
9-
PaperProvider,
10-
} from "react-native-paper";
11-
12-
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
13-
import { StyleSheet } from "react-native";
14-
15-
export { ErrorBoundary } from "expo-router";
16-
17-
const theme = {
18-
...DefaultTheme,
19-
colors: {
20-
...DefaultTheme.colors,
21-
primary: "tomato",
22-
secondary: "yellow",
23-
},
24-
};
257

268
SplashScreen.preventAutoHideAsync();
279

28-
export default function RootLayout() {
29-
const [loaded, error] = useFonts({
10+
export default function AppLayout() {
11+
const [areFontsLoaded, fontLoadError] = useFonts({
3012
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
3113
Roboto: require("../assets/fonts/Roboto-Regular.ttf"),
3214
RobotoBold: require("../assets/fonts/Roboto-Bold.ttf"),
@@ -35,48 +17,10 @@ export default function RootLayout() {
3517
});
3618

3719
useEffect(() => {
38-
if (error) throw error;
39-
}, [error]);
40-
41-
useEffect(() => {
42-
if (loaded) {
20+
if (areFontsLoaded || fontLoadError) {
4321
SplashScreen.hideAsync();
4422
}
45-
}, [loaded]);
23+
}, [areFontsLoaded, fontLoadError]);
4624

47-
if (!loaded) {
48-
return null;
49-
}
50-
51-
return (
52-
<SafeAreaProvider>
53-
<SafeAreaView style={styles.container}>
54-
<RootLayoutNav />
55-
</SafeAreaView>
56-
</SafeAreaProvider>
57-
);
25+
return <Slot />;
5826
}
59-
60-
export const options = {
61-
headerShown: false,
62-
};
63-
64-
function RootLayoutNav() {
65-
return (
66-
<PaperProvider theme={theme}>
67-
<Stack>
68-
<Stack.Screen name="index" options={{ headerShown: false }} />
69-
<Stack.Screen name="(tabs)" options={options} />
70-
<Stack.Screen name="(auth)/login" options={{ headerShown: false }} />
71-
<Stack.Screen name="(auth)/register" options={{ headerShown: false }} />
72-
<Stack.Screen name="accountConfirmation" options={options} />
73-
</Stack>
74-
</PaperProvider>
75-
);
76-
}
77-
78-
const styles = StyleSheet.create({
79-
container: {
80-
flex: 1,
81-
},
82-
});

apps/native/app/index.tsx

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,24 @@
1-
import OnboardingScreen from "@/screens/onboarding/Onboarding.screen";
2-
export default () => <OnboardingScreen />;
1+
import React, { useEffect, useState } from "react";
2+
import { Redirect, useRouter } from "expo-router";
3+
import AsyncStorage from "@react-native-async-storage/async-storage";
4+
5+
export default function InitialRoute() {
6+
const [shouldShowOnboarding, setShouldShowOnboarding] = useState(false);
7+
const router = useRouter();
8+
9+
useEffect(() => {
10+
const verifyAppLaunchStatus = async () => {
11+
const hasCompletedOnboarding = await AsyncStorage.getItem("hasLaunched");
12+
13+
if (hasCompletedOnboarding === null) {
14+
setShouldShowOnboarding(true);
15+
} else {
16+
router.replace("/(auth)/login");
17+
}
18+
};
19+
20+
verifyAppLaunchStatus();
21+
}, [router]);
22+
23+
return shouldShowOnboarding ? <Redirect href="/onboarding" /> : null;
24+
}

apps/native/screens/onboarding/Onboarding.screen.tsx apps/native/app/onboarding/index.tsx

+44-27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { router } from "expo-router";
2-
import React, { useState } from "react";
1+
import { useRouter } from "expo-router";
2+
import React, { useState, useCallback } from "react";
33
import {
44
View,
55
Text,
@@ -15,6 +15,8 @@ import Leafs from "@/assets/svg/leafs.svg";
1515
import Wallet from "@/assets/svg/wallet.svg";
1616
import Cloud from "@/assets/svg/cloud.svg";
1717
import { SvgProps } from "react-native-svg";
18+
import CustomButton from "@/components/ui/common/CustomButton";
19+
import AsyncStorage from "@react-native-async-storage/async-storage";
1820

1921
const { width, height } = Dimensions.get("window");
2022

@@ -49,9 +51,25 @@ const DATA: OnboardingItem[] = [
4951
const OnboardingScreen = () => {
5052
const [currentIndex, setCurrentIndex] = useState(0);
5153

52-
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
53-
const slideIndex = Math.round(event.nativeEvent.contentOffset.x / width);
54-
setCurrentIndex(slideIndex);
54+
const router = useRouter();
55+
let flashListRef = React.useRef<FlashList<OnboardingItem>>(null);
56+
57+
const handleScroll = useCallback(
58+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
59+
const slideIndex = Math.round(event.nativeEvent.contentOffset.x / width);
60+
setCurrentIndex(slideIndex);
61+
},
62+
[],
63+
);
64+
65+
const handleSignUp = async () => {
66+
await AsyncStorage.setItem("hasLaunched", "true");
67+
router.push("/(auth)/register");
68+
};
69+
70+
const handleLogIn = async () => {
71+
await AsyncStorage.setItem("hasLaunched", "true");
72+
router.push("/(auth)/login");
5573
};
5674

5775
const renderItem = ({ item }: { item: OnboardingItem }) => {
@@ -68,18 +86,24 @@ const OnboardingScreen = () => {
6886
return (
6987
<SafeAreaView style={styles.safeArea}>
7088
<FlashList
89+
ref={flashListRef}
7190
data={DATA}
7291
renderItem={renderItem}
7392
horizontal
7493
pagingEnabled
75-
onScroll={handleScroll}
94+
onMomentumScrollEnd={handleScroll}
7695
showsHorizontalScrollIndicator={false}
7796
estimatedItemSize={height}
97+
keyExtractor={item => item.id}
7898
/>
99+
79100
<View style={styles.pagination}>
80101
{DATA.map((_, index) => (
81-
<View
102+
<TouchableOpacity
82103
key={index}
104+
onPress={() =>
105+
flashListRef.current?.scrollToIndex({ index, animated: true })
106+
}
83107
style={[
84108
styles.circleWrapper,
85109
currentIndex === index ? styles.activeCircle : null,
@@ -90,24 +114,18 @@ const OnboardingScreen = () => {
90114
currentIndex === index ? styles.activeDot : styles.inactiveDot,
91115
]}
92116
/>
93-
</View>
117+
</TouchableOpacity>
94118
))}
95119
</View>
120+
96121
<View style={styles.buttonContainer}>
97-
{currentIndex === DATA.length - 1 ? (
98-
<TouchableOpacity
99-
onPress={() => router.push("/(auth)/login")}
100-
style={styles.button}>
101-
<Text style={styles.buttonText}>GET STARTED</Text>
102-
</TouchableOpacity>
103-
) : (
104-
<TouchableOpacity onPress={() => {}} style={styles.button}>
105-
<Text style={styles.buttonText}>CONTINUE</Text>
106-
</TouchableOpacity>
107-
)}
108-
<TouchableOpacity>
109-
<Text style={styles.skipText}>SKIP THE TOUR</Text>
110-
</TouchableOpacity>
122+
<CustomButton title="SIGN UP" onPress={handleSignUp} />
123+
124+
<CustomButton
125+
title="LOG IN"
126+
variant="secondary"
127+
onPress={handleLogIn}
128+
/>
111129
</View>
112130
</SafeAreaView>
113131
);
@@ -150,11 +168,9 @@ const styles = StyleSheet.create({
150168
justifyContent: "center",
151169
width: 20,
152170
height: 20,
153-
marginHorizontal: 5,
171+
marginHorizontal: 1,
154172
},
155173
activeCircle: {
156-
borderWidth: 2,
157-
borderColor: "#4CAF50",
158174
borderRadius: 10,
159175
},
160176
dot: {
@@ -163,16 +179,17 @@ const styles = StyleSheet.create({
163179
borderRadius: 6,
164180
},
165181
activeDot: {
166-
backgroundColor: "#4CAF50",
182+
backgroundColor: "#FF7A00",
167183
},
168184
inactiveDot: {
169-
backgroundColor: "transparent",
185+
backgroundColor: "#BDBDBD",
170186
borderWidth: 2,
171187
borderColor: "#ccc",
172188
},
173189
buttonContainer: {
174190
alignItems: "center",
175191
paddingHorizontal: 20,
192+
gap: 8,
176193
marginBottom: 20,
177194
},
178195
button: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from "react";
2+
import {
3+
TouchableOpacity,
4+
Text,
5+
StyleSheet,
6+
ViewStyle,
7+
TextStyle,
8+
} from "react-native";
9+
10+
type ButtonProps = {
11+
title: string;
12+
onPress: () => void;
13+
variant?: "primary" | "secondary";
14+
style?: ViewStyle;
15+
textStyle?: TextStyle;
16+
};
17+
18+
const CustomButton: React.FC<ButtonProps> = ({
19+
title,
20+
onPress,
21+
variant = "primary",
22+
style,
23+
textStyle,
24+
}) => {
25+
const isSecondary = variant === "secondary";
26+
27+
return (
28+
<TouchableOpacity
29+
style={StyleSheet.flatten([
30+
styles.button,
31+
isSecondary && styles.secondaryButton,
32+
style,
33+
])}
34+
onPress={onPress}>
35+
<Text
36+
style={StyleSheet.flatten([
37+
styles.buttonText,
38+
isSecondary && styles.secondaryText,
39+
textStyle,
40+
])}>
41+
{title}
42+
</Text>
43+
</TouchableOpacity>
44+
);
45+
};
46+
47+
const styles = StyleSheet.create({
48+
button: {
49+
backgroundColor: "#61892F",
50+
paddingVertical: 12,
51+
paddingHorizontal: 20,
52+
borderRadius: 8,
53+
alignItems: "center",
54+
justifyContent: "center",
55+
width: "100%",
56+
},
57+
secondaryButton: {
58+
backgroundColor: "transparent",
59+
},
60+
buttonText: {
61+
color: "#fff",
62+
fontSize: 16,
63+
fontWeight: "bold",
64+
},
65+
secondaryText: {
66+
color: "#6B6E70",
67+
},
68+
});
69+
70+
export default CustomButton;

0 commit comments

Comments
 (0)