diff --git a/src/screens/SentinelScreen.tsx b/src/screens/SentinelScreen.tsx index 795d3c7..579c5b2 100644 --- a/src/screens/SentinelScreen.tsx +++ b/src/screens/SentinelScreen.tsx @@ -23,6 +23,7 @@ import { colors, typography, spacing, borderRadius, shadows } from '../theme/col import { SystemStatus, KillSwitchLog } from '../types'; import VaultScreen from './VaultScreen'; +// Nautical-themed mnemonic word list (unique words only) const MNEMONIC_WORDS = [ 'anchor', 'harbor', 'compass', 'lighthouse', 'current', 'ocean', 'tide', 'voyage', 'keel', 'stern', 'bow', 'mast', 'sail', 'port', 'starboard', 'reef', @@ -30,9 +31,17 @@ const MNEMONIC_WORDS = [ 'horizon', 'sextant', 'sound', 'drift', 'wake', 'mariner', 'pilot', 'fathom', 'buoy', 'lantern', 'harpoon', 'lagoon', 'bay', 'strait', 'riptide', 'foam', 'coral', 'pearl', 'trident', 'ebb', 'flow', 'vault', 'cipher', 'shroud', - 'salt', 'wave', 'grotto', 'lagoon', 'storm', 'north', 'south', 'east', - 'west', 'ember', 'cabin', 'signal', 'ledger', 'torch', 'sanctum', 'oath', -]; + 'salt', 'wave', 'grotto', 'storm', 'north', 'south', 'east', 'west', + 'ember', 'cabin', 'ledger', 'torch', 'sanctum', 'oath', 'depths', 'captain', +] as const; + +// Animation timing constants +const ANIMATION_DURATION = { + pulse: 1200, + glow: 1500, + rotate: 30000, + heartbeatPress: 150, +} as const; const generateMnemonic = (wordCount = 12) => { const words: string[] = []; @@ -49,11 +58,14 @@ const splitMnemonic = (words: string[]) => [ words.slice(8, 12), ]; +// Icon names type for type safety +type StatusIconName = 'checkmark-circle' | 'warning' | 'alert-circle'; + // Status configuration with nautical theme const statusConfig: Record = { @@ -123,46 +135,56 @@ export default function SentinelScreen() { useEffect(() => { // Pulse animation - Animated.loop( + const pulseAnimation = Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.06, - duration: 1200, + duration: ANIMATION_DURATION.pulse, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, - duration: 1200, + duration: ANIMATION_DURATION.pulse, useNativeDriver: true, }), ]) - ).start(); + ); + pulseAnimation.start(); // Glow animation - Animated.loop( + const glowAnimation = Animated.loop( Animated.sequence([ Animated.timing(glowAnim, { toValue: 1, - duration: 1500, + duration: ANIMATION_DURATION.glow, useNativeDriver: true, }), Animated.timing(glowAnim, { toValue: 0.5, - duration: 1500, + duration: ANIMATION_DURATION.glow, useNativeDriver: true, }), ]) - ).start(); + ); + glowAnimation.start(); // Slow rotate for ship wheel - Animated.loop( + const rotateAnimation = Animated.loop( Animated.timing(rotateAnim, { toValue: 1, - duration: 30000, + duration: ANIMATION_DURATION.rotate, useNativeDriver: true, }) - ).start(); - }, []); + ); + rotateAnimation.start(); + + // Cleanup animations on unmount to prevent memory leaks + return () => { + pulseAnimation.stop(); + glowAnimation.stop(); + rotateAnimation.stop(); + }; + }, [pulseAnim, glowAnim, rotateAnim]); const openVaultWithMnemonic = () => { const words = generateMnemonic(); @@ -230,23 +252,23 @@ export default function SentinelScreen() { Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.15, - duration: 150, + duration: ANIMATION_DURATION.heartbeatPress, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, - duration: 150, + duration: ANIMATION_DURATION.heartbeatPress, useNativeDriver: true, }), ]).start(); - // Add new log + // Add new log using functional update to avoid stale closure const newLog: KillSwitchLog = { id: Date.now().toString(), action: 'HEARTBEAT_CONFIRMED', timestamp: new Date(), }; - setLogs([newLog, ...logs]); + setLogs((prevLogs) => [newLog, ...prevLogs]); // Reset status if warning if (status === 'warning') { @@ -324,7 +346,7 @@ export default function SentinelScreen() { colors={currentStatus.gradientColors} style={styles.statusCircle} > - + @@ -390,6 +412,8 @@ export default function SentinelScreen() { style={styles.vaultAccessButton} onPress={openVaultWithMnemonic} activeOpacity={0.8} + accessibilityLabel="Open Shadow Vault" + accessibilityRole="button" > Open @@ -400,6 +424,8 @@ export default function SentinelScreen() { style={styles.heartbeatButton} onPress={handleHeartbeat} activeOpacity={0.9} + accessibilityLabel="Signal the watch - Confirm your presence" + accessibilityRole="button" > setShowVault(false)} activeOpacity={0.85} + accessibilityLabel="Close vault" + accessibilityRole="button" > @@ -468,15 +496,17 @@ export default function SentinelScreen() { style={styles.mnemonicOverlay} behavior={Platform.OS === 'ios' ? 'padding' : undefined} > - + + setShowMnemonic(false)} activeOpacity={0.85} + accessibilityLabel="Close mnemonic modal" + accessibilityRole="button" > @@ -514,6 +544,9 @@ export default function SentinelScreen() { onPress={handleScreenshot} activeOpacity={0.85} disabled={isCapturing} + accessibilityLabel="Take screenshot backup of mnemonic" + accessibilityRole="button" + accessibilityState={{ disabled: isCapturing }} > {isCapturing ? 'CAPTURING...' : 'PHYSICAL BACKUP (SCREENSHOT)'} @@ -523,6 +556,8 @@ export default function SentinelScreen() { style={styles.mnemonicSecondaryButton} onPress={handleEmailBackup} activeOpacity={0.85} + accessibilityLabel="Send mnemonic backup via email" + accessibilityRole="button" > EMAIL BACKUP @@ -542,12 +577,15 @@ export default function SentinelScreen() { style={styles.emailSendButton} onPress={handleSendEmail} activeOpacity={0.85} + accessibilityLabel="Send backup email" + accessibilityRole="button" > SEND EMAIL ) : null} - + +