Fix iOS crash, storage, and printing issues; setup apk build
This commit is contained in:
+310
-9
@@ -10,9 +10,23 @@ import {
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
|
||||
import {
|
||||
faUserGear,
|
||||
faArrowLeft,
|
||||
faPrint,
|
||||
faCameraRetro,
|
||||
faLock,
|
||||
faBorderAll,
|
||||
faCalendarDay,
|
||||
faImages,
|
||||
faFloppyDisk,
|
||||
faChampagneGlasses,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { THEME } from '../styles/theme';
|
||||
import KioskMode from '../../modules/kiosk-mode';
|
||||
import { AppSettings, saveSettings } from '../services/settings';
|
||||
import { FRAMES } from '../data/frames';
|
||||
|
||||
interface AdminScreenProps {
|
||||
currentSettings: AppSettings;
|
||||
@@ -20,6 +34,30 @@ interface AdminScreenProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type FrameMode = 'off' | 'always' | 'available';
|
||||
type DateOverlayMode = 'off' | 'date' | 'datetime';
|
||||
type DatePosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'bottom-center';
|
||||
|
||||
const FRAME_MODE_OPTIONS: { value: FrameMode; label: string }[] = [
|
||||
{ value: 'off', label: 'Aus' },
|
||||
{ value: 'always', label: 'Immer an' },
|
||||
{ value: 'available', label: 'Verfügbar' },
|
||||
];
|
||||
|
||||
const DATE_OVERLAY_OPTIONS: { value: DateOverlayMode; label: string }[] = [
|
||||
{ value: 'off', label: 'Aus' },
|
||||
{ value: 'date', label: 'Nur Datum' },
|
||||
{ value: 'datetime', label: 'Datum + Uhrzeit' },
|
||||
];
|
||||
|
||||
const DATE_POSITION_OPTIONS: { value: DatePosition; label: string }[] = [
|
||||
{ value: 'bottom-right', label: 'Unten rechts' },
|
||||
{ value: 'bottom-left', label: 'Unten links' },
|
||||
{ value: 'bottom-center', label: 'Unten Mitte' },
|
||||
{ value: 'top-right', label: 'Oben rechts' },
|
||||
{ value: 'top-left', label: 'Oben links' },
|
||||
];
|
||||
|
||||
export default function AdminScreen({ currentSettings, onSave, onClose }: AdminScreenProps) {
|
||||
const [countdown, setCountdown] = useState<string>(String(currentSettings.countdownDuration));
|
||||
const [printerIp, setPrinterIp] = useState<string>(currentSettings.printerIp);
|
||||
@@ -28,6 +66,16 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
||||
const [kioskActive, setKioskActive] = useState<boolean>(false);
|
||||
const [isDeviceOwner, setIsDeviceOwner] = useState<boolean>(false);
|
||||
|
||||
// New settings state
|
||||
const [frameMode, setFrameMode] = useState<FrameMode>(currentSettings.frameMode);
|
||||
const [selectedFrameId, setSelectedFrameId] = useState<string | null>(currentSettings.selectedFrameId);
|
||||
const [dateOverlay, setDateOverlay] = useState<DateOverlayMode>(currentSettings.dateOverlay);
|
||||
const [dateOverlayPosition, setDateOverlayPosition] = useState<DatePosition>(currentSettings.dateOverlayPosition);
|
||||
const [eventText, setEventText] = useState<string>(currentSettings.eventText);
|
||||
const [burstCount, setBurstCount] = useState<string>(String(currentSettings.burstCount));
|
||||
const [burstInterval, setBurstInterval] = useState<string>(String(currentSettings.burstIntervalSec));
|
||||
const [welcomeText, setWelcomeText] = useState<string>(currentSettings.welcomeText);
|
||||
|
||||
// Load Kiosk and Device Owner status on mount
|
||||
useEffect(() => {
|
||||
const checkKioskStatus = () => {
|
||||
@@ -99,11 +147,36 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
||||
return;
|
||||
}
|
||||
|
||||
const burst = parseInt(burstCount, 10);
|
||||
if (isNaN(burst) || burst < 1 || burst > 5) {
|
||||
Alert.alert('Ungültige Eingabe', 'Die Serienbildanzahl muss zwischen 1 und 5 liegen.');
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = parseInt(burstInterval, 10);
|
||||
if (isNaN(interval) || interval < 3 || interval > 30) {
|
||||
Alert.alert('Ungültige Eingabe', 'Der Abstand zwischen Serienbildern muss zwischen 3 und 30 Sekunden liegen.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameMode === 'always' && selectedFrameId === null && FRAMES.length > 0) {
|
||||
Alert.alert('Hinweis', 'Bitte wähle einen Rahmen aus, wenn der Modus "Immer an" aktiviert ist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const updated: AppSettings = {
|
||||
countdownDuration: duration,
|
||||
printerIp: ipTrimmed,
|
||||
adminPassword: password.trim(),
|
||||
kioskModeEnabled: kioskActive,
|
||||
frameMode,
|
||||
selectedFrameId: frameMode === 'always' ? selectedFrameId : null,
|
||||
dateOverlay,
|
||||
dateOverlayPosition,
|
||||
eventText: eventText.trim(),
|
||||
burstCount: burst,
|
||||
burstIntervalSec: interval,
|
||||
welcomeText: welcomeText.trim() || 'Willkommen zu unserer Feier!',
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -115,19 +188,47 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
||||
}
|
||||
};
|
||||
|
||||
// Renders a 3-way toggle segment control
|
||||
const renderSegment = <T extends string>(
|
||||
options: { value: T; label: string }[],
|
||||
current: T,
|
||||
onChange: (v: T) => void,
|
||||
) => (
|
||||
<View style={styles.segmentRow}>
|
||||
{options.map((opt) => (
|
||||
<TouchableOpacity
|
||||
key={opt.value}
|
||||
style={[styles.segmentBtn, current === opt.value && styles.segmentBtnActive]}
|
||||
onPress={() => onChange(opt.value)}
|
||||
>
|
||||
<Text style={[styles.segmentText, current === opt.value && styles.segmentTextActive]}>
|
||||
{opt.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<Animated.View entering={FadeIn.duration(400)} exiting={FadeOut.duration(300)} style={styles.container}>
|
||||
<View style={styles.headerRow}>
|
||||
<Text style={styles.title}>SCHNAPPIX EINSTELLUNGEN</Text>
|
||||
<View style={styles.headerLeft}>
|
||||
<FontAwesomeIcon icon={faUserGear} size={22} color={THEME.colors.primary} />
|
||||
<Text style={styles.title}>EINSTELLUNGEN</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.closeBtn} onPress={onClose}>
|
||||
<FontAwesomeIcon icon={faArrowLeft} size={14} color={THEME.colors.text} />
|
||||
<Text style={styles.closeBtnText}>Zurück zur Fotobox</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
{/* Printer Configurations */}
|
||||
{/* ─── Printer Configuration ─── */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Drucker-Konfiguration</Text>
|
||||
<View style={styles.cardHeader}>
|
||||
<FontAwesomeIcon icon={faPrint} size={16} color={THEME.colors.accent} />
|
||||
<Text style={styles.cardTitle}>Drucker-Konfiguration</Text>
|
||||
</View>
|
||||
<Text style={styles.cardDesc}>
|
||||
Konfiguriere die lokale IP-Adresse deines über WLAN verbundenen Canon CP1300 Druckers.
|
||||
</Text>
|
||||
@@ -144,9 +245,12 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Booth Behavior */}
|
||||
{/* ─── Booth Behavior ─── */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Fotobox-Verhalten</Text>
|
||||
<View style={styles.cardHeader}>
|
||||
<FontAwesomeIcon icon={faCameraRetro} size={16} color={THEME.colors.accent} />
|
||||
<Text style={styles.cardTitle}>Fotobox-Verhalten</Text>
|
||||
</View>
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Countdown-Dauer (Sekunden)</Text>
|
||||
<TextInput
|
||||
@@ -170,11 +274,129 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Begrüßungstext</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Willkommen zu unserer Feier!"
|
||||
placeholderTextColor={THEME.colors.textMuted}
|
||||
value={welcomeText}
|
||||
onChangeText={setWelcomeText}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Event-Name (für Datum-Sticker)</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="z.B. Jannik's Party"
|
||||
placeholderTextColor={THEME.colors.textMuted}
|
||||
value={eventText}
|
||||
onChangeText={setEventText}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Security & Kiosk Mode */}
|
||||
{/* ─── Continuous Shooting ─── */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Kiosk-Modus & Sicherheit</Text>
|
||||
<View style={styles.cardHeader}>
|
||||
<FontAwesomeIcon icon={faImages} size={16} color={THEME.colors.accent} />
|
||||
<Text style={styles.cardTitle}>Serienbild / Burst-Modus</Text>
|
||||
</View>
|
||||
<Text style={styles.cardDesc}>
|
||||
Mehrere Fotos automatisch hintereinander aufnehmen. Bei Anzahl = 1 wird nur ein einzelnes Foto aufgenommen.
|
||||
</Text>
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Anzahl Fotos pro Durchgang (1–5)</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Standard: 1"
|
||||
placeholderTextColor={THEME.colors.textMuted}
|
||||
value={burstCount}
|
||||
onChangeText={setBurstCount}
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Abstand zwischen Fotos (Sekunden, 3–30)</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Standard: 5"
|
||||
placeholderTextColor={THEME.colors.textMuted}
|
||||
value={burstInterval}
|
||||
onChangeText={setBurstInterval}
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ─── Photo Frames ─── */}
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<FontAwesomeIcon icon={faBorderAll} size={16} color={THEME.colors.accent} />
|
||||
<Text style={styles.cardTitle}>Fotorahmen</Text>
|
||||
</View>
|
||||
<Text style={styles.cardDesc}>
|
||||
Rahmen über die Fotos legen. "Aus" deaktiviert Rahmen, "Immer an" erzwingt einen gewählten Rahmen, "Verfügbar" lässt Gäste einen Rahmen auswählen.
|
||||
</Text>
|
||||
{renderSegment(FRAME_MODE_OPTIONS, frameMode, setFrameMode)}
|
||||
|
||||
{frameMode === 'always' && FRAMES.length > 0 && (
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Ausgewählter Rahmen</Text>
|
||||
<View style={styles.frameChipRow}>
|
||||
{FRAMES.map((frame) => (
|
||||
<TouchableOpacity
|
||||
key={frame.id}
|
||||
style={[
|
||||
styles.frameChip,
|
||||
selectedFrameId === frame.id && styles.frameChipActive,
|
||||
]}
|
||||
onPress={() => setSelectedFrameId(frame.id)}
|
||||
>
|
||||
<Text style={[
|
||||
styles.frameChipText,
|
||||
selectedFrameId === frame.id && styles.frameChipTextActive,
|
||||
]}>
|
||||
{frame.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{frameMode === 'always' && FRAMES.length === 0 && (
|
||||
<Text style={styles.warningText}>
|
||||
Noch keine Rahmen hinterlegt. Lege PNG-Dateien in assets/frames/ ab.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* ─── Date/Time Overlay ─── */}
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<FontAwesomeIcon icon={faCalendarDay} size={16} color={THEME.colors.accent} />
|
||||
<Text style={styles.cardTitle}>Datum/Uhrzeit-Einblendung</Text>
|
||||
</View>
|
||||
<Text style={styles.cardDesc}>
|
||||
Datum automatisch auf jedes Foto einblenden. Unabhängig davon ist der Datum-Sticker in der Bearbeitung immer verfügbar.
|
||||
</Text>
|
||||
{renderSegment(DATE_OVERLAY_OPTIONS, dateOverlay, setDateOverlay)}
|
||||
|
||||
{dateOverlay !== 'off' && (
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Position der Einblendung</Text>
|
||||
{renderSegment(DATE_POSITION_OPTIONS, dateOverlayPosition, setDateOverlayPosition)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* ─── Kiosk Mode ─── */}
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<FontAwesomeIcon icon={faLock} size={16} color={THEME.colors.accent} />
|
||||
<Text style={styles.cardTitle}>Kiosk-Modus & Sicherheit</Text>
|
||||
</View>
|
||||
<Text style={styles.cardDesc}>
|
||||
Sperre den Bildschirm, um zu verhindern, dass Gäste die App schließen oder auf die Systemeinstellungen zugreifen.
|
||||
</Text>
|
||||
@@ -210,7 +432,9 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ─── Save Button ─── */}
|
||||
<TouchableOpacity style={styles.saveBtn} onPress={handleSave}>
|
||||
<FontAwesomeIcon icon={faFloppyDisk} size={18} color={THEME.colors.text} style={{ marginRight: 10 }} />
|
||||
<Text style={styles.saveBtnText}>EINSTELLUNGEN SPEICHERN</Text>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
@@ -233,6 +457,11 @@ const styles = StyleSheet.create({
|
||||
borderColor: THEME.colors.border,
|
||||
backgroundColor: THEME.colors.surface,
|
||||
},
|
||||
headerLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
fontWeight: '900',
|
||||
@@ -240,6 +469,9 @@ const styles = StyleSheet.create({
|
||||
letterSpacing: 2,
|
||||
},
|
||||
closeBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
backgroundColor: THEME.colors.surfaceSecondary,
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
@@ -266,11 +498,16 @@ const styles = StyleSheet.create({
|
||||
padding: THEME.spacing.lg,
|
||||
marginBottom: THEME.spacing.lg,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
marginBottom: THEME.spacing.xs,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: THEME.colors.text,
|
||||
marginBottom: THEME.spacing.xs,
|
||||
},
|
||||
cardDesc: {
|
||||
fontSize: 14,
|
||||
@@ -295,6 +532,64 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: THEME.spacing.md,
|
||||
fontSize: 16,
|
||||
},
|
||||
segmentRow: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: THEME.colors.surfaceSecondary,
|
||||
borderRadius: THEME.borderRadius.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
marginBottom: THEME.spacing.md,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
segmentBtn: {
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
segmentBtnActive: {
|
||||
backgroundColor: THEME.colors.primary,
|
||||
},
|
||||
segmentText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: THEME.colors.textMuted,
|
||||
},
|
||||
segmentTextActive: {
|
||||
color: THEME.colors.text,
|
||||
},
|
||||
frameChipRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
marginTop: THEME.spacing.xs,
|
||||
},
|
||||
frameChip: {
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 14,
|
||||
borderRadius: THEME.borderRadius.round,
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
backgroundColor: THEME.colors.surfaceSecondary,
|
||||
},
|
||||
frameChipActive: {
|
||||
borderColor: THEME.colors.primary,
|
||||
backgroundColor: 'rgba(255, 43, 214, 0.15)',
|
||||
},
|
||||
frameChipText: {
|
||||
fontSize: 13,
|
||||
color: THEME.colors.textMuted,
|
||||
fontWeight: '600',
|
||||
},
|
||||
frameChipTextActive: {
|
||||
color: THEME.colors.primary,
|
||||
},
|
||||
warningText: {
|
||||
fontSize: 12,
|
||||
color: THEME.colors.error,
|
||||
fontStyle: 'italic',
|
||||
marginTop: THEME.spacing.xs,
|
||||
},
|
||||
statusRow: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: THEME.spacing.xs,
|
||||
@@ -315,7 +610,7 @@ const styles = StyleSheet.create({
|
||||
color: THEME.colors.textMuted,
|
||||
},
|
||||
statusWarning: {
|
||||
color: THEME.colors.accent,
|
||||
color: THEME.colors.error,
|
||||
},
|
||||
kioskWarningText: {
|
||||
fontSize: 12,
|
||||
@@ -341,12 +636,18 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
maxWidth: 680,
|
||||
height: 52,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: THEME.colors.primary,
|
||||
borderRadius: THEME.borderRadius.md,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: THEME.spacing.sm,
|
||||
marginBottom: THEME.spacing.xl,
|
||||
shadowColor: THEME.colors.primary,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 12,
|
||||
elevation: 8,
|
||||
},
|
||||
saveBtnText: {
|
||||
color: THEME.colors.text,
|
||||
|
||||
Reference in New Issue
Block a user