/** * FeedPage - Shows latest episodes across all subscribed shows * Reverse chronological order, like an inbox/timeline */ import { createSignal, For, Show } from "solid-js"; import { useKeyboard } from "@opentui/solid"; import { useFeedStore } from "@/stores/feed"; import { format } from "date-fns"; import type { Episode } from "@/types/episode"; import type { Feed } from "@/types/feed"; type FeedPageProps = { focused: boolean; onPlayEpisode?: (episode: Episode, feed: Feed) => void; onExit?: () => void; }; export function FeedPage(props: FeedPageProps) { const feedStore = useFeedStore(); const [selectedIndex, setSelectedIndex] = createSignal(0); const [isRefreshing, setIsRefreshing] = createSignal(false); const allEpisodes = () => feedStore.getAllEpisodesChronological(); const formatDate = (date: Date): string => { return format(date, "MMM d, yyyy"); }; const formatDuration = (seconds: number): string => { const mins = Math.floor(seconds / 60); const hrs = Math.floor(mins / 60); if (hrs > 0) return `${hrs}h ${mins % 60}m`; return `${mins}m`; }; const handleRefresh = async () => { setIsRefreshing(true); await feedStore.refreshAllFeeds(); setIsRefreshing(false); }; useKeyboard((key) => { if (!props.focused) return; const episodes = allEpisodes(); if (key.name === "down" || key.name === "j") { setSelectedIndex((i) => Math.min(episodes.length - 1, i + 1)); } else if (key.name === "up" || key.name === "k") { setSelectedIndex((i) => Math.max(0, i - 1)); } else if (key.name === "return" || key.name === "enter") { const item = episodes[selectedIndex()]; if (item) props.onPlayEpisode?.(item.episode, item.feed); } else if (key.name === "home" || key.name === "g") { setSelectedIndex(0); } else if (key.name === "end") { setSelectedIndex(episodes.length - 1); } else if (key.name === "pageup") { setSelectedIndex((i) => Math.max(0, i - 10)); } else if (key.name === "pagedown") { setSelectedIndex((i) => Math.min(episodes.length - 1, i + 10)); } else if (key.name === "r") { handleRefresh(); } else if (key.name === "escape") { props.onExit?.(); } }); return ( {/* Status line */} Refreshing feeds... {/* Episode list */} 0} fallback={ No episodes yet. Subscribe to shows from Discover or Search. } > {(item, index) => ( setSelectedIndex(index())} > {index() === selectedIndex() ? ">" : " "} {item.episode.title} {item.feed.podcast.title} {formatDate(item.episode.pubDate)} {formatDuration(item.episode.duration)} )} ); }