Initialize Schnappix Photo Booth application with custom UVC camera, silent Wi-Fi IPP printing, local gallery storage, and kiosk screen pinning support

This commit is contained in:
2026-05-25 16:44:15 +02:00
parent 5958319772
commit 872e1ffc86
51 changed files with 8931 additions and 1 deletions
+350
View File
@@ -0,0 +1,350 @@
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
TouchableOpacity,
ScrollView,
Switch,
Alert,
} from 'react-native';
import { THEME } from '../styles/theme';
import KioskMode from '../../modules/kiosk-mode';
import { AppSettings, saveSettings } from '../services/settings';
interface AdminScreenProps {
currentSettings: AppSettings;
onSave: (settings: AppSettings) => void;
onClose: () => void;
}
export default function AdminScreen({ currentSettings, onSave, onClose }: AdminScreenProps) {
const [countdown, setCountdown] = useState<string>(String(currentSettings.countdownDuration));
const [printerIp, setPrinterIp] = useState<string>(currentSettings.printerIp);
const [password, setPassword] = useState<string>(currentSettings.adminPassword);
const [kioskActive, setKioskActive] = useState<boolean>(false);
const [isDeviceOwner, setIsDeviceOwner] = useState<boolean>(false);
// Load Kiosk and Device Owner status on mount
useEffect(() => {
const checkKioskStatus = () => {
try {
const active = KioskMode.isKioskActive();
const owner = KioskMode.isDeviceOwner();
setKioskActive(active);
setIsDeviceOwner(owner);
} catch (e) {
console.warn('Failed to query native KioskMode module:', e);
}
};
checkKioskStatus();
// Poll status every second
const interval = setInterval(checkKioskStatus, 1000);
return () => clearInterval(interval);
}, []);
const handleKioskToggle = (enable: boolean) => {
try {
if (enable) {
const success = KioskMode.startKiosk();
if (success) {
setKioskActive(true);
Alert.alert(
'Kiosk Mode Started',
isDeviceOwner
? 'True Kiosk Mode active. System navigation buttons are completely locked.'
: 'Screen Pinning initiated. Accept the OS prompt to lock the screen.'
);
} else {
Alert.alert('Error', 'Failed to start Kiosk Mode.');
}
} else {
const success = KioskMode.stopKiosk();
if (success) {
setKioskActive(false);
Alert.alert('Kiosk Mode Stopped', 'System navigation buttons are unlocked.');
} else {
Alert.alert('Error', 'Failed to stop Kiosk Mode.');
}
}
} catch (e: any) {
Alert.alert('Native Error', e.message || 'Kiosk module error');
}
};
const handleSave = async () => {
const duration = parseInt(countdown, 10);
if (isNaN(duration) || duration < 1 || duration > 30) {
Alert.alert('Invalid Input', 'Countdown duration must be a number between 1 and 30 seconds.');
return;
}
if (!printerIp.trim()) {
Alert.alert('Invalid Input', 'Printer IP address cannot be empty.');
return;
}
if (!password.trim() || password.length < 4) {
Alert.alert('Invalid Input', 'Admin password must be at least 4 digits.');
return;
}
const updated: AppSettings = {
countdownDuration: duration,
printerIp: printerIp.trim(),
adminPassword: password.trim(),
kioskModeEnabled: kioskActive,
};
try {
await saveSettings(updated);
onSave(updated);
Alert.alert('Success', 'Settings saved successfully!');
} catch (e) {
Alert.alert('Error', 'Failed to save settings to disk.');
}
};
return (
<View style={styles.container}>
<View style={styles.headerRow}>
<Text style={styles.title}>SCHNAPPIX SETTINGS</Text>
<TouchableOpacity style={styles.closeBtn} onPress={onClose}>
<Text style={styles.closeBtnText}>Back to Booth</Text>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={styles.scrollContent}>
{/* Printer Configurations */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Printer Configuration</Text>
<Text style={styles.cardDesc}>
Configure the local IP address of your Wi-Fi connected Canon CP1300 printer.
</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Printer IP Address</Text>
<TextInput
style={styles.input}
placeholder="e.g. 192.168.1.100"
placeholderTextColor={THEME.colors.textMuted}
value={printerIp}
onChangeText={setPrinterIp}
keyboardType="numeric"
/>
</View>
</View>
{/* Booth Behavior */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Photo Booth Behavior</Text>
<View style={styles.inputGroup}>
<Text style={styles.label}>Countdown Duration (seconds)</Text>
<TextInput
style={styles.input}
placeholder="Default: 3"
placeholderTextColor={THEME.colors.textMuted}
value={countdown}
onChangeText={setCountdown}
keyboardType="number-pad"
/>
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>Admin Panel Password</Text>
<TextInput
style={styles.input}
placeholder="Default: 1234"
placeholderTextColor={THEME.colors.textMuted}
secureTextEntry
value={password}
onChangeText={setPassword}
keyboardType="number-pad"
/>
</View>
</View>
{/* Security & Kiosk Mode */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Kiosk Mode & Security</Text>
<Text style={styles.cardDesc}>
Lock the screen to prevent guests from closing the app or opening system settings.
</Text>
<View style={styles.statusRow}>
<Text style={styles.statusLabel}>Kiosk Mode State:</Text>
<Text style={[styles.statusValue, kioskActive ? styles.statusActive : styles.statusInactive]}>
{kioskActive ? 'LOCKED 🔒' : 'UNLOCKED 🔓'}
</Text>
</View>
<View style={styles.statusRow}>
<Text style={styles.statusLabel}>Device Owner Mode:</Text>
<Text style={[styles.statusValue, isDeviceOwner ? styles.statusActive : styles.statusWarning]}>
{isDeviceOwner ? 'ACTIVE (True Lock) ✅' : 'INACTIVE (Screen Pinning Fallback) ⚠️'}
</Text>
</View>
{!isDeviceOwner && (
<Text style={styles.kioskWarningText}>
Note: To enable True Lock task mode without prompt bypasses, make the app device owner using ADB.
</Text>
)}
<View style={styles.toggleRow}>
<Text style={styles.toggleLabel}>Lock Screen (Kiosk Mode)</Text>
<Switch
value={kioskActive}
onValueChange={handleKioskToggle}
trackColor={{ false: THEME.colors.surfaceSecondary, true: THEME.colors.primary }}
thumbColor={THEME.colors.text}
/>
</View>
</View>
<TouchableOpacity style={styles.saveBtn} onPress={handleSave}>
<Text style={styles.saveBtnText}>SAVE SETTINGS</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: THEME.colors.background,
},
headerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: THEME.spacing.lg,
paddingHorizontal: THEME.spacing.xl,
borderBottomWidth: 1,
borderColor: THEME.colors.border,
backgroundColor: THEME.colors.surface,
},
title: {
fontSize: 22,
fontWeight: '900',
color: THEME.colors.text,
letterSpacing: 2,
},
closeBtn: {
backgroundColor: THEME.colors.surfaceSecondary,
borderWidth: 1,
borderColor: THEME.colors.border,
paddingVertical: THEME.spacing.sm,
paddingHorizontal: THEME.spacing.lg,
borderRadius: THEME.borderRadius.round,
},
closeBtnText: {
color: THEME.colors.text,
fontSize: 14,
fontWeight: 'bold',
},
scrollContent: {
padding: THEME.spacing.xl,
alignItems: 'center',
},
card: {
width: '100%',
maxWidth: 680,
backgroundColor: THEME.colors.surface,
borderRadius: THEME.borderRadius.md,
borderWidth: 1,
borderColor: THEME.colors.border,
padding: THEME.spacing.lg,
marginBottom: THEME.spacing.lg,
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
color: THEME.colors.text,
marginBottom: THEME.spacing.xs,
},
cardDesc: {
fontSize: 14,
color: THEME.colors.textMuted,
marginBottom: THEME.spacing.md,
},
inputGroup: {
marginBottom: THEME.spacing.md,
},
label: {
fontSize: 14,
color: THEME.colors.text,
marginBottom: THEME.spacing.xs,
},
input: {
height: 48,
backgroundColor: THEME.colors.surfaceSecondary,
borderWidth: 1,
borderColor: THEME.colors.border,
borderRadius: THEME.borderRadius.sm,
color: THEME.colors.text,
paddingHorizontal: THEME.spacing.md,
fontSize: 16,
},
statusRow: {
flexDirection: 'row',
marginBottom: THEME.spacing.xs,
},
statusLabel: {
fontSize: 14,
color: THEME.colors.textMuted,
marginRight: THEME.spacing.sm,
},
statusValue: {
fontSize: 14,
fontWeight: 'bold',
},
statusActive: {
color: THEME.colors.success,
},
statusInactive: {
color: THEME.colors.textMuted,
},
statusWarning: {
color: THEME.colors.accent,
},
kioskWarningText: {
fontSize: 12,
color: THEME.colors.textMuted,
marginTop: THEME.spacing.xs,
fontStyle: 'italic',
},
toggleRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: THEME.spacing.lg,
paddingTop: THEME.spacing.md,
borderTopWidth: 1,
borderTopColor: THEME.colors.border,
},
toggleLabel: {
fontSize: 16,
fontWeight: '600',
color: THEME.colors.text,
},
saveBtn: {
width: '100%',
maxWidth: 680,
height: 52,
backgroundColor: THEME.colors.primary,
borderRadius: THEME.borderRadius.md,
justifyContent: 'center',
alignItems: 'center',
marginTop: THEME.spacing.sm,
marginBottom: THEME.spacing.xl,
},
saveBtnText: {
color: THEME.colors.text,
fontSize: 16,
fontWeight: 'bold',
letterSpacing: 2,
},
});