import { View, Text, StyleSheet, FlatList, RefreshControl, TouchableOpacity, ActivityIndicator, Platform, useWindowDimensions } from "react-native"; import { useMatchStore, useAuthStore } from "../../src/store"; import { useEffect, useCallback, useState } from "react"; import { useFocusEffect } from "expo-router"; import { Ionicons } from "@expo/vector-icons"; import { useRouter } from "expo-router"; import { LinearGradient } from "expo-linear-gradient"; import Animated, { FadeIn, FadeInDown, FadeInRight, FadeInUp, useSharedValue, useAnimatedScrollHandler, useAnimatedStyle, interpolate, Extrapolation, } from "react-native-reanimated"; import { BlurView } from "expo-blur"; import { GradientBackground, GlassCard, GlassButton, DecorativeSeparator } from "../../src/components/ui"; import { SwipeableMatchItem } from "../../src/components/matches"; import { useAmbientOrbAnimation } from "../../src/hooks"; import { colors, gradients, spacing, typography, radius, shadows } from "../../src/theme"; import { MatchesTutorial } from "../../src/components/tutorials"; import { hasSeenMatchesTutorial, markMatchesTutorialSeen } from "../../src/lib/matchesTutorialSeen"; // Premium color palette const ACCENT = colors.premium.gold; const ACCENT_RGBA = 'rgba(212, 175, 55, '; const ROSE = colors.premium.rose; const ROSE_RGBA = 'rgba(232, 164, 174, '; const MAX_CONTENT_WIDTH = 500; const NAV_BAR_HEIGHT = 44; const STATUS_BAR_HEIGHT = 60; const HEADER_SCROLL_DISTANCE = 100; const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); export default function MatchesScreen() { const { matches, fetchMatches, markAllAsSeen, isLoading, hasMore, isLoadingMore, totalCount, // Archive state and methods archivedMatches, showArchived, toggleShowArchived, archiveMatch, unarchiveMatch, isLoadingArchived, fetchArchivedMatches, } = useMatchStore(); const { user } = useAuthStore(); const router = useRouter(); const { width } = useWindowDimensions(); const isWideScreen = width > MAX_CONTENT_WIDTH; const [showTutorial, setShowTutorial] = useState(false); const scrollY = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { scrollY.value = event.contentOffset.y; }, }); // Animated styles for collapsing header const heroStyle = useAnimatedStyle(() => { const opacity = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE * 0.7], [1, 0], Extrapolation.CLAMP ); const scale = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE], [1, 0.95], Extrapolation.CLAMP ); return { opacity, transform: [{ scale }] }; }); const compactHeaderStyle = useAnimatedStyle(() => { const opacity = interpolate( scrollY.value, [HEADER_SCROLL_DISTANCE * 0.5, HEADER_SCROLL_DISTANCE], [0, 1], Extrapolation.CLAMP ); return { opacity }; }); const navBarBackgroundStyle = useAnimatedStyle(() => { const opacity = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE * 0.8], [0, 1], Extrapolation.CLAMP ); return { opacity }; }); // Ambient orb breathing animations const { orbStyle1, orbStyle2 } = useAmbientOrbAnimation(); // Check if tutorial should be shown when screen is focused or matches change useFocusEffect( useCallback(() => { const checkTutorial = async () => { if (matches.length > 0) { const seen = await hasSeenMatchesTutorial(); if (!seen) { setShowTutorial(true); } } }; checkTutorial(); }, [matches.length]) ); const handleTutorialComplete = async () => { await markMatchesTutorialSeen(); setShowTutorial(false); }; useFocusEffect( useCallback(() => { fetchMatches(true); }, []) ); const handleLoadMore = useCallback(() => { console.log('[Matches] onEndReached called', { isLoading, isLoadingMore, hasMore, matchesCount: matches.length }); if (!isLoading && !isLoadingMore && hasMore) { fetchMatches(false); } }, [isLoading, isLoadingMore, hasMore, matches.length]); useEffect(() => { if (matches.length > 0) { markAllAsSeen(); } }, [matches.length]); const renderItem = ({ item, index }: { item: any; index: number }) => { // Skip rendering if question was deleted if (!item.question) { return null; } const userResponse = item.responses?.find((r: any) => r.user_id === user?.id); const partnerResponse = item.responses?.find((r: any) => r.user_id !== user?.id); let userText = item.question.text; let partnerText = item.question.partner_text; if (item.question.partner_text && userResponse && partnerResponse) { const userTime = new Date(userResponse.created_at).getTime(); const partnerTime = new Date(partnerResponse.created_at).getTime(); if (userTime > partnerTime) { userText = item.question.partner_text; partnerText = item.question.text; } } const isYesYes = item.match_type === "yes_yes"; const handleArchive = async () => { if (showArchived) { await unarchiveMatch(item.id); } else { await archiveMatch(item.id); } }; return ( router.push(`/chat/${item.id}`)} activeOpacity={0.8} > {/* Subtle gradient background */} {/* Top silk highlight */} {/* Premium icon container */} {userText} {item.question.partner_text && ( Partner: {partnerText} )} {isYesYes ? "YES + YES" : "YES + MAYBE"} {new Date(item.created_at).toLocaleDateString()} {item.unreadCount > 0 && ( )} {/* Premium border */} ); }; const renderFooter = () => { if (!isLoadingMore) return null; return ( ); }; if (!user) { return ( ); } return ( {/* Ambient Orbs - Premium gold/rose */} {/* Fixed Nav Bar */} {Platform.OS === "ios" ? ( ) : ( )} Matches {/* My Answers button moved to Nav Bar */} router.push({ pathname: "/(app)/my-answers", params: { returnTo: "/(app)/matches" } })} > item.id} contentContainerStyle={[styles.list, isWideScreen && styles.listWide]} showsVerticalScrollIndicator={false} onScroll={scrollHandler} scrollEventThrottle={16} refreshControl={ fetchMatches(true)} tintColor={colors.primary} colors={[colors.primary]} progressViewOffset={STATUS_BAR_HEIGHT + NAV_BAR_HEIGHT} /> } onEndReached={handleLoadMore} onEndReachedThreshold={2} ListFooterComponent={renderFooter} ListHeaderComponent={ {/* Premium label */} DISCOVER Matches {/* Decorative separator */} {/* Filter toggle */} showArchived && toggleShowArchived()} activeOpacity={0.7} > Active { if (!showArchived) { toggleShowArchived(); } }} activeOpacity={0.7} > {isLoadingArchived ? ( ) : ( <> Archived )} {/* Count badge */} {showArchived ? `${archivedMatches.length} ARCHIVED` : `${totalCount ?? matches.length} ${(totalCount ?? matches.length) === 1 ? 'MATCH' : 'MATCHES'}` } } ListEmptyComponent={ showArchived ? ( {/* Archive icon */} {/* Description */} No Archived Matches Matches you archive will appear here. Swipe left on any match to archive it. toggleShowArchived()} style={{ marginTop: spacing.lg }} > View Active Matches ) : ( {/* Description */} Answer questions together to discover what you both enjoy. Matches appear when you agree! {/* Feature hints */} Swipe right for yes Match when you both agree Chat about your matches {/* Bottom teaser */} Your first match is just a swipe away router.push("/(app)/swipe")} style={{ marginTop: spacing.lg }} > Start Swiping ) } /> {/* Matches Tutorial Overlay */} {showTutorial && ( )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, // Ambient orbs ambientOrb: { position: 'absolute', width: 300, height: 300, borderRadius: 150, }, orbTopRight: { top: 60, right: -40, }, orbBottomLeft: { bottom: 180, left: -40, }, orbGradient: { width: '100%', height: '100%', borderRadius: 150, }, // Fixed Nav Bar navBar: { position: "absolute", top: 0, left: 0, right: 0, height: STATUS_BAR_HEIGHT + NAV_BAR_HEIGHT, flexDirection: "row", alignItems: "center", justifyContent: "center", paddingTop: STATUS_BAR_HEIGHT - 10, paddingHorizontal: spacing.md, zIndex: 100, }, navBarBackground: { ...StyleSheet.absoluteFillObject, borderBottomWidth: 1, borderBottomColor: 'rgba(212, 175, 55, 0.15)', // Gold tint for matches overflow: "hidden", }, navBarTitle: { ...typography.headline, color: colors.text, textAlign: "center", }, navBarButton: { position: 'absolute', right: spacing.md, top: STATUS_BAR_HEIGHT - 5, width: 36, height: 36, borderRadius: 18, backgroundColor: 'rgba(255, 255, 255, 0.08)', justifyContent: 'center', alignItems: 'center', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.1)', }, // Header (Hero) header: { paddingTop: STATUS_BAR_HEIGHT + spacing.md, paddingHorizontal: spacing.lg, paddingBottom: spacing.lg, alignItems: 'center', }, headerWide: { alignSelf: 'center', width: '100%', maxWidth: MAX_CONTENT_WIDTH, }, headerContent: { alignItems: 'center', }, headerLabel: { ...typography.caption2, fontWeight: '600', letterSpacing: 3, color: ACCENT, marginBottom: spacing.xs, }, headerTitle: { ...typography.largeTitle, color: colors.text, textAlign: 'center', }, // Filter toggle filterContainer: { flexDirection: 'row', backgroundColor: 'rgba(255, 255, 255, 0.05)', borderRadius: radius.lg, padding: 4, marginBottom: spacing.md, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.08)', }, filterTab: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.md, gap: spacing.xs, }, filterTabActive: { backgroundColor: `${ACCENT_RGBA}0.15)`, }, filterTabText: { ...typography.caption1, color: colors.textTertiary, fontWeight: '500', }, filterTabTextActive: { color: ACCENT, fontWeight: '600', }, countBadgePremium: { flexDirection: 'row', alignItems: 'center', backgroundColor: `${ACCENT_RGBA}0.1)`, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.full, borderWidth: 1, borderColor: `${ACCENT_RGBA}0.2)`, gap: spacing.xs, }, countTextPremium: { ...typography.caption2, fontWeight: '600', letterSpacing: 2, color: ACCENT, }, // List list: { padding: spacing.lg, // Remove top padding as header handles it paddingTop: 0, paddingBottom: Platform.OS === 'ios' ? 120 : 100, }, listWide: { alignSelf: 'center', width: '100%', maxWidth: MAX_CONTENT_WIDTH, }, // Premium Match Card matchCardPremium: { marginBottom: spacing.sm, borderRadius: radius.lg, overflow: 'hidden', padding: spacing.md, }, cardSilkHighlight: { position: 'absolute', top: 0, left: 0, right: 0, height: 60, }, cardPremiumBorder: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, borderRadius: radius.lg, borderWidth: 1, borderColor: `${ACCENT_RGBA}0.15)`, }, matchRow: { flexDirection: "row", alignItems: "center", }, iconContainerPremium: { width: 44, height: 44, borderRadius: 22, backgroundColor: `${ROSE_RGBA}0.1)`, borderWidth: 1, borderColor: `${ROSE_RGBA}0.2)`, justifyContent: "center", alignItems: "center", marginRight: spacing.md, }, iconContainerYesYes: { backgroundColor: `${ACCENT_RGBA}0.1)`, borderColor: `${ACCENT_RGBA}0.2)`, }, content: { flex: 1, }, questionTextPremium: { ...typography.subhead, color: colors.text, fontWeight: "600", marginBottom: spacing.xs, lineHeight: 20, }, partnerTextPremium: { ...typography.caption1, color: colors.textSecondary, fontStyle: "italic", marginBottom: spacing.sm, }, metaRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", }, tagPremium: { backgroundColor: `${ROSE_RGBA}0.1)`, paddingHorizontal: spacing.sm, paddingVertical: 3, borderRadius: radius.sm, borderWidth: 1, borderColor: `${ROSE_RGBA}0.15)`, }, tagPremiumHighlight: { backgroundColor: `${ACCENT_RGBA}0.1)`, borderColor: `${ACCENT_RGBA}0.2)`, }, tagTextPremium: { ...typography.caption2, color: ROSE, fontWeight: "600", letterSpacing: 1, }, tagTextPremiumHighlight: { color: ACCENT, }, datePremium: { ...typography.caption2, color: colors.textTertiary, fontStyle: 'italic', }, rightSection: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginLeft: spacing.sm, }, unreadBadgePremium: { width: 22, height: 22, borderRadius: 11, backgroundColor: ACCENT, justifyContent: 'center', alignItems: 'center', }, chevronContainerPremium: { width: 28, height: 28, borderRadius: 14, backgroundColor: `${ACCENT_RGBA}0.08)`, borderWidth: 1, borderColor: `${ACCENT_RGBA}0.15)`, justifyContent: 'center', alignItems: 'center', }, // Premium Empty State emptyContent: { width: '100%', alignItems: 'center', paddingHorizontal: spacing.md, paddingVertical: spacing.xl, }, emptyIconContainer: { width: 80, height: 80, borderRadius: 40, backgroundColor: `${ACCENT_RGBA}0.1)`, borderWidth: 1, borderColor: `${ACCENT_RGBA}0.2)`, justifyContent: 'center', alignItems: 'center', marginBottom: spacing.lg, }, emptyTitle: { ...typography.title3, color: colors.text, marginBottom: spacing.sm, }, emptyDescription: { ...typography.body, color: colors.textSecondary, textAlign: 'center', marginBottom: spacing.xl, paddingHorizontal: spacing.md, }, emptyFeatures: { marginBottom: spacing.xl, }, emptyFeatureItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: spacing.sm, }, emptyFeatureText: { ...typography.subhead, color: colors.text, marginLeft: spacing.sm, }, emptyTeaser: { ...typography.footnote, fontStyle: 'italic', color: colors.textTertiary, textAlign: 'center', }, footerLoader: { paddingVertical: spacing.md, alignItems: "center", justifyContent: "center", }, });