Localize app to German, implement auto-save to gallery, always-on camera stream, fix expo-media-library and expo-file-system deprecation/compile errors
This commit is contained in:
+368
-54
@@ -1,23 +1,50 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { StyleSheet, Text, View, TouchableOpacity, Platform } from 'react-native';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
Platform,
|
||||
Modal,
|
||||
TextInput,
|
||||
TouchableWithoutFeedback,
|
||||
} from 'react-native';
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { THEME } from '../styles/theme';
|
||||
import { UsbCameraView, UsbCameraRef } from '../../modules/usb-camera';
|
||||
import { saveToGallery } from '../services/storage';
|
||||
|
||||
interface CameraScreenProps {
|
||||
countdownDuration: number;
|
||||
onPhotoCaptured: (uri: string) => void;
|
||||
onCancel: () => void;
|
||||
isIdle?: boolean;
|
||||
onStartBooth?: () => void;
|
||||
onNavigateToAdmin?: () => void;
|
||||
adminPassword?: string;
|
||||
}
|
||||
|
||||
export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCancel }: CameraScreenProps) {
|
||||
export default function CameraScreen({
|
||||
countdownDuration,
|
||||
onPhotoCaptured,
|
||||
onCancel,
|
||||
isIdle = false,
|
||||
onStartBooth,
|
||||
onNavigateToAdmin,
|
||||
adminPassword = '1234',
|
||||
}: CameraScreenProps) {
|
||||
const [permission, requestPermission] = useCameraPermissions();
|
||||
const [isUsbConnected, setIsUsbConnected] = useState<boolean>(false);
|
||||
const [countdown, setCountdown] = useState<number | string>('');
|
||||
const [isCapturing, setIsCapturing] = useState<boolean>(false);
|
||||
const [hasStarted, setHasStarted] = useState<boolean>(false);
|
||||
|
||||
// Admin Modal States
|
||||
const [passwordModalVisible, setPasswordModalVisible] = useState(false);
|
||||
const [enteredPassword, setEnteredPassword] = useState('');
|
||||
const [errorText, setErrorText] = useState('');
|
||||
|
||||
const usbCameraRef = useRef<UsbCameraRef>(null);
|
||||
const expoCameraRef = useRef<any>(null);
|
||||
|
||||
@@ -28,10 +55,9 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
}
|
||||
}, [permission]);
|
||||
|
||||
// 2. Check if a USB camera is connected.
|
||||
// We can query the USB module or attempt to check every second in the background.
|
||||
// 2. Check if a USB camera is connected.
|
||||
useEffect(() => {
|
||||
let checkInterval: NodeJS.Timeout;
|
||||
let checkInterval: any;
|
||||
if (Platform.OS === 'android') {
|
||||
const checkConnection = async () => {
|
||||
try {
|
||||
@@ -40,12 +66,10 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
setIsUsbConnected(connected);
|
||||
}
|
||||
} catch (e) {
|
||||
// If native view manager is not loaded or errors, fallback to false
|
||||
setIsUsbConnected(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Check initially and run an interval
|
||||
checkConnection();
|
||||
checkInterval = setInterval(checkConnection, 2000);
|
||||
}
|
||||
@@ -55,13 +79,19 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 3. Start the countdown on screen load
|
||||
// 3. Start the countdown on screen load (only when NOT idle)
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
if (isIdle) {
|
||||
setHasStarted(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let timer: any;
|
||||
let count = countdownDuration;
|
||||
|
||||
setCountdown(count);
|
||||
setHasStarted(true);
|
||||
setIsCapturing(false);
|
||||
|
||||
const runTimer = () => {
|
||||
if (count > 1) {
|
||||
@@ -69,7 +99,7 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
setCountdown(count);
|
||||
timer = setTimeout(runTimer, 1000);
|
||||
} else if (count === 1) {
|
||||
setCountdown('Cheese! 📸');
|
||||
setCountdown('Bitte lächeln! 📸');
|
||||
setIsCapturing(true);
|
||||
timer = setTimeout(() => {
|
||||
capture();
|
||||
@@ -82,7 +112,7 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
return () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
};
|
||||
}, [countdownDuration]);
|
||||
}, [countdownDuration, isIdle]);
|
||||
|
||||
// 4. Capture photo function
|
||||
const capture = async () => {
|
||||
@@ -94,6 +124,15 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
// USB Camera Capture
|
||||
console.log('Capturing from USB Camera...');
|
||||
const path = await usbCameraRef.current.takePicture(tempUri);
|
||||
|
||||
// Auto-save individual photo directly to gallery
|
||||
try {
|
||||
await saveToGallery(path);
|
||||
console.log('Auto-saved USB capture to gallery');
|
||||
} catch (e) {
|
||||
console.error('Failed to auto-save USB capture:', e);
|
||||
}
|
||||
|
||||
onPhotoCaptured(path);
|
||||
} else {
|
||||
// Fallback Camera Capture
|
||||
@@ -103,22 +142,47 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
quality: 0.95,
|
||||
skipProcessing: false,
|
||||
});
|
||||
|
||||
// Auto-save individual photo directly to gallery
|
||||
try {
|
||||
await saveToGallery(photo.uri);
|
||||
console.log('Auto-saved fallback capture to gallery');
|
||||
} catch (e) {
|
||||
console.error('Failed to auto-save fallback capture:', e);
|
||||
}
|
||||
|
||||
onPhotoCaptured(photo.uri);
|
||||
} else {
|
||||
throw new Error('Camera ref is not available.');
|
||||
throw new Error('Kamera-Referenz ist nicht verfügbar.');
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Capture failed:', error);
|
||||
alert('Failed to capture photo: ' + error.message);
|
||||
alert('Fehler beim Aufnehmen des Fotos: ' + error.message);
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSettingsTap = () => {
|
||||
setEnteredPassword('');
|
||||
setErrorText('');
|
||||
setPasswordModalVisible(true);
|
||||
};
|
||||
|
||||
const handlePasswordSubmit = () => {
|
||||
if (enteredPassword === adminPassword) {
|
||||
setPasswordModalVisible(false);
|
||||
onNavigateToAdmin?.();
|
||||
} else {
|
||||
setErrorText('Falsches Passwort');
|
||||
setEnteredPassword('');
|
||||
}
|
||||
};
|
||||
|
||||
if (!permission) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.infoText}>Loading permissions...</Text>
|
||||
<Text style={styles.infoText}>Berechtigungen werden geladen...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -126,20 +190,124 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
if (!permission.granted && !isUsbConnected) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.infoText}>We need your permission to show the camera</Text>
|
||||
<Text style={styles.infoText}>Wir benötigen deine Erlaubnis, um die Kamera anzuzeigen</Text>
|
||||
<TouchableOpacity style={styles.btn} onPress={requestPermission}>
|
||||
<Text style={styles.btnText}>Grant Permission</Text>
|
||||
<Text style={styles.btnText}>Erlaubnis erteilen</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.btn, styles.cancelBtn]} onPress={onCancel}>
|
||||
<Text style={styles.btnText}>Go Back</Text>
|
||||
<Text style={styles.btnText}>Zurück</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
if (isIdle) {
|
||||
// Home Overlay (frosty glass box on top of the camera stream)
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={onStartBooth}>
|
||||
<View style={styles.idleOverlayContainer}>
|
||||
{/* Settings button in the top-right corner */}
|
||||
<TouchableOpacity
|
||||
style={styles.settingsButton}
|
||||
onPress={handleSettingsTap}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.settingsIcon}>⚙️</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.welcomeBox}>
|
||||
<Text style={styles.logo}>SCHNAPPIX</Text>
|
||||
<Text style={styles.welcomeTitle}>Willkommen zu unserer Feier!</Text>
|
||||
|
||||
<View style={styles.divider} />
|
||||
|
||||
<View style={styles.startButton}>
|
||||
<Text style={styles.startButtonText}>ZUM STARTEN ÜBERALL TIPPEN</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.welcomeFooter}>Fotos machen • Collage erstellen • Sofort drucken</Text>
|
||||
</View>
|
||||
|
||||
{/* Password Prompt Modal */}
|
||||
<Modal
|
||||
visible={passwordModalVisible}
|
||||
transparent={true}
|
||||
animationType="fade"
|
||||
onRequestClose={() => setPasswordModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.modalTitle}>Admin-Zugang</Text>
|
||||
<Text style={styles.modalSub}>Passwort eingeben, um Einstellungen zu öffnen</Text>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
secureTextEntry
|
||||
placeholder="Passwort"
|
||||
placeholderTextColor={THEME.colors.textMuted}
|
||||
value={enteredPassword}
|
||||
onChangeText={setEnteredPassword}
|
||||
keyboardType="numeric"
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{errorText ? <Text style={styles.error}>{errorText}</Text> : null}
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.cancelButton]}
|
||||
onPress={() => setPasswordModalVisible(false)}
|
||||
>
|
||||
<Text style={styles.buttonText}>Abbrechen</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.confirmButton]}
|
||||
onPress={handlePasswordSubmit}
|
||||
>
|
||||
<Text style={styles.buttonText}>Öffnen</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
// Capture Mode overlays (Countdown and Cancel button)
|
||||
return (
|
||||
<View style={styles.captureOverlayContainer} pointerEvents="box-none">
|
||||
{/* Source indicator */}
|
||||
<View style={styles.hudContainer}>
|
||||
<Text style={styles.cameraSourceIndicator}>
|
||||
Kamera: {isUsbConnected ? 'Externe USB-Kamera' : 'Tablet-Frontkamera (Fallback)'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Countdown overlay */}
|
||||
{hasStarted && (
|
||||
<View style={styles.countdownOverlay} pointerEvents="none">
|
||||
<View style={styles.countdownBox}>
|
||||
<Text style={styles.countdownText}>{countdown}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Cancel Button */}
|
||||
{!isCapturing && (
|
||||
<TouchableOpacity style={styles.backButton} onPress={onCancel}>
|
||||
<Text style={styles.backButtonText}>ABBRECHEN</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* Camera Preview Area */}
|
||||
{/* Camera Preview Background */}
|
||||
<View style={styles.previewContainer}>
|
||||
{Platform.OS === 'android' && isUsbConnected ? (
|
||||
<UsbCameraView ref={usbCameraRef} style={styles.cameraPreview} />
|
||||
@@ -152,28 +320,8 @@ export default function CameraScreen({ countdownDuration, onPhotoCaptured, onCan
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Camera Info HUD */}
|
||||
<View style={styles.hudContainer}>
|
||||
<Text style={styles.cameraSourceIndicator}>
|
||||
Source: {isUsbConnected ? 'External USB Camera' : 'Front Tablet Camera (Fallback)'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Countdown overlay */}
|
||||
{hasStarted && (
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.countdownBox}>
|
||||
<Text style={styles.countdownText}>{countdown}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Cancel Button */}
|
||||
{!isCapturing && (
|
||||
<TouchableOpacity style={styles.backButton} onPress={onCancel}>
|
||||
<Text style={styles.backButtonText}>CANCEL</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{/* Render UI controls/overlays on top */}
|
||||
{renderContent()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -182,32 +330,119 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#000',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
previewContainer: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
cameraPreview: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
idleOverlayContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
||||
},
|
||||
captureOverlayContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
settingsButton: {
|
||||
position: 'absolute',
|
||||
top: 24,
|
||||
right: 24,
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: THEME.borderRadius.round,
|
||||
backgroundColor: 'rgba(30, 30, 35, 0.75)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 999,
|
||||
},
|
||||
settingsIcon: {
|
||||
fontSize: 24,
|
||||
color: THEME.colors.text,
|
||||
},
|
||||
welcomeBox: {
|
||||
alignItems: 'center',
|
||||
padding: THEME.spacing.xl,
|
||||
borderRadius: THEME.borderRadius.lg,
|
||||
backgroundColor: 'rgba(18, 18, 20, 0.8)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.15)',
|
||||
width: '60%',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 20,
|
||||
elevation: 10,
|
||||
},
|
||||
logo: {
|
||||
fontSize: 54,
|
||||
fontWeight: '900',
|
||||
color: THEME.colors.primary,
|
||||
letterSpacing: 8,
|
||||
marginBottom: THEME.spacing.sm,
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textShadowOffset: { width: 0, height: 2 },
|
||||
textShadowRadius: 4,
|
||||
},
|
||||
welcomeTitle: {
|
||||
fontSize: 22,
|
||||
color: THEME.colors.text,
|
||||
textAlign: 'center',
|
||||
marginBottom: THEME.spacing.lg,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
divider: {
|
||||
width: 60,
|
||||
height: 3,
|
||||
backgroundColor: THEME.colors.accent,
|
||||
marginBottom: THEME.spacing.xl,
|
||||
borderRadius: THEME.borderRadius.sm,
|
||||
},
|
||||
startButton: {
|
||||
paddingVertical: THEME.spacing.md,
|
||||
paddingHorizontal: THEME.spacing.xl,
|
||||
backgroundColor: THEME.colors.primary,
|
||||
borderRadius: THEME.borderRadius.round,
|
||||
marginBottom: THEME.spacing.xl,
|
||||
},
|
||||
startButtonText: {
|
||||
color: THEME.colors.text,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 2,
|
||||
},
|
||||
welcomeFooter: {
|
||||
fontSize: 14,
|
||||
color: THEME.colors.textMuted,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
hudContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
paddingVertical: THEME.spacing.xs,
|
||||
paddingHorizontal: THEME.spacing.md,
|
||||
borderRadius: THEME.borderRadius.sm,
|
||||
borderWidth: 0.5,
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
cameraSourceIndicator: {
|
||||
color: THEME.colors.text,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
overlay: {
|
||||
countdownOverlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
@@ -215,37 +450,39 @@ const styles = StyleSheet.create({
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
countdownBox: {
|
||||
backgroundColor: THEME.colors.glassBackground,
|
||||
backgroundColor: 'rgba(20, 20, 25, 0.85)',
|
||||
paddingVertical: THEME.spacing.xl,
|
||||
paddingHorizontal: THEME.spacing.xxl,
|
||||
borderRadius: THEME.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
borderColor: 'rgba(255, 255, 255, 0.15)',
|
||||
minWidth: 180,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 15,
|
||||
elevation: 8,
|
||||
},
|
||||
countdownText: {
|
||||
fontSize: 72,
|
||||
fontSize: 64,
|
||||
fontWeight: '900',
|
||||
color: THEME.colors.text,
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.75)',
|
||||
textShadowOffset: { width: -1, height: 1 },
|
||||
textShadowRadius: 10,
|
||||
textAlign: 'center',
|
||||
},
|
||||
backButton: {
|
||||
position: 'absolute',
|
||||
top: 24,
|
||||
left: 24,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
backgroundColor: 'rgba(30, 30, 35, 0.75)',
|
||||
paddingVertical: THEME.spacing.sm,
|
||||
paddingHorizontal: THEME.spacing.lg,
|
||||
borderRadius: THEME.borderRadius.round,
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
backButtonText: {
|
||||
color: THEME.colors.text,
|
||||
@@ -277,4 +514,81 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: THEME.colors.overlay,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
modalContent: {
|
||||
width: 340,
|
||||
padding: THEME.spacing.lg,
|
||||
borderRadius: THEME.borderRadius.md,
|
||||
backgroundColor: THEME.colors.surfaceSecondary,
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 15,
|
||||
elevation: 10,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: THEME.colors.text,
|
||||
marginBottom: THEME.spacing.xs,
|
||||
},
|
||||
modalSub: {
|
||||
fontSize: 13,
|
||||
color: THEME.colors.textMuted,
|
||||
marginBottom: THEME.spacing.md,
|
||||
textAlign: 'center',
|
||||
},
|
||||
input: {
|
||||
width: '100%',
|
||||
height: 50,
|
||||
backgroundColor: THEME.colors.surface,
|
||||
borderColor: THEME.colors.border,
|
||||
borderWidth: 1,
|
||||
borderRadius: THEME.borderRadius.sm,
|
||||
color: THEME.colors.text,
|
||||
paddingHorizontal: THEME.spacing.md,
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
marginBottom: THEME.spacing.sm,
|
||||
},
|
||||
error: {
|
||||
color: THEME.colors.error,
|
||||
fontSize: 14,
|
||||
marginBottom: THEME.spacing.sm,
|
||||
},
|
||||
modalButtons: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
marginTop: THEME.spacing.sm,
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
height: 45,
|
||||
borderRadius: THEME.borderRadius.sm,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: THEME.spacing.xs,
|
||||
},
|
||||
cancelButton: {
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 1,
|
||||
borderColor: THEME.colors.border,
|
||||
},
|
||||
confirmButton: {
|
||||
backgroundColor: THEME.colors.primary,
|
||||
},
|
||||
buttonText: {
|
||||
color: THEME.colors.text,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user