import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, SafeAreaView, Animated, Modal, } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons'; import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors'; import { SystemStatus, KillSwitchLog } from '../types'; import VaultScreen from './VaultScreen'; // Animation timing constants const ANIMATION_DURATION = { pulse: 1200, glow: 1500, rotate: 30000, heartbeatPress: 150, } as const; // Icon names type for type safety type StatusIconName = 'checkmark-circle' | 'warning' | 'alert-circle'; // Status configuration with nautical theme const statusConfig: Record = { normal: { color: colors.sentinel.statusNormal, label: 'ALL CLEAR', icon: 'checkmark-circle', description: 'The lighthouse burns bright. All systems nominal.', gradientColors: ['#6BBF8A', '#4A9F6A'], }, warning: { color: colors.sentinel.statusWarning, label: 'STORM WARNING', icon: 'warning', description: 'Anomaly detected. Captain\'s attention required.', gradientColors: ['#E5B873', '#C99953'], }, releasing: { color: colors.sentinel.statusCritical, label: 'RELEASE ACTIVE', icon: 'alert-circle', description: 'Legacy release protocol initiated.', gradientColors: ['#E57373', '#C55353'], }, }; // Mock data const initialLogs: KillSwitchLog[] = [ { id: '1', action: 'HEARTBEAT_CONFIRMED', timestamp: new Date('2024-01-18T09:30:00') }, { id: '2', action: 'SUBSCRIPTION_VERIFIED', timestamp: new Date('2024-01-17T00:00:00') }, { id: '3', action: 'JOURNAL_ACTIVITY', timestamp: new Date('2024-01-16T15:42:00') }, { id: '4', action: 'HEARTBEAT_CONFIRMED', timestamp: new Date('2024-01-15T11:20:00') }, ]; export default function SentinelScreen() { const [status, setStatus] = useState('normal'); const [lastSubscriptionCheck] = useState(new Date('2024-01-18T00:00:00')); const [lastFlowActivity] = useState(new Date('2024-01-18T10:30:00')); const [logs, setLogs] = useState(initialLogs); const [pulseAnim] = useState(new Animated.Value(1)); const [glowAnim] = useState(new Animated.Value(0.5)); const [rotateAnim] = useState(new Animated.Value(0)); const [showVault, setShowVault] = useState(false); useEffect(() => { const pulseAnimation = Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.06, duration: ANIMATION_DURATION.pulse, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: ANIMATION_DURATION.pulse, useNativeDriver: true, }), ]) ); pulseAnimation.start(); const glowAnimation = Animated.loop( Animated.sequence([ Animated.timing(glowAnim, { toValue: 1, duration: ANIMATION_DURATION.glow, useNativeDriver: true, }), Animated.timing(glowAnim, { toValue: 0.5, duration: ANIMATION_DURATION.glow, useNativeDriver: true, }), ]) ); glowAnimation.start(); const rotateAnimation = Animated.loop( Animated.timing(rotateAnim, { toValue: 1, duration: ANIMATION_DURATION.rotate, useNativeDriver: true, }) ); rotateAnimation.start(); return () => { pulseAnimation.stop(); glowAnimation.stop(); rotateAnimation.stop(); }; }, [pulseAnim, glowAnim, rotateAnim]); const openVault = () => setShowVault(true); const handleHeartbeat = () => { Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.15, duration: ANIMATION_DURATION.heartbeatPress, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: ANIMATION_DURATION.heartbeatPress, useNativeDriver: true, }), ]).start(); const newLog: KillSwitchLog = { id: Date.now().toString(), action: 'HEARTBEAT_CONFIRMED', timestamp: new Date(), }; setLogs((prevLogs) => [newLog, ...prevLogs]); if (status === 'warning') { setStatus('normal'); } }; const formatDateTime = (date: Date) => date.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); const formatTimeAgo = (date: Date) => { const now = new Date(); const diff = now.getTime() - date.getTime(); const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (hours > 24) return `${Math.floor(hours / 24)} days ago`; if (hours > 0) return `${hours}h ${minutes}m ago`; return `${minutes}m ago`; }; const currentStatus = statusConfig[status]; const spin = rotateAnim.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'], }); return ( {/* Header */} LIGHTHOUSE The Watchful Guardian {/* Status Display */} {currentStatus.label} {currentStatus.description} {/* Ship Wheel Watermark */} {/* Metrics Grid */} SUBSCRIPTION {formatTimeAgo(lastSubscriptionCheck)} {formatDateTime(lastSubscriptionCheck)} LAST JOURNAL {formatTimeAgo(lastFlowActivity)} {formatDateTime(lastFlowActivity)} {/* Shadow Vault Access */} Shadow Vault Access sealed assets from the Lighthouse. Open {/* Heartbeat Button */} SIGNAL THE WATCH Confirm your presence, Captain {/* Watch Log */} WATCH LOG {logs.map((log) => ( {log.action} {formatDateTime(log.timestamp)} ))} {/* Vault Modal */} setShowVault(false)} > {showVault ? : null} setShowVault(false)} activeOpacity={0.85} accessibilityLabel="Close vault" accessibilityRole="button" > ); } const styles = StyleSheet.create({ container: { flex: 1 }, gradient: { flex: 1 }, safeArea: { flex: 1 }, scrollView: { flex: 1 }, scrollContent: { padding: spacing.lg, paddingBottom: 120, }, header: { marginBottom: spacing.xl }, headerTitleRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.xs, }, title: { fontSize: typography.fontSize.xl, fontWeight: '700', color: colors.sentinel.text, letterSpacing: typography.letterSpacing.widest, fontFamily: typography.fontFamily.serif, }, subtitle: { fontSize: typography.fontSize.sm, color: colors.sentinel.textSecondary, marginLeft: spacing.xl + spacing.sm, fontStyle: 'italic', }, statusContainer: { alignItems: 'center', paddingVertical: spacing.xl, marginBottom: spacing.lg, position: 'relative', }, statusCircleOuter: { position: 'absolute', width: 170, height: 170, borderRadius: 85, }, statusCircle: { width: 140, height: 140, borderRadius: 70, justifyContent: 'center', alignItems: 'center', marginBottom: spacing.md, ...shadows.glow, }, statusLabel: { fontSize: typography.fontSize.xl, fontWeight: '700', letterSpacing: typography.letterSpacing.widest, marginBottom: spacing.sm, }, statusDescription: { fontSize: typography.fontSize.base, color: colors.sentinel.textSecondary, textAlign: 'center', maxWidth: 280, fontStyle: 'italic', }, wheelWatermark: { position: 'absolute', top: 200, right: -60, opacity: 0.5, }, metricsGrid: { flexDirection: 'row', gap: spacing.md, marginBottom: spacing.lg, }, metricCard: { flex: 1, backgroundColor: colors.sentinel.cardBackground, borderRadius: borderRadius.xl, padding: spacing.base, borderWidth: 1, borderColor: colors.sentinel.cardBorder, }, metricIconContainer: { width: 40, height: 40, borderRadius: 20, backgroundColor: `${colors.sentinel.primary}15`, justifyContent: 'center', alignItems: 'center', marginBottom: spacing.sm, }, metricLabel: { fontSize: typography.fontSize.xs, color: colors.sentinel.textSecondary, letterSpacing: typography.letterSpacing.wide, marginBottom: spacing.xs, fontWeight: '600', }, metricValue: { fontSize: typography.fontSize.md, color: colors.sentinel.text, fontWeight: '700', marginBottom: spacing.xs, }, metricTime: { fontSize: typography.fontSize.xs, color: colors.sentinel.textSecondary, fontFamily: typography.fontFamily.mono, }, heartbeatButton: { borderRadius: borderRadius.xl, overflow: 'hidden', marginBottom: spacing.xl, ...shadows.medium, }, heartbeatGradient: { padding: spacing.lg }, heartbeatContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.md, }, heartbeatText: { fontSize: typography.fontSize.lg, fontWeight: '700', color: '#fff', letterSpacing: typography.letterSpacing.wider, }, heartbeatSubtext: { fontSize: typography.fontSize.sm, color: 'rgba(255, 255, 255, 0.8)', marginTop: 2, fontStyle: 'italic', }, logsSection: { backgroundColor: colors.sentinel.cardBackground, borderRadius: borderRadius.xl, padding: spacing.base, borderWidth: 1, borderColor: colors.sentinel.cardBorder, }, logsSectionHeader: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md, paddingBottom: spacing.sm, borderBottomWidth: 1, borderBottomColor: colors.sentinel.cardBorder, }, logsSectionTitle: { fontSize: typography.fontSize.xs, color: colors.sentinel.textSecondary, letterSpacing: typography.letterSpacing.widest, fontWeight: '700', }, logItem: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: spacing.sm, }, logDot: { width: 8, height: 8, borderRadius: 4, backgroundColor: colors.sentinel.primary, marginTop: 6, marginRight: spacing.md, }, logContent: { flex: 1 }, logAction: { fontSize: typography.fontSize.sm, color: colors.sentinel.text, fontFamily: typography.fontFamily.mono, fontWeight: '500', marginBottom: 2, }, logTime: { fontSize: typography.fontSize.xs, color: colors.sentinel.textSecondary, fontFamily: typography.fontFamily.mono, }, vaultAccessCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.sentinel.cardBackground, borderRadius: borderRadius.xl, padding: spacing.base, marginBottom: spacing.lg, borderWidth: 1, borderColor: colors.sentinel.cardBorder, }, vaultAccessIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: `${colors.nautical.teal}20`, alignItems: 'center', justifyContent: 'center', marginRight: spacing.md, }, vaultAccessContent: { flex: 1 }, vaultAccessTitle: { fontSize: typography.fontSize.base, fontWeight: '600', color: colors.sentinel.text, marginBottom: 2, }, vaultAccessText: { fontSize: typography.fontSize.sm, color: colors.sentinel.textSecondary, }, vaultAccessButton: { backgroundColor: colors.nautical.teal, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: borderRadius.full, }, vaultAccessButtonText: { color: colors.nautical.cream, fontWeight: '700', fontSize: typography.fontSize.sm, }, vaultModalContainer: { flex: 1, backgroundColor: colors.vault.background, }, vaultCloseButton: { position: 'absolute', top: spacing.xl + spacing.lg, right: spacing.lg, width: 36, height: 36, borderRadius: 18, backgroundColor: 'rgba(26, 58, 74, 0.65)', alignItems: 'center', justifyContent: 'center', }, });