import { Feather, MaterialCommunityIcons } from "@expo/vector-icons"; import React, { useCallback } from "react"; import { FlatList, ListRenderItemInfo, Platform, StyleSheet, Text, View, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Colors } from "@/constants/colors"; import { useTimmy, type WsEvent } from "@/context/TimmyContext"; const C = Colors.dark; type FeatherIconName = React.ComponentProps["name"]; type MCIconName = React.ComponentProps["name"]; type EventConfig = | { color: string; iconFamily: "Feather"; icon: FeatherIconName; label: (e: WsEvent) => string; } | { color: string; iconFamily: "MaterialCommunityIcons"; icon: MCIconName; label: (e: WsEvent) => string; }; const EVENT_CONFIG: Record = { job_started: { color: C.jobStarted, icon: "play-circle", iconFamily: "Feather", label: (e) => `Job started${e.agentId ? ` · ${e.agentId}` : ""}`, }, job_completed: { color: C.jobCompleted, icon: "check-circle", iconFamily: "Feather", label: (e) => `Job completed${e.agentId ? ` · ${e.agentId}` : ""}`, }, chat: { color: C.chat, icon: "message-circle", iconFamily: "Feather", label: (e) => e.agentId === "timmy" ? `Timmy: ${e.text ?? ""}` : `${e.agentId ?? "visitor"}: ${e.text ?? ""}`, }, agent_state: { color: C.agentState, icon: "cpu", iconFamily: "Feather", label: (e) => `${e.agentId ?? "agent"} → ${e.state ?? "?"}`, }, visitor_count: { color: "#8B5CF6", icon: "users", iconFamily: "Feather", label: (e) => `${e.count ?? 0} visitor${e.count !== 1 ? "s" : ""} online`, }, }; const FALLBACK_CONFIG: EventConfig = { color: C.textMuted, icon: "radio", iconFamily: "Feather", label: (e) => e.type, }; function EventIcon({ config }: { config: EventConfig }) { if (config.iconFamily === "MaterialCommunityIcons") { return ( ); } return ; } function EventRow({ item }: { item: WsEvent }) { const config = EVENT_CONFIG[item.type] ?? FALLBACK_CONFIG; const label = config.label(item); const time = new Date(item.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", }); return ( {label} {item.type.replace(/_/g, " ")} {time} ); } const keyExtractor = (item: WsEvent) => item.id; const renderItem = ({ item }: ListRenderItemInfo) => ( ); export default function FeedScreen() { const { recentEvents, connectionStatus } = useTimmy(); const insets = useSafeAreaInsets(); const topPad = Platform.OS === "web" ? 67 : insets.top; const bottomPad = Platform.OS === "web" ? 84 + 34 : insets.bottom + 84; const ListEmpty = useCallback( () => ( Waiting for events {connectionStatus === "connected" ? "Events will appear here as Timmy works" : "Connect to the API server to see live events"} ), [connectionStatus] ); return ( Live Feed {recentEvents.length} event{recentEvents.length !== 1 ? "s" : ""} {recentEvents.length} data={recentEvents} keyExtractor={keyExtractor} renderItem={renderItem} ListEmptyComponent={ListEmpty} contentContainerStyle={[ styles.listContent, { paddingBottom: bottomPad }, ]} showsVerticalScrollIndicator={false} ItemSeparatorComponent={() => } /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: C.background, }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "flex-start", paddingHorizontal: 24, paddingTop: 12, paddingBottom: 16, }, title: { fontSize: 28, fontFamily: "Inter_700Bold", color: C.text, letterSpacing: -0.5, }, subtitle: { fontSize: 13, fontFamily: "Inter_400Regular", color: C.textSecondary, marginTop: 2, }, countBadge: { backgroundColor: C.surface, borderRadius: 20, paddingHorizontal: 12, paddingVertical: 4, borderWidth: 1, borderColor: C.border, alignSelf: "flex-start", marginTop: 8, }, countText: { fontSize: 13, fontFamily: "Inter_600SemiBold", color: C.textSecondary, }, listContent: { paddingHorizontal: 16, flexGrow: 1, }, row: { flexDirection: "row", alignItems: "flex-start", gap: 12, paddingVertical: 12, paddingHorizontal: 4, }, iconBadge: { width: 36, height: 36, borderRadius: 10, alignItems: "center", justifyContent: "center", flexShrink: 0, }, rowContent: { flex: 1, gap: 3, }, rowLabel: { fontSize: 13, fontFamily: "Inter_400Regular", color: C.text, lineHeight: 18, }, rowType: { fontSize: 11, fontFamily: "Inter_500Medium", textTransform: "uppercase", letterSpacing: 0.5, }, rowTime: { fontSize: 11, fontFamily: "Inter_400Regular", color: C.textMuted, flexShrink: 0, marginTop: 2, }, separator: { height: 1, backgroundColor: C.border, opacity: 0.5, }, emptyContainer: { flex: 1, alignItems: "center", justifyContent: "center", paddingTop: 80, gap: 12, paddingHorizontal: 40, }, emptyTitle: { fontSize: 18, fontFamily: "Inter_600SemiBold", color: C.textSecondary, marginTop: 8, }, emptySubtitle: { fontSize: 14, fontFamily: "Inter_400Regular", color: C.textMuted, textAlign: "center", lineHeight: 20, }, });