- Introduced new translation files for medication, personal, and weight management in Chinese. - Updated the main index file to include the new translation modules. - Enhanced the medication type definitions to include 'ointment'. - Refactored workout type labels to utilize i18n for better localization support. - Improved sleep quality descriptions and recommendations with i18n integration.
763 lines
22 KiB
TypeScript
763 lines
22 KiB
TypeScript
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||
import { Colors } from '@/constants/Colors';
|
||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||
import { useI18n } from '@/hooks/useI18n';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import { CameraType, CameraView, useCameraPermissions } from 'expo-camera';
|
||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||
import { Image } from 'expo-image';
|
||
import * as ImagePicker from 'expo-image-picker';
|
||
import { LinearGradient } from 'expo-linear-gradient';
|
||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||
import React, { useMemo, useRef, useState } from 'react';
|
||
import {
|
||
ActivityIndicator,
|
||
Alert,
|
||
Dimensions,
|
||
Modal,
|
||
StyleSheet,
|
||
Text,
|
||
TouchableOpacity,
|
||
View,
|
||
} from 'react-native';
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||
|
||
type MealType = 'breakfast' | 'lunch' | 'dinner' | 'snack';
|
||
|
||
export default function FoodCameraScreen() {
|
||
const { t } = useI18n();
|
||
const insets = useSafeAreaInsets();
|
||
const router = useRouter();
|
||
const params = useLocalSearchParams<{ mealType?: string }>();
|
||
const cameraRef = useRef<CameraView>(null);
|
||
const { ensureLoggedIn } = useAuthGuard();
|
||
const scheme = (useColorScheme() ?? 'light') as keyof typeof Colors;
|
||
const colors = Colors[scheme];
|
||
|
||
const [currentMealType, setCurrentMealType] = useState<MealType>(
|
||
(params.mealType as MealType) || 'dinner'
|
||
);
|
||
const [facing, setFacing] = useState<CameraType>('back');
|
||
const [permission, requestPermission] = useCameraPermissions();
|
||
const [showInstructionModal, setShowInstructionModal] = useState(false);
|
||
const [isCapturing, setIsCapturing] = useState(false);
|
||
|
||
// 餐次选择选项
|
||
const mealOptions = [
|
||
{ key: 'breakfast' as const, label: t('nutritionRecords.mealTypes.breakfast'), icon: '☀️' },
|
||
{ key: 'lunch' as const, label: t('nutritionRecords.mealTypes.lunch'), icon: '🌤️' },
|
||
{ key: 'dinner' as const, label: t('nutritionRecords.mealTypes.dinner'), icon: '🌙' },
|
||
{ key: 'snack' as const, label: t('nutritionRecords.mealTypes.snack'), icon: '🍎' },
|
||
];
|
||
|
||
// 计算固定的相机高度
|
||
const cameraHeight = useMemo(() => {
|
||
const { height: screenHeight } = Dimensions.get('window');
|
||
|
||
// 计算固定占用的高度
|
||
const headerHeight = insets.top + 40; // HeaderBar 高度
|
||
const topMetaHeight = 12 + 28 + 26 + 16 + 6; // topMeta 区域
|
||
const shotsRowHeight = 12 + 88; // MealType 区域
|
||
const bottomBarHeight = 12 + 86 + 10 + Math.max(insets.bottom, 20); // bottomBar 区域
|
||
const margins = 12 + 12; // cameraCard 的上下边距
|
||
|
||
// 可用于相机的高度
|
||
const availableHeight = screenHeight - headerHeight - topMetaHeight - shotsRowHeight - bottomBarHeight - margins;
|
||
|
||
// 确保最小高度为 300,最大不超过屏幕的 55%
|
||
return Math.max(300, Math.min(availableHeight, screenHeight * 0.55));
|
||
}, [insets.top, insets.bottom]);
|
||
|
||
if (!permission) {
|
||
return (
|
||
<View style={styles.container}>
|
||
<LinearGradient colors={['#fefefe', '#f4f7fb']} style={StyleSheet.absoluteFill} />
|
||
<HeaderBar
|
||
title={t('foodCamera.title')}
|
||
onBack={() => router.back()}
|
||
transparent={true}
|
||
/>
|
||
<View style={[styles.loadingContainer, { paddingTop: insets.top + 40 }]}>
|
||
<ActivityIndicator size="large" color={colors.primary} />
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
if (!permission.granted) {
|
||
return (
|
||
<View style={[styles.container, { backgroundColor: '#f8fafc' }]}>
|
||
<HeaderBar
|
||
title={t('foodCamera.title')}
|
||
onBack={() => router.back()}
|
||
transparent
|
||
/>
|
||
<View style={[styles.permissionCard, { marginTop: insets.top + 60 }]}>
|
||
<Ionicons name="camera-outline" size={64} color="#94a3b8" style={{ marginBottom: 20 }} />
|
||
<Text style={styles.permissionTitle}>
|
||
{t('foodCamera.permission.title')}
|
||
</Text>
|
||
<Text style={styles.permissionTip}>
|
||
{t('foodCamera.permission.description')}
|
||
</Text>
|
||
<TouchableOpacity
|
||
style={[styles.permissionBtn, { backgroundColor: colors.primary }]}
|
||
onPress={requestPermission}
|
||
>
|
||
<Text style={styles.permissionBtnText}>
|
||
{t('foodCamera.permission.button')}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
// 切换相机前后摄像头
|
||
function toggleCameraFacing() {
|
||
setFacing(current => (current === 'back' ? 'front' : 'back'));
|
||
}
|
||
|
||
// 拍摄照片
|
||
const takePicture = async () => {
|
||
if (cameraRef.current && !isCapturing) {
|
||
setIsCapturing(true);
|
||
try {
|
||
const photo = await cameraRef.current.takePictureAsync({
|
||
quality: 0.8,
|
||
base64: false,
|
||
});
|
||
|
||
if (photo) {
|
||
console.log('照片拍摄成功:', photo.uri);
|
||
const isLoggedIn = await ensureLoggedIn();
|
||
if (isLoggedIn) {
|
||
router.replace(`/food/food-recognition?imageUri=${encodeURIComponent(photo.uri)}&mealType=${currentMealType}`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('拍照失败:', error);
|
||
Alert.alert(t('foodCamera.alerts.captureFailed.title'), t('foodCamera.alerts.captureFailed.message'));
|
||
} finally {
|
||
setIsCapturing(false);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 从相册选择照片
|
||
const pickImageFromGallery = async () => {
|
||
try {
|
||
const result = await ImagePicker.launchImageLibraryAsync({
|
||
mediaTypes: ['images'],
|
||
allowsEditing: true,
|
||
aspect: [4, 3],
|
||
quality: 0.8,
|
||
});
|
||
|
||
if (!result.canceled && result.assets[0]) {
|
||
const imageUri = result.assets[0].uri;
|
||
console.log('从相册选择的照片:', imageUri);
|
||
const isLoggedIn = await ensureLoggedIn();
|
||
if (isLoggedIn) {
|
||
router.push(`/food/food-recognition?imageUri=${encodeURIComponent(imageUri)}&mealType=${currentMealType}`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('选择照片失败:', error);
|
||
Alert.alert(t('foodCamera.alerts.pickFailed.title'), t('foodCamera.alerts.pickFailed.message'));
|
||
}
|
||
};
|
||
|
||
// 餐次选择
|
||
const handleMealTypeChange = (mealType: MealType) => {
|
||
setCurrentMealType(mealType);
|
||
};
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<LinearGradient colors={['#fefefe', '#f4f7fb']} style={StyleSheet.absoluteFill} />
|
||
|
||
<HeaderBar
|
||
title={t('foodCamera.title')}
|
||
onBack={() => router.back()}
|
||
transparent={true}
|
||
right={
|
||
<TouchableOpacity
|
||
onPress={() => setShowInstructionModal(true)}
|
||
activeOpacity={0.7}
|
||
>
|
||
{isLiquidGlassAvailable() ? (
|
||
<GlassView
|
||
style={styles.infoButton}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(255, 255, 255, 0.3)"
|
||
isInteractive={true}
|
||
>
|
||
<Ionicons name="help-circle-outline" size={24} color="#333" />
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.infoButton, styles.fallbackInfoButton]}>
|
||
<Ionicons name="help-circle-outline" size={24} color="#333" />
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
}
|
||
/>
|
||
|
||
<View style={{ height: insets.top + 40 }} />
|
||
|
||
{/* Top Meta Info */}
|
||
<View style={styles.topMeta}>
|
||
<View style={styles.metaBadge}>
|
||
<Text style={styles.metaBadgeText}>{t('foodCamera.hint')}</Text>
|
||
</View>
|
||
<Text style={styles.metaTitle}>
|
||
{t('nutritionRecords.listTitle')}
|
||
</Text>
|
||
<Text style={styles.metaSubtitle}>
|
||
{t('foodCamera.guide.description')}
|
||
</Text>
|
||
</View>
|
||
|
||
{/* Camera Card */}
|
||
<View style={styles.cameraCard}>
|
||
<View style={[styles.cameraFrame, { height: cameraHeight }]}>
|
||
<CameraView
|
||
ref={cameraRef}
|
||
style={styles.cameraView}
|
||
facing={facing}
|
||
/>
|
||
<LinearGradient
|
||
colors={['transparent', 'rgba(0,0,0,0.1)']}
|
||
style={styles.cameraOverlay}
|
||
/>
|
||
{/* Viewfinder Overlay */}
|
||
<View style={styles.viewfinderOverlay}>
|
||
<View style={[styles.corner, styles.topLeft]} />
|
||
<View style={[styles.corner, styles.topRight]} />
|
||
<View style={[styles.corner, styles.bottomLeft]} />
|
||
<View style={[styles.corner, styles.bottomRight]} />
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Meal Type Selector (Replacing Shots Row) */}
|
||
<View style={styles.shotsRow}>
|
||
{mealOptions.map((option) => {
|
||
const active = currentMealType === option.key;
|
||
return (
|
||
<TouchableOpacity
|
||
key={option.key}
|
||
onPress={() => handleMealTypeChange(option.key)}
|
||
activeOpacity={0.7}
|
||
style={[styles.shotCard, active && styles.shotCardActive]}
|
||
>
|
||
<Text style={styles.mealTypeIcon}>{option.icon}</Text>
|
||
<Text style={[styles.shotLabel, active && styles.shotLabelActive]}>
|
||
{option.label}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
);
|
||
})}
|
||
</View>
|
||
|
||
{/* Bottom Actions */}
|
||
<View style={[styles.bottomBar, { paddingBottom: Math.max(insets.bottom, 20) }]}>
|
||
<View style={styles.bottomActions}>
|
||
{/* Album Button */}
|
||
<TouchableOpacity
|
||
onPress={pickImageFromGallery}
|
||
disabled={isCapturing}
|
||
activeOpacity={0.7}
|
||
>
|
||
{isLiquidGlassAvailable() ? (
|
||
<GlassView
|
||
style={styles.secondaryBtn}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(255, 255, 255, 0.6)"
|
||
isInteractive={true}
|
||
>
|
||
<Ionicons name="images-outline" size={20} color="#0f172a" />
|
||
<Text style={styles.secondaryBtnText}>
|
||
{t('foodCamera.buttons.album')}
|
||
</Text>
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.secondaryBtn, styles.fallbackSecondaryBtn]}>
|
||
<Ionicons name="images-outline" size={20} color="#0f172a" />
|
||
<Text style={styles.secondaryBtnText}>
|
||
{t('foodCamera.buttons.album')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
|
||
{/* Capture Button */}
|
||
<TouchableOpacity
|
||
onPress={takePicture}
|
||
disabled={isCapturing}
|
||
activeOpacity={0.8}
|
||
>
|
||
{isLiquidGlassAvailable() ? (
|
||
<GlassView
|
||
style={styles.captureBtn}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(255, 255, 255, 0.8)"
|
||
isInteractive={true}
|
||
>
|
||
<View style={styles.captureOuterRing}>
|
||
{isCapturing ? (
|
||
<ActivityIndicator color={colors.primary} />
|
||
) : (
|
||
<View style={styles.captureInner} />
|
||
)}
|
||
</View>
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.captureBtn, styles.fallbackCaptureBtn]}>
|
||
<View style={styles.captureOuterRing}>
|
||
{isCapturing ? (
|
||
<ActivityIndicator color={colors.primary} />
|
||
) : (
|
||
<View style={styles.captureInner} />
|
||
)}
|
||
</View>
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
|
||
{/* Flip Button */}
|
||
<TouchableOpacity
|
||
onPress={toggleCameraFacing}
|
||
disabled={isCapturing}
|
||
activeOpacity={0.7}
|
||
>
|
||
{isLiquidGlassAvailable() ? (
|
||
<GlassView
|
||
style={styles.secondaryBtn}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(255, 255, 255, 0.6)"
|
||
isInteractive={true}
|
||
>
|
||
<Ionicons name="camera-reverse-outline" size={20} color="#0f172a" />
|
||
<Text style={styles.secondaryBtnText}>
|
||
{t('foodCamera.buttons.capture')}
|
||
</Text>
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.secondaryBtn, styles.fallbackSecondaryBtn]}>
|
||
<Ionicons name="camera-reverse-outline" size={20} color="#0f172a" />
|
||
<Text style={styles.secondaryBtnText}>
|
||
{t('foodCamera.buttons.capture')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Instruction Modal */}
|
||
<Modal
|
||
visible={showInstructionModal}
|
||
animationType="fade"
|
||
transparent={true}
|
||
onRequestClose={() => setShowInstructionModal(false)}
|
||
>
|
||
<View style={styles.modalOverlay}>
|
||
<View style={styles.instructionModal}>
|
||
<Text style={styles.instructionTitle}>{t('foodCamera.guide.title')}</Text>
|
||
|
||
<View style={styles.exampleContainer}>
|
||
{/* Good Example */}
|
||
<View style={styles.exampleItem}>
|
||
<View style={styles.exampleImagePlaceholder}>
|
||
<View style={styles.checkmarkContainer}>
|
||
<Ionicons name="checkmark" size={20} color="#FFF" />
|
||
</View>
|
||
<Image
|
||
style={styles.exampleImage}
|
||
source={{ uri: 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/function/food-right.jpeg' }}
|
||
contentFit="cover"
|
||
cachePolicy={'memory-disk'}
|
||
/>
|
||
</View>
|
||
<Text style={styles.exampleText}>{t('foodCamera.guide.good')}</Text>
|
||
</View>
|
||
|
||
{/* Bad Example */}
|
||
<View style={styles.exampleItem}>
|
||
<View style={styles.exampleImagePlaceholder}>
|
||
<View style={styles.crossContainer}>
|
||
<Ionicons name="close" size={20} color="#FFF" />
|
||
</View>
|
||
<Image
|
||
style={styles.exampleImage}
|
||
source={{ uri: 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/function/food-wrong.jpeg' }}
|
||
contentFit="cover"
|
||
cachePolicy={'memory-disk'}
|
||
/>
|
||
</View>
|
||
<Text style={styles.exampleText}>{t('foodCamera.guide.bad')}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<Text style={styles.instructionDescription}>
|
||
{t('foodCamera.guide.description')}
|
||
</Text>
|
||
|
||
<TouchableOpacity
|
||
style={styles.knowButton}
|
||
onPress={() => setShowInstructionModal(false)}
|
||
>
|
||
<Text style={styles.knowButtonText}>{t('foodCamera.guide.button')}</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
</Modal>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
},
|
||
loadingContainer: {
|
||
flex: 1,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
topMeta: {
|
||
paddingHorizontal: 20,
|
||
paddingTop: 12,
|
||
gap: 6,
|
||
},
|
||
metaBadge: {
|
||
alignSelf: 'flex-start',
|
||
backgroundColor: '#e0f2fe',
|
||
paddingHorizontal: 10,
|
||
paddingVertical: 6,
|
||
borderRadius: 14,
|
||
},
|
||
metaBadgeText: {
|
||
color: '#0369a1',
|
||
fontWeight: '700',
|
||
fontSize: 12,
|
||
},
|
||
metaTitle: {
|
||
fontSize: 22,
|
||
fontWeight: '700',
|
||
color: '#0f172a',
|
||
},
|
||
metaSubtitle: {
|
||
fontSize: 14,
|
||
color: '#475569',
|
||
},
|
||
cameraCard: {
|
||
marginHorizontal: 20,
|
||
marginTop: 12,
|
||
borderRadius: 24,
|
||
overflow: 'hidden',
|
||
shadowColor: '#0f172a',
|
||
shadowOpacity: 0.12,
|
||
shadowRadius: 18,
|
||
shadowOffset: { width: 0, height: 10 },
|
||
},
|
||
cameraFrame: {
|
||
borderRadius: 24,
|
||
overflow: 'hidden',
|
||
backgroundColor: '#0b172a',
|
||
position: 'relative',
|
||
},
|
||
cameraView: {
|
||
flex: 1,
|
||
},
|
||
cameraOverlay: {
|
||
position: 'absolute',
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
height: 80,
|
||
},
|
||
viewfinderOverlay: {
|
||
...StyleSheet.absoluteFillObject,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
margin: 20,
|
||
},
|
||
corner: {
|
||
position: 'absolute',
|
||
width: 30,
|
||
height: 30,
|
||
borderColor: 'rgba(255, 255, 255, 0.6)',
|
||
borderWidth: 4,
|
||
borderRadius: 2,
|
||
},
|
||
topLeft: {
|
||
top: 0,
|
||
left: 0,
|
||
borderRightWidth: 0,
|
||
borderBottomWidth: 0,
|
||
},
|
||
topRight: {
|
||
top: 0,
|
||
right: 0,
|
||
borderLeftWidth: 0,
|
||
borderBottomWidth: 0,
|
||
},
|
||
bottomLeft: {
|
||
bottom: 0,
|
||
left: 0,
|
||
borderRightWidth: 0,
|
||
borderTopWidth: 0,
|
||
},
|
||
bottomRight: {
|
||
bottom: 0,
|
||
right: 0,
|
||
borderLeftWidth: 0,
|
||
borderTopWidth: 0,
|
||
},
|
||
shotsRow: {
|
||
flexDirection: 'row',
|
||
paddingHorizontal: 20,
|
||
paddingTop: 12,
|
||
gap: 8,
|
||
justifyContent: 'space-between',
|
||
},
|
||
shotCard: {
|
||
flex: 1,
|
||
borderRadius: 14,
|
||
backgroundColor: '#f8fafc',
|
||
paddingVertical: 12,
|
||
gap: 6,
|
||
borderWidth: 1,
|
||
borderColor: '#e2e8f0',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
shotCardActive: {
|
||
borderColor: '#38bdf8',
|
||
backgroundColor: '#ecfeff',
|
||
},
|
||
mealTypeIcon: {
|
||
fontSize: 20,
|
||
},
|
||
shotLabel: {
|
||
fontSize: 12,
|
||
color: '#475569',
|
||
fontWeight: '600',
|
||
},
|
||
shotLabelActive: {
|
||
color: '#0ea5e9',
|
||
},
|
||
bottomBar: {
|
||
paddingHorizontal: 20,
|
||
paddingTop: 12,
|
||
gap: 10,
|
||
},
|
||
bottomActions: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
},
|
||
captureBtn: {
|
||
width: 72,
|
||
height: 72,
|
||
borderRadius: 36,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
overflow: 'hidden',
|
||
shadowColor: '#0ea5e9',
|
||
shadowOpacity: 0.25,
|
||
shadowRadius: 12,
|
||
shadowOffset: { width: 0, height: 6 },
|
||
},
|
||
fallbackCaptureBtn: {
|
||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||
borderWidth: 2,
|
||
borderColor: 'rgba(14, 165, 233, 0.2)',
|
||
},
|
||
captureOuterRing: {
|
||
width: 60,
|
||
height: 60,
|
||
borderRadius: 30,
|
||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
captureInner: {
|
||
width: 48,
|
||
height: 48,
|
||
borderRadius: 24,
|
||
backgroundColor: '#fff',
|
||
shadowColor: '#0ea5e9',
|
||
shadowOpacity: 0.4,
|
||
shadowRadius: 6,
|
||
shadowOffset: { width: 0, height: 2 },
|
||
},
|
||
secondaryBtn: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 6,
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 12,
|
||
borderRadius: 16,
|
||
overflow: 'hidden',
|
||
shadowColor: '#0f172a',
|
||
shadowOpacity: 0.08,
|
||
shadowRadius: 8,
|
||
shadowOffset: { width: 0, height: 4 },
|
||
minWidth: 88,
|
||
justifyContent: 'center',
|
||
},
|
||
fallbackSecondaryBtn: {
|
||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(15, 23, 42, 0.1)',
|
||
},
|
||
secondaryBtnText: {
|
||
color: '#0f172a',
|
||
fontWeight: '600',
|
||
fontSize: 14,
|
||
},
|
||
infoButton: {
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: 20,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
overflow: 'hidden',
|
||
},
|
||
fallbackInfoButton: {
|
||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
||
},
|
||
permissionCard: {
|
||
marginHorizontal: 24,
|
||
borderRadius: 18,
|
||
padding: 24,
|
||
backgroundColor: '#fff',
|
||
shadowColor: '#0f172a',
|
||
shadowOpacity: 0.08,
|
||
shadowRadius: 12,
|
||
shadowOffset: { width: 0, height: 10 },
|
||
alignItems: 'center',
|
||
gap: 10,
|
||
},
|
||
permissionTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '700',
|
||
color: '#0f172a',
|
||
marginBottom: 4,
|
||
},
|
||
permissionTip: {
|
||
fontSize: 14,
|
||
color: '#475569',
|
||
textAlign: 'center',
|
||
lineHeight: 20,
|
||
marginBottom: 16,
|
||
},
|
||
permissionBtn: {
|
||
borderRadius: 14,
|
||
paddingHorizontal: 24,
|
||
paddingVertical: 14,
|
||
width: '100%',
|
||
alignItems: 'center',
|
||
},
|
||
permissionBtnText: {
|
||
color: '#fff',
|
||
fontWeight: '700',
|
||
fontSize: 16,
|
||
},
|
||
modalOverlay: {
|
||
flex: 1,
|
||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||
justifyContent: 'center',
|
||
paddingHorizontal: 20,
|
||
},
|
||
instructionModal: {
|
||
backgroundColor: '#FFF',
|
||
borderRadius: 24,
|
||
padding: 24,
|
||
alignItems: 'center',
|
||
},
|
||
instructionTitle: {
|
||
fontSize: 20,
|
||
fontWeight: '700',
|
||
color: '#0f172a',
|
||
marginBottom: 24,
|
||
},
|
||
exampleContainer: {
|
||
flexDirection: 'row',
|
||
gap: 16,
|
||
marginBottom: 24,
|
||
},
|
||
exampleItem: {
|
||
flex: 1,
|
||
alignItems: 'center',
|
||
gap: 8,
|
||
},
|
||
exampleImagePlaceholder: {
|
||
width: '100%',
|
||
aspectRatio: 1,
|
||
backgroundColor: '#F1F5F9',
|
||
borderRadius: 16,
|
||
overflow: 'hidden',
|
||
position: 'relative',
|
||
},
|
||
exampleImage: {
|
||
width: '100%',
|
||
height: '100%',
|
||
},
|
||
exampleText: {
|
||
fontSize: 13,
|
||
color: '#64748b',
|
||
fontWeight: '500',
|
||
},
|
||
checkmarkContainer: {
|
||
position: 'absolute',
|
||
top: 8,
|
||
right: 8,
|
||
width: 24,
|
||
height: 24,
|
||
borderRadius: 12,
|
||
backgroundColor: '#22c55e',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
zIndex: 10,
|
||
},
|
||
crossContainer: {
|
||
position: 'absolute',
|
||
top: 8,
|
||
right: 8,
|
||
width: 24,
|
||
height: 24,
|
||
borderRadius: 12,
|
||
backgroundColor: '#ef4444',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
zIndex: 10,
|
||
},
|
||
instructionDescription: {
|
||
fontSize: 15,
|
||
textAlign: 'center',
|
||
color: '#334155',
|
||
marginBottom: 24,
|
||
lineHeight: 22,
|
||
},
|
||
knowButton: {
|
||
backgroundColor: '#0f172a',
|
||
borderRadius: 16,
|
||
paddingVertical: 14,
|
||
paddingHorizontal: 32,
|
||
width: '100%',
|
||
},
|
||
knowButtonText: {
|
||
color: '#FFF',
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
textAlign: 'center',
|
||
},
|
||
}); |