Skip to content

Commit e6c11b5

Browse files
authored
feat(native): add first-time user onboarding on home screen (#690)
1 parent 400525f commit e6c11b5

File tree

5 files changed

+706
-56
lines changed

5 files changed

+706
-56
lines changed

apps/native/app/(tabs)/_layout.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import { Tabs } from "expo-router";
22
import CustomTabBar from "@/components/CustomTabBar";
3+
import { CopilotProvider } from "react-native-copilot";
34

45
export default function TabLayout() {
56
return (
6-
<Tabs
7-
// @ts-ignore
8-
tabBar={(props) => <CustomTabBar {...props} />}
9-
screenOptions={{
10-
headerShown: false,
11-
}}
12-
>
13-
<Tabs.Screen name="home" />
14-
<Tabs.Screen name="wallet" />
15-
<Tabs.Screen name="notifications" />
16-
<Tabs.Screen name="settings" />
17-
<Tabs.Screen
18-
name="search"
19-
options={{
20-
href: null,
7+
<CopilotProvider>
8+
<Tabs
9+
// @ts-ignore
10+
tabBar={(props) => <CustomTabBar {...props} />}
11+
screenOptions={{
12+
headerShown: false,
2113
}}
22-
/>
23-
</Tabs>
14+
>
15+
<Tabs.Screen name="home" />
16+
<Tabs.Screen name="wallet" />
17+
<Tabs.Screen name="notifications" />
18+
<Tabs.Screen name="settings" />
19+
<Tabs.Screen
20+
name="search"
21+
options={{
22+
href: null,
23+
}}
24+
/>
25+
</Tabs>
26+
</CopilotProvider>
2427
);
2528
}

apps/native/app/(tabs)/home/index.tsx

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useEffect } from "react";
1+
import React, { useEffect, useRef } from "react";
22
import { useAtom } from "jotai";
33
import { StyleSheet, Text, View, TouchableOpacity } from "react-native";
44
import { FlashList } from "@shopify/flash-list";
55
import { useRouter } from "expo-router";
66
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
7+
import { CopilotStep, walkthroughable, useCopilot } from "react-native-copilot";
78
import Heading from "@common/Heading";
89
import WalletSummary from "@components/ui/WalletSummary";
910
import { THEME, TypographyWeight } from "@/theme";
@@ -23,10 +24,14 @@ export const balanceData = [
2324
{ id: 2, value: "2", label: "Wallets", icon: "wallet-outline" },
2425
];
2526

27+
const WalkthroughableView = walkthroughable(View);
28+
2629
export default function Home() {
2730
const [isSearching] = useAtom(isSearchingAtom);
2831
const [isLoading] = useAtom(searchLoadingAtom);
2932
const [, selectCategory] = useAtom(selectCategoryAtom);
33+
const { start } = useCopilot();
34+
const hasStartedRef = useRef(false);
3035

3136
const router = useRouter();
3237

@@ -41,6 +46,19 @@ export default function Home() {
4146
}
4247
}, [selectCategory, isSearching]);
4348

49+
useEffect(() => {
50+
if (isSearching || isLoading || hasStartedRef.current) {
51+
return;
52+
}
53+
54+
const timeout = setTimeout(() => {
55+
hasStartedRef.current = true;
56+
start();
57+
}, 300);
58+
59+
return () => clearTimeout(timeout);
60+
}, [isLoading, isSearching, start]);
61+
4462
const ActivityHeader = () => (
4563
<View
4664
style={{
@@ -50,25 +68,10 @@ export default function Home() {
5068
>
5169
<View style={[styles.balancesContainer, { gap: spacing.md }]}>
5270
{balanceData.map((item) => (
53-
<WalletSummary
71+
<BalanceCard
5472
key={item.id}
55-
value={item.value}
56-
icon={
57-
item.label === "Tokens" ? (
58-
<MaterialIcons name="toll" size={24} color={colors.primary} />
59-
) : (
60-
<MaterialCommunityIcons
61-
name="wallet-outline"
62-
size={24}
63-
color={colors.gray700}
64-
/>
65-
)
66-
}
67-
label={item.label}
68-
style={[
69-
styles.balanceCard,
70-
{ width: Math.max(layout.screenPadding * 8, 140) },
71-
]}
73+
item={item}
74+
cardWidth={Math.max(layout.screenPadding * 8, 140)}
7275
/>
7376
))}
7477
</View>
@@ -150,6 +153,65 @@ export default function Home() {
150153
);
151154
}
152155

156+
function BalanceCard({
157+
item,
158+
cardWidth,
159+
}: {
160+
item: (typeof balanceData)[number];
161+
cardWidth: number;
162+
}) {
163+
const { colors } = THEME;
164+
165+
const content = (
166+
<WalletSummary
167+
value={item.value}
168+
icon={
169+
item.label === "Tokens" ? (
170+
<MaterialIcons name="toll" size={24} color={colors.primary} />
171+
) : (
172+
<MaterialCommunityIcons
173+
name="wallet-outline"
174+
size={24}
175+
color={colors.gray700}
176+
/>
177+
)
178+
}
179+
label={item.label}
180+
style={[styles.balanceCard, { width: cardWidth }]}
181+
/>
182+
);
183+
184+
if (item.label === "Tokens") {
185+
return (
186+
<CopilotStep
187+
text={
188+
"Here you can view the total amount of tokens (a digital asset representing your contributions)."
189+
}
190+
order={1}
191+
name="homeTokens"
192+
>
193+
<WalkthroughableView>{content}</WalkthroughableView>
194+
</CopilotStep>
195+
);
196+
}
197+
198+
if (item.label === "Wallets") {
199+
return (
200+
<CopilotStep
201+
text={
202+
"Here you can see the total amount of wallets (restricted to max 2 at this time)."
203+
}
204+
order={2}
205+
name="homeWallets"
206+
>
207+
<WalkthroughableView>{content}</WalkthroughableView>
208+
</CopilotStep>
209+
);
210+
}
211+
212+
return content;
213+
}
214+
153215
const styles = StyleSheet.create({
154216
container: {
155217
flex: 1,

apps/native/components/CustomTabBar.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { View, Text, Pressable, StyleSheet, Platform } from "react-native";
33
import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
44
import { MaterialIcons } from "@expo/vector-icons";
55
import { ModalContext } from "@/context/ModalContext";
6+
import { CopilotStep, walkthroughable } from "react-native-copilot";
7+
8+
const WalkthroughableView = walkthroughable(View);
69

710
export default function CustomTabBar({
811
state,
@@ -23,19 +26,25 @@ export default function CustomTabBar({
2326
if (index === midpoint) {
2427
return (
2528
<React.Fragment key="chooseAction">
26-
<Pressable
27-
onPress={handleChooseActionPress}
28-
style={styles.sendButtonWrapper}
29-
android_ripple={{
30-
color: "#dcdcdc",
31-
radius: 40,
32-
borderless: true,
33-
}}
29+
<CopilotStep
30+
text={"Tap to send or request tokens."}
31+
order={3}
32+
name="homeSendAction"
3433
>
35-
<View style={styles.sendButton}>
36-
<MaterialIcons name="swap-horiz" size={26} color="#fff" />
37-
</View>
38-
</Pressable>
34+
<WalkthroughableView style={styles.sendButtonWrapper}>
35+
<Pressable
36+
onPress={handleChooseActionPress}
37+
style={styles.sendButton}
38+
android_ripple={{
39+
color: "#dcdcdc",
40+
radius: 40,
41+
borderless: true,
42+
}}
43+
>
44+
<MaterialIcons name="swap-horiz" size={26} color="#fff" />
45+
</Pressable>
46+
</WalkthroughableView>
47+
</CopilotStep>
3948
{renderTab(route, index)}
4049
</React.Fragment>
4150
);

apps/native/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"react-compiler-runtime": "^19.1.0-rc.2",
6565
"react-dom": "19.1.0",
6666
"react-native": "0.81.5",
67+
"react-native-copilot": "^3.3.3",
6768
"react-native-gesture-handler": "~2.28.0",
6869
"react-native-paper": "^5.12.3",
6970
"react-native-reanimated": "~4.1.1",

0 commit comments

Comments
 (0)