version 0.0.0.3

This commit is contained in:
2026-03-09 00:31:21 +01:00
parent 5eb3ebf199
commit c67da1d085
459 changed files with 15070 additions and 12783 deletions
+223
View File
@@ -0,0 +1,223 @@
/**
* QRScanner - Vollbild QR-Code Scanner fuer ARIA Pairing
*
* Scannt QR-Codes im Format:
* {"host": "rvs.hackersoft.de", "port": 443, "token": "a3f8b2c9..."}
*/
import React, { useState, useCallback } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Modal,
Alert,
Platform,
PermissionsAndroid,
} from 'react-native';
import { CameraScreen } from 'react-native-camera-kit';
import { ConnectionConfig } from '../services/rvs';
interface QRScannerProps {
visible: boolean;
onScan: (config: ConnectionConfig) => void;
onClose: () => void;
}
/** QR-Daten parsen und validieren */
function parseQRData(data: string): ConnectionConfig | null {
try {
const parsed = JSON.parse(data);
if (!parsed.host || !parsed.token) {
return null;
}
return {
host: String(parsed.host),
port: Number(parsed.port) || 443,
token: String(parsed.token),
useTLS: parsed.tls !== false, // Standard: TLS an
};
} catch {
return null;
}
}
/** Kamera-Berechtigung anfordern (Android) */
async function requestCameraPermission(): Promise<boolean> {
if (Platform.OS !== 'android') return true;
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
{
title: 'Kamera-Zugriff',
message: 'ARIA Cockpit braucht Kamera-Zugriff um den QR-Code zu scannen.',
buttonPositive: 'Erlauben',
buttonNegative: 'Ablehnen',
},
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
} catch {
return false;
}
}
const QRScanner: React.FC<QRScannerProps> = ({ visible, onScan, onClose }) => {
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
const [scanned, setScanned] = useState(false);
// Berechtigung pruefen beim Oeffnen
React.useEffect(() => {
if (visible) {
setScanned(false);
requestCameraPermission().then(granted => {
setHasPermission(granted);
if (!granted) {
Alert.alert(
'Kamera blockiert',
'Bitte erlaube den Kamera-Zugriff in den Einstellungen.',
[{ text: 'OK', onPress: onClose }],
);
}
});
}
}, [visible, onClose]);
const handleBarcodeScan = useCallback(
(event: { nativeEvent: { codeStringValue: string } }) => {
if (scanned) return;
const data = event.nativeEvent.codeStringValue;
const config = parseQRData(data);
if (config) {
setScanned(true);
onScan(config);
} else {
// Ungueltig — einmal warnen, dann weiter scannen lassen
setScanned(true);
Alert.alert(
'Ungueltiger QR-Code',
'Der QR-Code hat nicht das erwartete ARIA-Format.\n\nErwartet: {"host": "...", "port": 443, "token": "..."}',
[{ text: 'Nochmal', onPress: () => setScanned(false) }],
);
}
},
[scanned, onScan],
);
if (!visible) return null;
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="fullScreen"
onRequestClose={onClose}
>
<View style={styles.container}>
{hasPermission ? (
<>
<CameraScreen
scanBarcode={true}
onReadCode={handleBarcodeScan}
showFrame={true}
frameColor="#0096FF"
laserColor="#0096FF"
colorForScannerFrame="#0096FF"
/>
{/* Overlay oben */}
<View style={styles.topOverlay}>
<Text style={styles.title}>QR-Code scannen</Text>
<Text style={styles.subtitle}>
Richte die Kamera auf den QR-Code vom RVS
</Text>
</View>
{/* Abbrechen-Button unten */}
<View style={styles.bottomOverlay}>
<TouchableOpacity style={styles.cancelButton} onPress={onClose}>
<Text style={styles.cancelText}>Abbrechen</Text>
</TouchableOpacity>
</View>
</>
) : (
<View style={styles.noPermission}>
<Text style={styles.noPermissionText}>Kamera-Zugriff wird benoetigt</Text>
<TouchableOpacity style={styles.cancelButton} onPress={onClose}>
<Text style={styles.cancelText}>Zurueck</Text>
</TouchableOpacity>
</View>
)}
</View>
</Modal>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
},
topOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
paddingTop: 60,
paddingBottom: 20,
paddingHorizontal: 20,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
alignItems: 'center',
},
title: {
color: '#FFFFFF',
fontSize: 20,
fontWeight: '700',
},
subtitle: {
color: '#AAAACC',
fontSize: 14,
marginTop: 6,
textAlign: 'center',
},
bottomOverlay: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
paddingBottom: 40,
paddingTop: 20,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
alignItems: 'center',
},
cancelButton: {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
paddingHorizontal: 40,
paddingVertical: 14,
borderRadius: 12,
},
cancelText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
noPermission: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
noPermissionText: {
color: '#AAAACC',
fontSize: 16,
marginBottom: 20,
textAlign: 'center',
},
});
export default QRScanner;
+26 -4
View File
@@ -18,6 +18,7 @@ import {
} from 'react-native';
import rvs, { ConnectionState, RVSMessage, ConnectionConfig } from '../services/rvs';
import ModeSelector from '../components/ModeSelector';
import QRScanner from '../components/QRScanner';
// --- Typen ---
@@ -56,6 +57,7 @@ const SettingsScreen: React.FC = () => {
const [manualPort, setManualPort] = useState('8765');
const [currentMode, setCurrentMode] = useState('normal');
const [gpsEnabled, setGpsEnabled] = useState(false);
const [scannerVisible, setScannerVisible] = useState(false);
const [logTab, setLogTab] = useState<LogTab>('live');
const [logs, setLogs] = useState<LogEntry[]>([]);
const [events, setEvents] = useState<EventEntry[]>([]);
@@ -105,11 +107,24 @@ const SettingsScreen: React.FC = () => {
// --- QR-Code scannen ---
const openQRScanner = useCallback(() => {
// In Produktion: QR-Scanner oeffnen (react-native-camera)
// Format: aria://host:port?token=xxx&tls=1
setScannerVisible(true);
}, []);
const handleQRScan = useCallback((config: ConnectionConfig) => {
setScannerVisible(false);
// Felder im UI aktualisieren
setManualHost(config.host);
setManualPort(String(config.port));
setManualToken(config.token);
// Direkt verbinden
rvs.setConfig(config);
rvs.connect();
Alert.alert(
'QR-Scanner',
'QR-Code Scanner wird in der naechsten Version implementiert.\n\nBitte Token manuell eingeben.',
'Pairing erfolgreich',
`Verbinde mit ${config.host}:${config.port}...`,
);
}, []);
@@ -170,6 +185,12 @@ const SettingsScreen: React.FC = () => {
connectionState === 'connecting' ? 'Verbinde...' : 'Getrennt';
return (
<>
<QRScanner
visible={scannerVisible}
onScan={handleQRScan}
onClose={() => setScannerVisible(false)}
/>
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* === Verbindung === */}
@@ -348,6 +369,7 @@ const SettingsScreen: React.FC = () => {
{/* Platz am Ende */}
<View style={styles.bottomSpacer} />
</ScrollView>
</>
);
};