/** * SettingsScreen - Einstellungen und Verbindungsverwaltung * * QR-Scanner fuer Pairing, Moduswahl, GPS-Toggle, Log-Viewer. */ import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, TextInput, TouchableOpacity, ScrollView, Switch, StyleSheet, Alert, Platform, } from 'react-native'; import rvs, { ConnectionState, RVSMessage, ConnectionConfig } from '../services/rvs'; import ModeSelector from '../components/ModeSelector'; // --- Typen --- interface LogEntry { id: string; timestamp: number; source: string; message: string; level: 'info' | 'warn' | 'error'; } interface EventEntry { id: string; timestamp: number; title: string; description: string; } type LogTab = 'live' | 'events'; // Container-Farben fuer Live-Logs const SOURCE_COLORS: Record = { 'aria-core': '#4A9EFF', // Blau bridge: '#FFD60A', // Gelb proxy: '#FFFFFF', // Weiss rvs: '#34C759', // Gruen default: '#8888AA', // Grau }; // --- Komponente --- const SettingsScreen: React.FC = () => { const [connectionState, setConnectionState] = useState('disconnected'); const [manualToken, setManualToken] = useState(''); const [manualHost, setManualHost] = useState(''); const [manualPort, setManualPort] = useState('8765'); const [currentMode, setCurrentMode] = useState('normal'); const [gpsEnabled, setGpsEnabled] = useState(false); const [logTab, setLogTab] = useState('live'); const [logs, setLogs] = useState([]); const [events, setEvents] = useState([]); let logIdCounter = 0; // RVS-Nachrichten abonnieren (Logs und Events) useEffect(() => { const unsubState = rvs.onStateChange(setConnectionState); setConnectionState(rvs.getState()); const unsubMessage = rvs.onMessage((message: RVSMessage) => { if (message.type === 'log') { const entry: LogEntry = { id: `log_${Date.now()}_${logIdCounter++}`, timestamp: message.timestamp, source: (message.payload.source as string) || 'default', message: (message.payload.message as string) || '', level: (message.payload.level as 'info' | 'warn' | 'error') || 'info', }; setLogs(prev => [...prev.slice(-200), entry]); // Max 200 Eintraege behalten } if (message.type === 'event') { const entry: EventEntry = { id: `evt_${Date.now()}_${logIdCounter++}`, timestamp: message.timestamp, title: (message.payload.title as string) || '', description: (message.payload.description as string) || '', }; setEvents(prev => [...prev.slice(-100), entry]); } // Modus-Bestaetigung if (message.type === 'mode') { const mode = message.payload.mode as string; if (mode) setCurrentMode(mode); } }); return () => { unsubState(); unsubMessage(); }; }, []); // --- QR-Code scannen --- const openQRScanner = useCallback(() => { // In Produktion: QR-Scanner oeffnen (react-native-camera) // Format: aria://host:port?token=xxx&tls=1 Alert.alert( 'QR-Scanner', 'QR-Code Scanner wird in der naechsten Version implementiert.\n\nBitte Token manuell eingeben.', ); }, []); // --- Manuelle Verbindung --- const connectManually = useCallback(() => { if (!manualHost.trim() || !manualToken.trim()) { Alert.alert('Fehler', 'Host und Token muessen angegeben werden.'); return; } const config: ConnectionConfig = { host: manualHost.trim(), port: parseInt(manualPort, 10) || 8765, token: manualToken.trim(), useTLS: true, }; rvs.setConfig(config); rvs.connect(); }, [manualHost, manualPort, manualToken]); const disconnectRVS = useCallback(() => { rvs.disconnect(); }, []); // --- GPS Toggle --- const handleGPSToggle = useCallback((value: boolean) => { setGpsEnabled(value); // In Produktion: Wert in AsyncStorage persistieren }, []); // --- Modus aendern --- const handleModeChange = useCallback((modeId: string) => { setCurrentMode(modeId); }, []); // --- Zeitformat --- const formatTime = (ts: number): string => { return new Date(ts).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit', }); }; // --- Verbindungsstatus --- const connectionDotColor = connectionState === 'connected' ? '#34C759' : connectionState === 'connecting' ? '#FFD60A' : '#FF3B30'; const connectionLabel = connectionState === 'connected' ? 'Verbunden' : connectionState === 'connecting' ? 'Verbinde...' : 'Getrennt'; return ( {/* === Verbindung === */} Verbindung {/* Status-Anzeige */} {connectionLabel} {connectionState === 'connected' && ( Trennen )} {/* QR-Scanner */} {'\uD83D\uDCF1'} QR-Code scannen (Pairing) {/* Manuelle Eingabe */} Host Port Token Verbinden {/* === Modus === */} Betriebsmodus {/* === GPS === */} Standort GPS-Position mitsenden Standort wird automatisch an Nachrichten angehaengt {/* === Logs === */} Protokoll {/* Tab-Umschalter */} setLogTab('live')} > Live Logs setLogTab('events')} > Events {/* Log-Inhalt */} {logTab === 'live' ? ( logs.length > 0 ? ( logs.slice(-50).map(log => ( {formatTime(log.timestamp)} [{log.source}] {log.message} )) ) : ( Noch keine Logs empfangen ) ) : ( events.length > 0 ? ( events.slice(-30).map(event => ( {formatTime(event.timestamp)} {event.title} {event.description} )) ) : ( Noch keine Events empfangen ) )} {/* Log-Aktionen */} { if (logTab === 'live') setLogs([]); else setEvents([]); }} > Protokoll l\u00F6schen {/* === About === */} {'\u00DC'}ber ARIA Cockpit Version 0.1.0 (Alpha) Stefans Kommandozentrale f{'\u00FC'}r ARIA.{'\n'} Gebaut mit React Native + TypeScript. {/* Platz am Ende */} ); }; // --- Styles --- const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#0D0D1A', }, content: { padding: 16, }, sectionTitle: { color: '#8888AA', fontSize: 13, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1, marginTop: 20, marginBottom: 8, marginLeft: 4, }, card: { backgroundColor: '#12122A', borderRadius: 14, padding: 16, borderWidth: 1, borderColor: '#1E1E2E', }, // Verbindungsstatus statusRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, }, statusDot: { width: 10, height: 10, borderRadius: 5, marginRight: 10, }, statusLabel: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', flex: 1, }, disconnectButton: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6, backgroundColor: 'rgba(255, 59, 48, 0.2)', }, disconnectText: { color: '#FF3B30', fontSize: 13, fontWeight: '600', }, // QR-Button qrButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1E1E2E', borderRadius: 10, padding: 14, marginBottom: 16, }, qrIcon: { fontSize: 22, marginRight: 10, }, qrText: { color: '#FFFFFF', fontSize: 15, fontWeight: '500', }, // Eingabefelder inputLabel: { color: '#8888AA', fontSize: 12, marginBottom: 4, marginLeft: 2, }, input: { backgroundColor: '#1E1E2E', borderRadius: 8, paddingHorizontal: 14, paddingVertical: 10, color: '#FFFFFF', fontSize: 15, marginBottom: 12, borderWidth: 1, borderColor: '#2A2A3E', }, connectButton: { backgroundColor: '#0096FF', borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 4, }, connectButtonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '700', }, // Toggle toggleRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, toggleInfo: { flex: 1, marginRight: 12, }, toggleLabel: { color: '#FFFFFF', fontSize: 15, fontWeight: '500', }, toggleHint: { color: '#666680', fontSize: 12, marginTop: 2, }, // Logs tabRow: { flexDirection: 'row', marginBottom: 12, borderRadius: 8, backgroundColor: '#1E1E2E', overflow: 'hidden', }, tab: { flex: 1, paddingVertical: 10, alignItems: 'center', }, tabActive: { backgroundColor: '#0096FF', }, tabText: { color: '#666680', fontSize: 13, fontWeight: '600', }, tabTextActive: { color: '#FFFFFF', }, logContainer: { maxHeight: 300, backgroundColor: '#0A0A18', borderRadius: 8, padding: 10, }, logEntry: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 4, }, logTime: { color: '#555570', fontSize: 11, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', marginRight: 6, }, logSource: { fontSize: 11, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', fontWeight: '700', marginRight: 6, }, logMessage: { color: '#CCCCDD', fontSize: 11, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', flex: 1, }, logError: { color: '#FF3B30', }, logWarn: { color: '#FFD60A', }, emptyLog: { color: '#555570', fontSize: 13, textAlign: 'center', padding: 20, }, eventEntry: { marginBottom: 10, paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#1E1E2E', }, eventTime: { color: '#555570', fontSize: 11, }, eventTitle: { color: '#FFFFFF', fontSize: 14, fontWeight: '600', marginTop: 2, }, eventDescription: { color: '#8888AA', fontSize: 13, marginTop: 2, }, clearButton: { marginTop: 10, padding: 10, alignItems: 'center', borderRadius: 8, backgroundColor: '#1E1E2E', }, clearButtonText: { color: '#666680', fontSize: 13, fontWeight: '500', }, // About aboutTitle: { color: '#FFFFFF', fontSize: 18, fontWeight: '700', }, aboutVersion: { color: '#0096FF', fontSize: 13, marginTop: 2, }, aboutInfo: { color: '#666680', fontSize: 13, marginTop: 8, lineHeight: 20, }, bottomSpacer: { height: 40, }, }); export default SettingsScreen;