import React from 'react'; import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; import { AlertCircle, Cpu, ShieldCheck, ShieldAlert, AlertOctagon, AlertTriangle, CheckSquare, HelpCircle, FileText, Image as ImageIcon, Music, Video, Globe, Type, } from 'lucide-react-native'; import type { LucideIcon } from 'lucide-react-native'; import { COLORS, SPACING, RADIUS, FONT_SIZES, FONT_WEIGHTS, ACCENT, getBadgeBgColor, } from '../theme/colors'; import type { AnalysisSession } from '../types/analysis'; import { useTranslation, getTranslation, enumLabel } from '../i18n'; interface RunningItemProgress { progress: number; completedComponents: string[]; } interface Props { item: AnalysisSession; onPress: () => void; onLongPress: () => void; progress?: RunningItemProgress; } function formatRelativeTime(dateStr: string): string { const { t } = getTranslation(); const date = new Date(dateStr); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMin = Math.floor(diffMs / 60000); if (diffMin < 1) return t('justNow'); if (diffMin < 60) return t('minutesAgo', { min: diffMin }); const diffHr = Math.floor(diffMin / 60); if (diffHr < 24) return t('hoursAgo', { hr: diffHr }); const diffDay = Math.floor(diffHr / 24); if (diffDay < 7) return t('daysAgo', { day: diffDay }); return date.toLocaleDateString(); } const TYPE_ICON: Record = { text: Type, image: ImageIcon, audio: Music, video: Video, url: Globe, }; interface CardSummary { badgeScore: number; label: string; badgeColor: string; badgeIcon: LucideIcon; } /** Decide what to show on the right side of the card based on session type. */ function summarizeSession(item: AnalysisSession, t: (k: any) => string, isRo: boolean): CardSummary { const componentsRun = item.components_run || []; const isSingle = componentsRun.length === 1; const single = isSingle ? componentsRun[0] : null; if (single === 'domain' || single === 'source_assessment') { const trust = (item as any).source_trust_score ?? 0; return { badgeScore: trust, label: (item as any).source_verdict ? enumLabel((item as any).source_verdict) : t('sourceAssessment'), badgeColor: trust >= 70 ? '#22c55e' : trust >= 40 ? '#eab308' : trust >= 20 ? '#f97316' : '#ef4444', badgeIcon: trust >= 70 ? ShieldCheck : trust >= 40 ? ShieldAlert : AlertOctagon, }; } if (single === 'ai_tampered' || single === 'ai-tampered') { const score = item.risk_score ?? 0; return { badgeScore: score, label: t('aiDetection'), badgeColor: score >= 80 ? '#ef4444' : score >= 60 ? '#f97316' : score >= 40 ? '#eab308' : '#22c55e', badgeIcon: score >= 60 ? AlertTriangle : Cpu, }; } if (single === 'techniques') { const score = item.risk_score ?? 0; return { badgeScore: score, label: t('manipulationTechniques'), badgeColor: score >= 70 ? '#ef4444' : score >= 40 ? '#f97316' : score >= 20 ? '#eab308' : '#22c55e', badgeIcon: score >= 70 ? AlertOctagon : score >= 40 ? AlertTriangle : ShieldCheck, }; } if (single === 'claims') { const score = item.risk_score ?? 0; return { badgeScore: score, label: t('claimsVerification'), badgeColor: ACCENT.violet, badgeIcon: CheckSquare, }; } // Pipeline (multi-component or unknown) — use risk_score + risk_category const score = item.risk_score ?? 0; return { badgeScore: score, label: item.risk_category ? enumLabel(item.risk_category) : (item.risk_level ? enumLabel(item.risk_level) : t('analysis')), badgeColor: score >= 70 ? '#ef4444' : score >= 50 ? '#f97316' : score >= 30 ? '#eab308' : score > 0 ? '#22c55e' : ACCENT.violet, badgeIcon: score >= 70 ? AlertOctagon : score >= 30 ? AlertTriangle : score > 0 ? ShieldCheck : HelpCircle, }; } export default function HistoryCard({ item, onPress, onLongPress, progress }: Props) { const { t, language } = useTranslation(); const isRo = language === 'ro'; const TypeIcon = TYPE_ICON[item.input_type || 'text'] || AlertCircle; const statusDone = item.status === 'completed'; const isRunning = item.status === 'running' || item.status === 'pending' || item.status === 'processing'; const summary = summarizeSession(item, t, isRo); const a11yLabel = [ item.input_text || item.input_url || t('mediaAnalysis'), item.created_at ? formatRelativeTime(item.created_at) : null, statusDone ? `${summary.label}, ${Math.round(summary.badgeScore)}` : item.status, ] .filter(Boolean) .join('. '); return ( {/* Type icon */} {/* Content */} {item.input_text || item.input_url || t('mediaAnalysis')} {item.created_at && ( {formatRelativeTime(item.created_at)} )} {isRunning && ( {progress ? `${Math.round(progress.progress)}%` : item.status} )} {!statusDone && !isRunning && item.status && ( {item.status} )} {isRunning && progress && progress.progress > 0 && ( )} {/* Score / state */} {statusDone ? ( <> {Math.round(summary.badgeScore)} {summary.label} ) : ( )} ); } const styles = StyleSheet.create({ card: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(255,255,255,0.025)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)', borderRadius: RADIUS.xl, padding: 14, marginBottom: SPACING.sm + 2, }, typeIconBox: { width: 38, height: 38, borderRadius: RADIUS.lg, backgroundColor: 'rgba(167,139,250,0.10)', justifyContent: 'center', alignItems: 'center', marginRight: 12, }, content: { flex: 1, marginRight: SPACING.sm, }, inputText: { color: COLORS.text.primary, fontSize: FONT_SIZES.sm, lineHeight: 19, marginBottom: 4, }, metaRow: { flexDirection: 'row', alignItems: 'center', }, time: { color: COLORS.text.muted, fontSize: FONT_SIZES.xs, }, statusBadge: { marginLeft: SPACING.sm, paddingHorizontal: SPACING.sm, paddingVertical: 1, borderRadius: RADIUS.full, backgroundColor: 'rgba(255,255,255,0.06)', }, statusText: { color: COLORS.status.yellow, fontSize: FONT_SIZES.xs - 1, textTransform: 'capitalize', }, progressBarBg: { width: '100%', height: 3, borderRadius: 1.5, backgroundColor: 'rgba(255,255,255,0.08)', overflow: 'hidden', marginTop: SPACING.xs, }, progressBarFill: { height: '100%', borderRadius: 1.5, backgroundColor: COLORS.brand.primary, }, scoreBox: { alignItems: 'center', minWidth: 64, gap: 4, }, scoreCircle: { width: 44, height: 44, borderRadius: 22, borderWidth: 2, justifyContent: 'center', alignItems: 'center', }, scoreNum: { fontSize: 16, fontWeight: FONT_WEIGHTS.bold, letterSpacing: -0.4, }, catBadge: { flexDirection: 'row', alignItems: 'center', gap: 3, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6, maxWidth: 90, }, catText: { fontSize: 9.5, fontWeight: FONT_WEIGHTS.semibold, letterSpacing: 0.4, textTransform: 'uppercase', }, });