import { router } from 'expo-router';
import React, { useState } from 'react';
import {
ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, Text,
TextInput, View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { clearCache } from '../lib/api';
import { C } from '../lib/theme';
import type { Priority } from '../lib/types';
/* Scrollable screen shell with pull-to-refresh (clears the API cache) */
export function Screen({
children, onRefresh,
}: {
children: React.ReactNode;
onRefresh?: () => void;
}) {
const insets = useSafeAreaInsets();
const [refreshing, setRefreshing] = useState(false);
return (
{
setRefreshing(true);
clearCache();
onRefresh();
setTimeout(() => setRefreshing(false), 400);
}}
/>
) : undefined
}>
{children}
);
}
export function Loading() {
return (
);
}
export function ErrBox({ msg }: { msg: string }) {
return (
Could not load data — {msg}. Pull down to retry.
);
}
export function Sec({ title, count }: { title: string; count?: number | string }) {
return (
{title.toUpperCase()}
{count != null && {count}}
);
}
export function TileGrid({ children }: { children: React.ReactNode }) {
return {children};
}
export function Tile({
label, value, unit, sub, delta, deltaTone,
}: {
label: string;
value: string;
unit?: string;
sub?: string;
delta?: string;
deltaTone?: 'good' | 'bad' | 'neutral';
}) {
const toneColor = deltaTone === 'good' ? C.green : deltaTone === 'bad' ? C.red : C.muted;
return (
{label.toUpperCase()}
{value}
{unit ? {unit} : null}
{delta ? {delta} : null}
{sub ? {sub} : null}
);
}
/* Today-screen navigation card */
export function NavCard({
title, big, unit, note, flagline, okline, href,
}: {
title: string;
big: string;
unit?: string;
note?: string;
flagline?: string;
okline?: string;
href: string;
}) {
return (
[s.navcard, pressed && { borderColor: C.accent }]}
onPress={() => router.push(href as any)}>
{title.toUpperCase()}
›
{big}
{unit ? {unit} : null}
{flagline ? {flagline} : null}
{okline ? {okline} : null}
{note ? (
{note}
) : null}
);
}
export function Panel({
title, sub, children,
}: {
title?: string;
sub?: string;
children: React.ReactNode;
}) {
return (
{title ? (
{title}
{sub ? {sub} : null}
) : null}
{children}
);
}
export function PriorityCard({ p, rank }: { p: Priority; rank: number }) {
const color = p.severity === 'flag' ? C.red : C.yellow;
const border = p.severity === 'flag' ? 'rgba(255,69,58,0.35)' : 'rgba(245,197,66,0.35)';
return (
#{rank} · {String(p.severity).toUpperCase()}
{p.title}
{p.detail}
);
}
export function SearchInput({
value, onChange, placeholder,
}: {
value: string;
onChange: (v: string) => void;
placeholder: string;
}) {
return (
);
}
export function SrcNote({ children }: { children: React.ReactNode }) {
return {children};
}
export function LegendRow({ items }: { items: [string, string][] }) {
return (
{items.map(([label, color]) => (
{label}
))}
);
}
const s = StyleSheet.create({
errBox: {
borderWidth: 1, borderColor: 'rgba(255,69,58,0.3)', backgroundColor: C.redDim,
borderRadius: 12, padding: 14, marginVertical: 12,
},
sec: {
fontSize: 11, fontWeight: '600', letterSpacing: 0.8, color: C.muted,
marginTop: 26, marginBottom: 10,
},
tiles: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
tile: {
backgroundColor: C.surface, borderWidth: 1, borderColor: C.border, borderRadius: 14,
padding: 14, flexBasis: '47%', flexGrow: 1,
},
tileLbl: { fontSize: 11, color: C.muted, letterSpacing: 0.5, marginBottom: 6 },
tileVal: { fontSize: 24, fontWeight: '300', letterSpacing: -0.6, color: C.text },
tileUnit: { fontSize: 12, color: C.muted, fontWeight: '400', letterSpacing: 0 },
tileSub: { fontSize: 11.5, color: C.muted, marginTop: 5 },
navcard: {
backgroundColor: C.surface, borderWidth: 1, borderColor: C.border, borderRadius: 14,
padding: 14, flexBasis: '47%', flexGrow: 1,
},
navHead: {
flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8,
},
navT: { fontSize: 12, fontWeight: '600', color: C.muted, letterSpacing: 0.6 },
navBig: { fontSize: 22, fontWeight: '300', letterSpacing: -0.5, color: C.text },
navNote: { fontSize: 11.5, color: C.muted, marginTop: 4 },
panel: {
backgroundColor: C.surface, borderWidth: 1, borderColor: C.border, borderRadius: 14,
padding: 14, marginBottom: 10,
},
panelHead: {
flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 10,
},
priority: {
borderWidth: 1, borderRadius: 12, paddingVertical: 13, paddingHorizontal: 14,
backgroundColor: C.surface, marginBottom: 8,
},
search: {
paddingVertical: 10, paddingHorizontal: 14, borderRadius: 12, borderWidth: 1,
borderColor: C.border, backgroundColor: C.surface, color: C.text, fontSize: 14,
marginTop: 4, marginBottom: 12,
},
srcNote: { fontSize: 11, color: C.dim, marginTop: 12, lineHeight: 16 },
});