Improve mobile data retrieval and state management

- Add race protection and packId normalization to swipe screen
- Add loading/error states to matchStore with couple_id filter
- Sync per-match unread counts in realtime
- Use useFocusEffect for data freshness on Home screen
- Wire RefreshControl to actual loading state on Matches

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
jjbrunton committed 2026-01-03 20:10:58 +00:00
1 parent 994dd2a295
commit ae91602921
21 files changed
+2453 -237

No files matched your search

+17 -14
View File
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useEffect, useCallback } from "react";
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Platform, useWindowDimensions } from "react-native";
import Animated, {
FadeInDown,
@@ -13,7 +13,7 @@ import Animated, {
import { LinearGradient } from "expo-linear-gradient";
import { useAuthStore, useMatchStore, usePacksStore } from "../../src/store";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { router, useFocusEffect } from "expo-router";
import { GradientBackground, GlassCard } from "../../src/components/ui";
import { colors, gradients, spacing, radius, typography, shadows } from "../../src/theme";
@@ -23,17 +23,20 @@ const ACCENT_COLOR = colors.primary;
export default function HomeScreen() {
const { user, partner, couple } = useAuthStore();
const { matches, newMatchesCount, fetchMatches } = useMatchStore();
const { packs, fetchPacks } = usePacksStore();
const { enabledPackIds, fetchPacks } = usePacksStore();
const { width } = useWindowDimensions();
useEffect(() => {
fetchMatches();
fetchPacks();
}, []);
// Refresh data when screen gains focus for freshness
useFocusEffect(
useCallback(() => {
fetchMatches();
fetchPacks();
}, [])
);
const recentMatches = matches.slice(0, 3);
const topMatches = matches.slice(0, 3);
const isWideScreen = width > MAX_CONTENT_WIDTH;
const enabledPacksCount = packs.length;
const enabledPacksCount = enabledPackIds.length;
// Subtle pulsating animation for the CTA icon
const pulseScale = useSharedValue(1);
@@ -219,8 +222,8 @@ export default function HomeScreen() {
</TouchableOpacity>
</Animated.View>
{/* Recent Matches */}
{recentMatches.length > 0 && (
{/* Top Matches (sorted by unread first, then recent) */}
{topMatches.length > 0 && (
<Animated.View
entering={FadeInDown.delay(400).duration(500)}
style={styles.section}
@@ -230,7 +233,7 @@ export default function HomeScreen() {
<View style={styles.sectionIcon}>
<Ionicons name="heart" size={14} color={ACCENT_COLOR} />
</View>
<Text style={styles.sectionTitle}>Recent Matches</Text>
<Text style={styles.sectionTitle}>Top Matches</Text>
</View>
<TouchableOpacity
onPress={() => router.push("/(app)/matches")}
@@ -240,7 +243,7 @@ export default function HomeScreen() {
<Ionicons name="chevron-forward" size={14} color={ACCENT_COLOR} />
</TouchableOpacity>
</View>
{recentMatches.map((match, index) => (
{topMatches.map((match, index) => (
<Animated.View
key={match.id}
entering={FadeInRight.delay(450 + index * 100).duration(400)}
@@ -272,7 +275,7 @@ export default function HomeScreen() {
)}
{/* Tips when no matches */}
{recentMatches.length === 0 && (
{topMatches.length === 0 && (
<Animated.View
entering={FadeInDown.delay(400).duration(500)}
style={styles.section}