import { View, Text, StyleSheet, RefreshControl, Platform, Alert, Pressable } from "react-native"; import { usePacksStore, useAuthStore, useSubscriptionStore } from "../../../src/store"; import { Ionicons } from "@expo/vector-icons"; import { router } from "expo-router"; import { useMemo, useState, useCallback } from "react"; import Animated, { FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming, useAnimatedScrollHandler, interpolate, Extrapolation, } from "react-native-reanimated"; import { BlurView } from "expo-blur"; import { GradientBackground } from "../../../src/components/ui"; import { Paywall } from "../../../src/components/paywall"; import { PackTeaser } from "../../../src/components/PackTeaser"; import { colors, spacing, radius, typography } from "../../../src/theme"; import type { QuestionPack, Category } from "../../../src/types"; const HEADER_MAX_HEIGHT = 180; const HEADER_MIN_HEIGHT = 100; const HEADER_SCROLL_DISTANCE = HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT; interface PackRowProps { pack: QuestionPack; isEnabled: boolean; isPremiumLocked: boolean; isExpanded: boolean; onToggle: () => void; onPress: () => void; onInfoPress: () => void; } function PackRow({ pack, isEnabled, isPremiumLocked, isExpanded, onToggle, onPress, onInfoPress }: PackRowProps) { const questionCount = pack.questions?.[0]?.count || 0; return ( {pack.icon || "๐Ÿ“ฆ"} {pack.name} {pack.is_premium && ( PRO )} {questionCount} {questionCount === 1 ? "question" : "questions"} {isEnabled && ยท Enabled} {isExpanded && ( {pack.description && ( {pack.description} )} Preview {isPremiumLocked ? ( <> Unlock ) : isEnabled ? ( <> Enabled ) : ( <> Enable )} )} ); } interface CategorySectionProps { category: Category; packs: QuestionPack[]; enabledPackIds: string[]; hasPremiumAccess: boolean; expandedPackId: string | null; onPackPress: (packId: string) => void; onPackToggle: (pack: QuestionPack) => void; onPackInfo: (pack: QuestionPack) => void; } function CategorySection({ category, packs, enabledPackIds, hasPremiumAccess, expandedPackId, onPackPress, onPackToggle, onPackInfo, }: CategorySectionProps) { const [isCollapsed, setIsCollapsed] = useState(false); const enabledCount = packs.filter(p => enabledPackIds.includes(p.id)).length; const rotateAnim = useSharedValue(0); const chevronStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${rotateAnim.value}deg` }], })); const handleToggleCollapse = () => { rotateAnim.value = withTiming(isCollapsed ? 0 : -90, { duration: 200 }); setIsCollapsed(!isCollapsed); }; return ( {category.icon && ( {category.icon} )} {category.name} {enabledCount > 0 && ( {enabledCount} )} {!isCollapsed && ( {packs.map((pack) => ( onPackPress(pack.id)} onToggle={() => onPackToggle(pack)} onInfoPress={() => onPackInfo(pack)} /> ))} )} ); } export default function QuestionPacksScreen() { const { packs, categories, enabledPackIds, togglePack, isLoading, fetchPacks } = usePacksStore(); const { user, partner } = useAuthStore(); const { subscription } = useSubscriptionStore(); const [expandedPackId, setExpandedPackId] = useState(null); const [showPaywall, setShowPaywall] = useState(false); const [showTeaser, setShowTeaser] = useState(false); const [teaserPack, setTeaserPack] = useState(null); const scrollY = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { scrollY.value = event.contentOffset.y; }, }); const hasPremiumAccess = useMemo(() => { return user?.is_premium || partner?.is_premium || subscription.isProUser; }, [user?.is_premium, partner?.is_premium, subscription.isProUser]); const packsByCategory = useMemo(() => { const grouped = new Map(); categories.forEach(cat => grouped.set(cat.id, [])); grouped.set("uncategorized", []); packs.forEach(pack => { const catId = pack.category_id || "uncategorized"; const existing = grouped.get(catId) || []; existing.push(pack); grouped.set(catId, existing); }); return grouped; }, [packs, categories]); const handlePackPress = useCallback((packId: string) => { setExpandedPackId(current => current === packId ? null : packId); }, []); const handlePackToggle = useCallback(async (pack: QuestionPack) => { if (pack.is_premium && !hasPremiumAccess) { setTeaserPack(pack); setShowTeaser(true); return; } const result = await togglePack(pack.id); if (!result.success && result.reason === "no_couple") { Alert.alert( "Pair with Partner", "You need to pair with your partner before you can customise question packs.", [ { text: "Later", style: "cancel" }, { text: "Pair Now", onPress: () => router.push("/pairing") } ] ); } }, [hasPremiumAccess, togglePack]); const handlePackInfo = useCallback((pack: QuestionPack) => { const isPremiumLocked = pack.is_premium && !hasPremiumAccess; if (isPremiumLocked) { setTeaserPack(pack); setShowTeaser(true); } else { router.push(`/pack/${pack.id}`); } }, [hasPremiumAccess]); const handleTeaserUnlock = useCallback(() => { setShowTeaser(false); setShowPaywall(true); }, []); const handleTeaserClose = useCallback(() => { setShowTeaser(false); setTeaserPack(null); }, []); const enabledPackCount = enabledPackIds.length; const totalQuestions = packs .filter(p => enabledPackIds.includes(p.id)) .reduce((sum, p) => sum + (p.questions?.[0]?.count || 0), 0); // Animated styles for collapsing header const largeHeaderStyle = useAnimatedStyle(() => { const opacity = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE * 0.5], [1, 0], Extrapolation.CLAMP ); const translateY = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE], [0, -20], Extrapolation.CLAMP ); return { opacity, transform: [{ translateY }] }; }); 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 }; }); return ( {/* Fixed Nav Bar */} {/* Nav bar background with blur */} {Platform.OS === "ios" ? ( ) : ( )} {/* Back button */} router.back()} hitSlop={8} > {/* Compact title (fades in on scroll) */} Question Packs {/* Spacer for alignment */} {/* Scrollable Content */} } contentContainerStyle={styles.scrollContent} > {/* Large Header (fades out on scroll) */} COLLECTION Question Packs {totalQuestions} {totalQuestions === 1 ? "question" : "questions"} from {enabledPackCount} {enabledPackCount === 1 ? "pack" : "packs"} {/* Categories */} {categories.map(category => { const categoryPacks = packsByCategory.get(category.id) || []; if (categoryPacks.length === 0) return null; return ( ); })} {(packsByCategory.get("uncategorized")?.length || 0) > 0 && ( )} {/* Modals */} {teaserPack && ( )} setShowPaywall(false)} onSuccess={() => fetchPacks()} /> ); } const NAV_BAR_HEIGHT = 44; const STATUS_BAR_HEIGHT = 60; // Match other pages const styles = StyleSheet.create({ container: { flex: 1, }, // Fixed Nav Bar navBar: { position: "absolute", top: 0, left: 0, right: 0, height: STATUS_BAR_HEIGHT + NAV_BAR_HEIGHT, flexDirection: "row", alignItems: "center", paddingTop: STATUS_BAR_HEIGHT - 10, paddingHorizontal: spacing.md, zIndex: 100, }, navBarBackground: { ...StyleSheet.absoluteFillObject, borderBottomWidth: 1, borderBottomColor: "rgba(255, 255, 255, 0.1)", overflow: "hidden", }, backButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: "rgba(255, 255, 255, 0.1)", justifyContent: "center", alignItems: "center", }, navBarTitle: { flex: 1, ...typography.headline, color: colors.text, textAlign: "center", marginRight: 40, // Balance with back button }, navBarSpacer: { width: 0, }, // Large Header largeHeader: { alignItems: "center", paddingBottom: spacing.lg, }, label: { ...typography.caption1, fontWeight: "600", letterSpacing: 3, color: colors.primary, textAlign: "center", marginBottom: spacing.xs, }, title: { ...typography.title1, color: colors.text, textAlign: "center", }, separator: { flexDirection: "row", alignItems: "center", justifyContent: "center", marginVertical: spacing.md, width: 140, }, separatorLine: { flex: 1, height: 1, backgroundColor: "rgba(233, 69, 96, 0.3)", }, separatorDiamond: { width: 6, height: 6, backgroundColor: colors.primary, transform: [{ rotate: "45deg" }], marginHorizontal: spacing.md, opacity: 0.6, }, statsBadge: { flexDirection: "row", alignItems: "center", backgroundColor: "rgba(233, 69, 96, 0.1)", paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.full, borderWidth: 1, borderColor: "rgba(233, 69, 96, 0.2)", gap: spacing.sm, }, statsBadgeText: { ...typography.caption1, color: colors.textSecondary, fontWeight: "500", }, // Scroll Content scrollContent: { paddingTop: 60, // Match other pages paddingBottom: Platform.OS === "ios" ? 120 : 100, }, categoriesContainer: { paddingHorizontal: spacing.lg, }, // Category Section categorySection: { marginBottom: spacing.lg, }, categoryHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingVertical: spacing.sm, marginBottom: spacing.xs, }, categoryHeaderLeft: { flexDirection: "row", alignItems: "center", gap: spacing.sm, }, categoryIcon: { fontSize: 18, }, categoryName: { ...typography.headline, color: colors.text, textTransform: "uppercase", letterSpacing: 1, fontSize: 13, }, categoryBadge: { backgroundColor: colors.primary, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, minWidth: 20, alignItems: "center", }, categoryBadgeText: { ...typography.caption2, color: colors.text, fontWeight: "600", }, packsList: { backgroundColor: "rgba(255, 255, 255, 0.03)", borderRadius: radius.lg, borderWidth: 1, borderColor: colors.glass.border, overflow: "hidden", }, // Pack Row packRow: { borderBottomWidth: 1, borderBottomColor: colors.glass.border, }, packRowMain: { flexDirection: "row", alignItems: "center", padding: spacing.md, gap: spacing.md, }, packIcon: { width: 44, height: 44, borderRadius: 12, backgroundColor: "rgba(255, 255, 255, 0.05)", justifyContent: "center", alignItems: "center", borderWidth: 1, borderColor: "rgba(255, 255, 255, 0.08)", }, packIconEnabled: { backgroundColor: "rgba(233, 69, 96, 0.1)", borderColor: "rgba(233, 69, 96, 0.2)", }, packIconText: { fontSize: 20, }, packContent: { flex: 1, }, packTitleRow: { flexDirection: "row", alignItems: "center", gap: spacing.xs, }, packName: { ...typography.body, color: colors.text, fontWeight: "500", flexShrink: 1, }, premiumBadge: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 8, paddingVertical: 3, borderRadius: radius.full, backgroundColor: "rgba(212, 175, 55, 0.12)", borderWidth: 1, borderColor: "rgba(212, 175, 55, 0.25)", }, premiumBadgeLocked: { backgroundColor: "rgba(212, 175, 55, 0.18)", borderColor: "rgba(212, 175, 55, 0.35)", }, premiumBadgeText: { ...typography.caption2, color: colors.premium.gold, fontWeight: "700", letterSpacing: 0.6, }, packMeta: { ...typography.caption1, color: colors.textTertiary, marginTop: 2, }, packMetaEnabled: { color: colors.primary, }, // Expanded Pack packExpanded: { paddingHorizontal: spacing.md, paddingBottom: spacing.md, paddingTop: 0, }, packDescription: { ...typography.subhead, color: colors.textSecondary, lineHeight: 20, marginBottom: spacing.md, }, packActions: { flexDirection: "row", gap: spacing.sm, }, packInfoButton: { flexDirection: "row", alignItems: "center", gap: spacing.xs, paddingVertical: spacing.sm, paddingHorizontal: spacing.md, borderRadius: radius.sm, backgroundColor: "rgba(255, 255, 255, 0.05)", borderWidth: 1, borderColor: colors.glass.border, }, packInfoButtonText: { ...typography.subhead, color: colors.textSecondary, }, packToggleButton: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.xs, paddingVertical: spacing.sm, paddingHorizontal: spacing.md, borderRadius: radius.sm, backgroundColor: "rgba(255, 255, 255, 0.08)", borderWidth: 1, borderColor: "rgba(255, 255, 255, 0.12)", }, packToggleButtonEnabled: { backgroundColor: "rgba(233, 69, 96, 0.1)", borderColor: "rgba(233, 69, 96, 0.3)", }, packToggleButtonLocked: { backgroundColor: "rgba(212, 175, 55, 0.1)", borderColor: "rgba(212, 175, 55, 0.3)", }, packToggleButtonText: { ...typography.subhead, color: colors.text, fontWeight: "500", }, packToggleButtonTextEnabled: { color: colors.primary, }, packToggleButtonTextLocked: { color: colors.premium.gold, }, });