import React, { useCallback, useEffect, useMemo, useState } from "react"; import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, useColorScheme, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { Ionicons } from "@expo/vector-icons"; import SearchBar from "../components/SearchBar"; import ExpandableItem from "../components/ExpandableItem"; import { useAuth } from "../context/AuthContext"; import { Entry, entriesApi } from "../services/api"; type SortMode = "time" | "subject"; function formatDate(iso: string): string { return new Date(iso).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric", }); } function groupBySubject(entries: Entry[]): { label: string; items: Entry[] }[] { const map = new Map(); entries.forEach((e) => { const key = e.subject?.name ?? e.groupName; if (!map.has(key)) map.set(key, []); map.get(key)!.push(e); }); return Array.from(map.entries()).map(([label, items]) => ({ label, items })); } function groupByDate(entries: Entry[]): { label: string; items: Entry[] }[] { const map = new Map(); entries.forEach((e) => { const key = formatDate(e.timePublished); if (!map.has(key)) map.set(key, []); map.get(key)!.push(e); }); return Array.from(map.entries()).map(([label, items]) => ({ label, items })); } function SectionHeader({ label, dark }: { label: string; dark: boolean }) { return ( {label} ); } function SortToggle({ mode, onChange, dark, accent, }: { mode: SortMode; onChange: (m: SortMode) => void; dark: boolean; accent: string; }) { return ( {(["time", "subject"] as SortMode[]).map((m) => { const active = mode === m; return ( onChange(m)} className="flex-1 flex-row items-center justify-center gap-1.5 py-2 rounded-lg" style={{ backgroundColor: active ? accent : "transparent" }} activeOpacity={0.7} > {m === "time" ? "By time" : "By subject"} ); })} ); } export default function SubscribedFeed() { const dark = useColorScheme() === "dark"; const accent = dark ? "#E07B45" : "#C4622D"; const { user } = useAuth(); const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [query, setQuery] = useState(""); const [sortMode, setSortMode] = useState("time"); const fetchFeed = useCallback(async () => { if (!user || user.subscribedSubjectIds.length === 0) { setEntries([]); return; } setLoading(true); setError(""); try { const pages = await Promise.all( user.subscribedSubjectIds.map((subjectId) => entriesApi.getEntries({ subjectId, page: 0 }) ) ); const merged = pages .flatMap((p) => p.content) .sort((a, b) => new Date(b.timePublished).getTime() - new Date(a.timePublished).getTime() ); setEntries(merged); } catch { setError("Couldn't load your feed."); } finally { setLoading(false); } }, [user?.subscribedSubjectIds]); useEffect(() => { fetchFeed(); }, [fetchFeed]); // Filter then group const filtered = useMemo(() => { if (!query.trim()) return entries; const lower = query.toLowerCase(); return entries.filter((e) => e.title.toLowerCase().includes(lower) || e.subject?.name?.toLowerCase().includes(lower) || e.groupName?.toLowerCase().includes(lower) ); }, [query, entries]); const grouped = useMemo(() => sortMode === "time" ? groupByDate(filtered) : groupBySubject(filtered), [filtered, sortMode]); if (!user) return ( Your Feed 📰 Sign in to see your feed Subscribe to subjects and their latest entries will appear here. ); if (!loading && user.subscribedSubjectIds.length === 0) return ( Your Feed 🔖 No subscriptions yet Go to Discover and tap a subject tag to subscribe. ); return ( {/* Header */} Your Feed {loading ? "Loading…" : `${entries.length} entries · ${user.subscribedSubjectIds.length} subjects`} {loading ? : } {loading && entries.length === 0 ? ( ) : error ? ( ⚠️ {error} Retry ) : ( {/* Toggle */} {filtered.length === 0 ? ( 🔍 No results for "{query}" ) : ( grouped.map((section) => ( {section.items.map((item) => ( ))} )) )} )} ); }