|
1 | 1 | import { useIsFocused } from "@react-navigation/native"; |
2 | 2 | import { useContext, useEffect, useRef, useState } from "react"; |
3 | | -import { Alert, Animated, FlatList, StyleSheet, View } from "react-native"; |
| 3 | +import { Alert, Animated, FlatList, RefreshControl, StyleSheet, View } from "react-native"; |
4 | 4 | import { |
5 | 5 | Appbar, |
6 | 6 | Avatar, |
7 | 7 | Divider, |
8 | 8 | IconButton, |
9 | 9 | List, |
10 | 10 | Text, |
| 11 | + useTheme, |
11 | 12 | } from "react-native-paper"; |
| 13 | +import * as Haptics from "expo-haptics"; |
12 | 14 | import { getFriendsBalance, getGroups } from "../api/groups"; |
13 | 15 | import { AuthContext } from "../context/AuthContext"; |
14 | 16 | import { formatCurrency } from "../utils/currency"; |
15 | 17 |
|
16 | 18 | const FriendsScreen = () => { |
17 | 19 | const { token, user } = useContext(AuthContext); |
| 20 | + const theme = useTheme(); |
18 | 21 | const [friends, setFriends] = useState([]); |
19 | 22 | const [isLoading, setIsLoading] = useState(true); |
| 23 | + const [isRefreshing, setIsRefreshing] = useState(false); |
20 | 24 | const [showTooltip, setShowTooltip] = useState(true); |
21 | 25 | const isFocused = useIsFocused(); |
22 | 26 |
|
23 | | - useEffect(() => { |
24 | | - const fetchData = async () => { |
25 | | - setIsLoading(true); |
26 | | - try { |
27 | | - // Fetch friends balance + groups concurrently for group icons |
28 | | - const friendsResponse = await getFriendsBalance(); |
29 | | - const friendsData = friendsResponse.data.friendsBalance || []; |
30 | | - const groupsResponse = await getGroups(); |
31 | | - const groups = groupsResponse?.data?.groups || []; |
32 | | - const groupMeta = new Map( |
33 | | - groups.map((g) => [g._id, { name: g.name, imageUrl: g.imageUrl }]) |
34 | | - ); |
| 27 | + const fetchData = async (showLoading = true) => { |
| 28 | + if (showLoading) setIsLoading(true); |
| 29 | + try { |
| 30 | + // Fetch friends balance + groups concurrently for group icons |
| 31 | + const friendsResponse = await getFriendsBalance(); |
| 32 | + const friendsData = friendsResponse.data.friendsBalance || []; |
| 33 | + const groupsResponse = await getGroups(); |
| 34 | + const groups = groupsResponse?.data?.groups || []; |
| 35 | + const groupMeta = new Map( |
| 36 | + groups.map((g) => [g._id, { name: g.name, imageUrl: g.imageUrl }]) |
| 37 | + ); |
35 | 38 |
|
36 | | - const transformedFriends = friendsData.map((friend) => ({ |
37 | | - id: friend.userId, |
38 | | - name: friend.userName, |
39 | | - imageUrl: friend.userImageUrl || null, |
40 | | - netBalance: friend.netBalance, |
41 | | - groups: (friend.breakdown || []).map((group) => ({ |
42 | | - id: group.groupId, |
43 | | - name: group.groupName, |
44 | | - balance: group.balance, |
45 | | - imageUrl: groupMeta.get(group.groupId)?.imageUrl || null, |
46 | | - })), |
47 | | - })); |
| 39 | + const transformedFriends = friendsData.map((friend) => ({ |
| 40 | + id: friend.userId, |
| 41 | + name: friend.userName, |
| 42 | + imageUrl: friend.userImageUrl || null, |
| 43 | + netBalance: friend.netBalance, |
| 44 | + groups: (friend.breakdown || []).map((group) => ({ |
| 45 | + id: group.groupId, |
| 46 | + name: group.groupName, |
| 47 | + balance: group.balance, |
| 48 | + imageUrl: groupMeta.get(group.groupId)?.imageUrl || null, |
| 49 | + })), |
| 50 | + })); |
48 | 51 |
|
49 | | - setFriends(transformedFriends); |
50 | | - } catch (error) { |
51 | | - console.error("Failed to fetch friends balance data:", error); |
52 | | - Alert.alert("Error", "Failed to load friends balance data."); |
53 | | - } finally { |
54 | | - setIsLoading(false); |
55 | | - } |
56 | | - }; |
| 52 | + setFriends(transformedFriends); |
| 53 | + } catch (error) { |
| 54 | + console.error("Failed to fetch friends balance data:", error); |
| 55 | + Alert.alert("Error", "Failed to load friends balance data."); |
| 56 | + } finally { |
| 57 | + if (showLoading) setIsLoading(false); |
| 58 | + } |
| 59 | + }; |
57 | 60 |
|
| 61 | + const onRefresh = async () => { |
| 62 | + setIsRefreshing(true); |
| 63 | + await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); |
| 64 | + await fetchData(false); |
| 65 | + setIsRefreshing(false); |
| 66 | + }; |
| 67 | + |
| 68 | + useEffect(() => { |
58 | 69 | if (token && isFocused) { |
59 | 70 | fetchData(); |
60 | 71 | } |
@@ -235,6 +246,14 @@ const FriendsScreen = () => { |
235 | 246 | ListEmptyComponent={ |
236 | 247 | <Text style={styles.emptyText}>No balances with friends yet.</Text> |
237 | 248 | } |
| 249 | + refreshControl={ |
| 250 | + <RefreshControl |
| 251 | + refreshing={isRefreshing} |
| 252 | + onRefresh={onRefresh} |
| 253 | + colors={[theme.colors.primary]} |
| 254 | + tintColor={theme.colors.primary} |
| 255 | + /> |
| 256 | + } |
238 | 257 | /> |
239 | 258 | </View> |
240 | 259 | ); |
|
0 commit comments