import { ThemedText } from '@/components/ThemedText'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { IconSymbol } from '@/components/ui/IconSymbol'; import { Colors } from '@/constants/Colors'; import { DOSAGE_UNITS, FORM_OPTIONS } from '@/constants/Medication'; import { useAppDispatch } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useCosUpload } from '@/hooks/useCosUpload'; import { createMedicationAction, fetchMedicationRecords, fetchMedications } from '@/store/medicationsSlice'; import type { MedicationForm, RepeatPattern } from '@/types/medication'; import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'; import DateTimePicker from '@react-native-community/datetimepicker'; import { Picker } from '@react-native-picker/picker'; import Voice from '@react-native-voice/voice'; import dayjs from 'dayjs'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { Image } from 'expo-image'; import * as ImagePicker from 'expo-image-picker'; import { router } from 'expo-router'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Alert, KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, StyleSheet, TextInput, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; type ThemeColors = typeof Colors.light | typeof Colors.dark; const HEX_COLOR_REGEX = /^#([0-9a-f]{6})$/i; const withAlpha = (hex: string, alpha: number) => { if (!HEX_COLOR_REGEX.test(hex)) { return hex; } const normalized = hex.replace('#', ''); const r = parseInt(normalized.slice(0, 2), 16); const g = parseInt(normalized.slice(2, 4), 16); const b = parseInt(normalized.slice(4, 6), 16); const safeAlpha = Math.min(Math.max(alpha, 0), 1); return `rgba(${r}, ${g}, ${b}, ${safeAlpha})`; }; // 表单数据接口(用于内部状态管理) interface AddMedicationFormData { name: string; photoUrl?: string | null; form: MedicationForm; dosageValue: string; dosageUnit: string; timesPerDay: number; medicationTimes: string[]; startDate: string; note: string; } const TIMES_PER_DAY_OPTIONS = Array.from({ length: 10 }, (_, index) => index + 1); const STEP_TITLES = ['药品名称', '剂型与剂量', '服药频率', '服药时间', '备注']; const STEP_DESCRIPTIONS = [ '为药物命名并上传包装照片,方便识别', '选择药片类型并填写每次的用药剂量', '设置用药频率以及每日次数', '添加并管理每天的提醒时间', '填写备注或医生叮嘱(可选)', ]; const DEFAULT_TIME_PRESETS = ['08:00', '12:00', '18:00', '22:00']; const formatTime = (date: Date) => dayjs(date).format('HH:mm'); const getDefaultTimeByIndex = (index: number) => DEFAULT_TIME_PRESETS[index % DEFAULT_TIME_PRESETS.length]; const createDateFromTime = (time: string) => { try { if (!time || typeof time !== 'string') { console.warn('[MEDICATION] Invalid time string provided:', time); return new Date(); } const parts = time.split(':'); if (parts.length !== 2) { console.warn('[MEDICATION] Invalid time format:', time); return new Date(); } const hour = parseInt(parts[0], 10); const minute = parseInt(parts[1], 10); if (isNaN(hour) || isNaN(minute) || hour < 0 || hour > 23 || minute < 0 || minute > 59) { console.warn('[MEDICATION] Invalid time values:', { hour, minute }); return new Date(); } const next = new Date(); next.setHours(hour, minute, 0, 0); // 验证日期是否有效 if (isNaN(next.getTime())) { console.error('[MEDICATION] Failed to create valid date'); return new Date(); } return next; } catch (error) { console.error('[MEDICATION] Error in createDateFromTime:', error); return new Date(); } }; export default function AddMedicationScreen() { const dispatch = useAppDispatch(); const insets = useSafeAreaInsets(); const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colors: ThemeColors = Colors[theme]; const glassAvailable = isLiquidGlassAvailable(); const totalSteps = STEP_TITLES.length; const [currentStep, setCurrentStep] = useState(0); const [medicationName, setMedicationName] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const { upload, uploading } = useCosUpload({ prefix: 'images/medications' }); // 获取登录验证相关的功能 const { ensureLoggedIn } = useAuthGuard(); const softBorderColor = useMemo(() => withAlpha(colors.border, 0.45), [colors.border]); const fadedBorderFill = useMemo(() => withAlpha(colors.border, 0.2), [colors.border]); const glassPrimaryTint = useMemo(() => withAlpha(colors.primary, theme === 'dark' ? 0.55 : 0.45), [colors.primary, theme]); const glassDisabledTint = useMemo(() => withAlpha(colors.border, theme === 'dark' ? 0.45 : 0.6), [colors.border, theme]); const glassPrimaryBackground = useMemo(() => withAlpha(colors.primary, theme === 'dark' ? 0.35 : 0.7), [colors.primary, theme]); const glassDisabledBackground = useMemo(() => withAlpha(colors.border, theme === 'dark' ? 0.35 : 0.5), [colors.border, theme]); const [photoPreview, setPhotoPreview] = useState(null); const [photoUrl, setPhotoUrl] = useState(null); const [selectedForm, setSelectedForm] = useState('capsule'); const [dosageValue, setDosageValue] = useState('1'); const [dosageUnit, setDosageUnit] = useState(DOSAGE_UNITS[0]); const [unitPickerVisible, setUnitPickerVisible] = useState(false); const [unitPickerValue, setUnitPickerValue] = useState(DOSAGE_UNITS[0]); const [timesPerDay, setTimesPerDay] = useState(1); const [timesPickerVisible, setTimesPickerVisible] = useState(false); const [timesPickerValue, setTimesPickerValue] = useState(1); const [startDate, setStartDate] = useState(new Date()); const [datePickerVisible, setDatePickerVisible] = useState(false); const [datePickerValue, setDatePickerValue] = useState(new Date()); const [medicationTimes, setMedicationTimes] = useState([DEFAULT_TIME_PRESETS[0]]); const [timePickerVisible, setTimePickerVisible] = useState(false); const [timePickerDate, setTimePickerDate] = useState(createDateFromTime(DEFAULT_TIME_PRESETS[0])); const [editingTimeIndex, setEditingTimeIndex] = useState(null); const [note, setNote] = useState(''); const [dictationActive, setDictationActive] = useState(false); const [dictationLoading, setDictationLoading] = useState(false); const isDictationSupported = Platform.OS === 'ios'; useEffect(() => { setMedicationTimes((prev) => { if (timesPerDay > prev.length) { const next = [...prev]; while (next.length < timesPerDay) { next.push(getDefaultTimeByIndex(next.length)); } return next; } if (timesPerDay < prev.length) { return prev.slice(0, timesPerDay); } return prev; }); }, [timesPerDay]); const appendDictationResult = useCallback( (text: string) => { const clean = text.trim(); if (!clean) return; setNote((prev) => { if (!prev) { return clean; } return `${prev}${prev.endsWith('\n') ? '' : '\n'}${clean}`; }); }, [setNote] ); const stepTitle = STEP_TITLES[currentStep] ?? STEP_TITLES[0]; const stepDescription = STEP_DESCRIPTIONS[currentStep] ?? ''; const canProceed = useMemo(() => { switch (currentStep) { case 0: return medicationName.trim().length > 0; case 1: return Number(dosageValue) > 0 && !!dosageUnit && !!selectedForm; case 2: return timesPerDay > 0; case 3: return medicationTimes.length > 0; default: return true; } }, [currentStep, dosageUnit, dosageValue, medicationName, medicationTimes.length, selectedForm, timesPerDay]); useEffect(() => { if (!isDictationSupported) { return; } Voice.onSpeechStart = () => { setDictationActive(true); setDictationLoading(false); }; Voice.onSpeechEnd = () => { setDictationActive(false); setDictationLoading(false); }; Voice.onSpeechResults = (event: any) => { const recognized = event?.value?.[0]; if (recognized) { appendDictationResult(recognized); } }; Voice.onSpeechError = (error: any) => { console.log('[MEDICATION] voice error', error); setDictationActive(false); setDictationLoading(false); Alert.alert('语音识别不可用', '无法使用语音输入,请检查权限设置后重试'); }; return () => { Voice.destroy() .then(() => { Voice.removeAllListeners(); }) .catch(() => {}); }; }, [appendDictationResult, isDictationSupported]); const handleNext = useCallback(async () => { if (!canProceed) return; // 如果不是最后一步,继续下一步 if (currentStep < totalSteps - 1) { setCurrentStep((prev) => Math.min(prev + 1, totalSteps - 1)); return; } // 最后一步:提交药物数据 setIsSubmitting(true); try { // 先检查用户是否已登录,如果未登录则跳转到登录页面 const isLoggedIn = await ensureLoggedIn({ shouldBack: true }); if (!isLoggedIn) { // 未登录,ensureLoggedIn 已处理跳转,直接返回 setIsSubmitting(false); return; } // 构建药物数据,符合 CreateMedicationDto 接口 const medicationData = { name: medicationName.trim(), photoUrl: photoUrl || undefined, form: selectedForm, dosageValue: Number(dosageValue), dosageUnit: dosageUnit, timesPerDay: timesPerDay, medicationTimes: medicationTimes, startDate: dayjs(startDate).startOf('day').toISOString(), // ISO 8601 格式 repeatPattern: 'daily' as RepeatPattern, note: note.trim() || undefined, }; // 调用 Redux action 创建药物 const result = await dispatch(createMedicationAction(medicationData)).unwrap(); // 刷新药物列表和记录数据,确保返回主界面能看到新数据 await dispatch(fetchMedications({ isActive: true })); // 获取今天的记录,确保新添加的药物记录能显示 const today = dayjs().format('YYYY-MM-DD'); await dispatch(fetchMedicationRecords({ date: today })); // 成功提示 Alert.alert( '添加成功', `已成功添加药物"${medicationName}"`, [ { text: '确定', onPress: () => router.back(), }, ] ); } catch (error) { console.error('[MEDICATION] 创建药物失败', error); Alert.alert( '添加失败', error instanceof Error ? error.message : '创建药物时发生错误,请稍后重试', [{ text: '确定' }] ); } finally { setIsSubmitting(false); } }, [ canProceed, currentStep, totalSteps, medicationName, photoUrl, selectedForm, dosageValue, dosageUnit, medicationTimes, startDate, note, dispatch, ensureLoggedIn, ]); const handlePrev = useCallback(() => { if (currentStep === 0) return; setCurrentStep((prev) => Math.max(prev - 1, 0)); }, [currentStep]); const handleDictationPress = useCallback(async () => { if (!isDictationSupported || dictationLoading) { return; } try { if (dictationActive) { setDictationLoading(true); await Voice.stop(); setDictationLoading(false); return; } setDictationLoading(true); try { await Voice.stop(); } catch { // no-op: safe to ignore if not already recording } await Voice.start('zh-CN'); } catch (error) { console.log('[MEDICATION] unable to start dictation', error); setDictationLoading(false); Alert.alert('无法启动语音输入', '请检查麦克风与语音识别权限后重试'); } }, [dictationActive, dictationLoading, isDictationSupported]); const handleTakePhoto = useCallback(async () => { try { const permission = await ImagePicker.requestCameraPermissionsAsync(); if (permission.status !== 'granted') { Alert.alert('权限不足', '需要相机权限以拍摄药品照片'); return; } const result = await ImagePicker.launchCameraAsync({ allowsEditing: true, mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.9, }); if (result.canceled || !result.assets?.length) { return; } const asset = result.assets[0]; setPhotoPreview(asset.uri); setPhotoUrl(null); try { const { url } = await upload( { uri: asset.uri, name: asset.fileName ?? `medication-${Date.now()}.jpg`, type: asset.mimeType ?? 'image/jpeg' }, { prefix: 'images/medications' } ); setPhotoUrl(url); } catch (uploadError) { console.error('[MEDICATION] 图片上传失败', uploadError); Alert.alert('上传失败', '图片上传失败,请稍后重试'); } } catch (error) { console.error('[MEDICATION] 拍照失败', error); Alert.alert('拍照失败', '无法打开相机,请稍后再试'); } }, [upload]); const handleRemovePhoto = useCallback(() => { setPhotoPreview(null); setPhotoUrl(null); }, []); const openDatePicker = useCallback(() => { setDatePickerValue(startDate); setDatePickerVisible(true); }, [startDate]); const confirmStartDate = useCallback((date: Date) => { setStartDate(date); setDatePickerVisible(false); }, []); const openTimePicker = useCallback( (index?: number) => { try { if (typeof index === 'number') { if (index >= 0 && index < medicationTimes.length) { setEditingTimeIndex(index); setTimePickerDate(createDateFromTime(medicationTimes[index])); } else { console.error('[MEDICATION] Invalid time index:', index); return; } } else { setEditingTimeIndex(null); setTimePickerDate(createDateFromTime(getDefaultTimeByIndex(medicationTimes.length))); } setTimePickerVisible(true); } catch (error) { console.error('[MEDICATION] Error in openTimePicker:', error); } }, [medicationTimes] ); const confirmTime = useCallback( (date: Date) => { try { if (!date || isNaN(date.getTime())) { console.error('[MEDICATION] Invalid date provided to confirmTime'); setTimePickerVisible(false); setEditingTimeIndex(null); return; } const nextValue = formatTime(date); setMedicationTimes((prev) => { if (editingTimeIndex == null) { return [...prev, nextValue]; } return prev.map((time, idx) => (idx === editingTimeIndex ? nextValue : time)); }); setTimePickerVisible(false); setEditingTimeIndex(null); } catch (error) { console.error('[MEDICATION] Error in confirmTime:', error); setTimePickerVisible(false); setEditingTimeIndex(null); } }, [editingTimeIndex] ); const removeTime = useCallback((index: number) => { setMedicationTimes((prev) => { if (prev.length === 1) { return prev; } return prev.filter((_, idx) => idx !== index); }); }, []); useEffect(() => { setUnitPickerVisible(false); setTimesPickerVisible(false); }, [currentStep]); const openUnitPicker = useCallback(() => { setUnitPickerValue(dosageUnit); setUnitPickerVisible(true); }, [dosageUnit]); const closeUnitPicker = useCallback(() => { setUnitPickerVisible(false); }, []); const confirmUnitPicker = useCallback(() => { setDosageUnit(unitPickerValue); setUnitPickerVisible(false); }, [unitPickerValue]); const openTimesPicker = useCallback(() => { setTimesPickerValue(timesPerDay); setTimesPickerVisible(true); }, [timesPerDay]); const closeTimesPicker = useCallback(() => { setTimesPickerVisible(false); }, []); const confirmTimesPicker = useCallback(() => { setTimesPerDay(timesPickerValue); setTimesPickerVisible(false); }, [timesPickerValue]); const renderStepContent = () => { switch (currentStep) { case 0: return ( {photoPreview ? ( <> {uploading ? '上传中…' : '重新拍摄'} ) : ( 拍照上传药品图片 辅助识别药品包装,更易区分 )} {uploading && ( 正在上传 )} ); case 1: return ( 每次剂量 {dosageUnit} 类型 {FORM_OPTIONS.map((option) => { const active = selectedForm === option.id; return ( setSelectedForm(option.id)} activeOpacity={0.9} > {option.label} ); })} ); case 2: return ( 每日次数 {`${timesPerDay} 次/日`} ); case 3: return ( 每日提醒时间 {medicationTimes.map((time, index) => ( openTimePicker(index)}> {time} removeTime(index)} disabled={medicationTimes.length === 1} hitSlop={12}> ))} openTimePicker()}> 添加时间 ); case 4: return ( 备注 {isDictationSupported && ( {dictationLoading ? ( ) : ( )} )} ); default: return null; } }; const showDateField = currentStep === 2; return ( router.back()} withSafeTop={false} transparent variant="elevated" /> {Array.from({ length: totalSteps }).map((_, index) => { const isActive = index <= currentStep; return ( ); })} {stepTitle} {stepDescription} {renderStepContent()} {showDateField && ( 开始日期 {dayjs(startDate).format('YYYY 年 MM 月 DD 日')} )} {currentStep > 0 && ( 上一步 )} {glassAvailable ? ( {isSubmitting ? ( ) : ( {currentStep === totalSteps - 1 ? '完成' : '下一步'} )} ) : ( {isSubmitting ? ( ) : ( {currentStep === totalSteps - 1 ? '完成' : '下一步'} )} )} setDatePickerVisible(false)} > setDatePickerVisible(false)} /> { if (Platform.OS === 'ios') { if (date) setDatePickerValue(date); } else { if (event.type === 'set' && date) { confirmStartDate(date); } else { setDatePickerVisible(false); } } }} /> {Platform.OS === 'ios' && ( setDatePickerVisible(false)} style={[styles.modalBtn, { borderColor: softBorderColor }]} > 取消 confirmStartDate(datePickerValue)} style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colors.primary }]} > 确定 )} { setTimePickerVisible(false); setEditingTimeIndex(null); }} > { setTimePickerVisible(false); setEditingTimeIndex(null); }} /> {editingTimeIndex !== null ? '修改提醒时间' : '添加提醒时间'} { if (Platform.OS === 'ios') { if (date) setTimePickerDate(date); } else { if (event.type === 'set' && date) { confirmTime(date); } else { setTimePickerVisible(false); setEditingTimeIndex(null); } } }} /> {Platform.OS === 'ios' && ( { setTimePickerVisible(false); setEditingTimeIndex(null); }} style={[styles.modalBtn, { borderColor: softBorderColor }]} > 取消 confirmTime(timePickerDate)} style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colors.primary }]} > 确定 )} 选择每日次数 setTimesPickerValue(Number(value))} itemStyle={styles.unitPickerItem} style={styles.unitPicker} > {TIMES_PER_DAY_OPTIONS.map((count) => ( ))} 取消 确定 选择剂量单位 setUnitPickerValue(String(value))} itemStyle={styles.unitPickerItem} style={styles.unitPicker} > {DOSAGE_UNITS.map((unit) => ( ))} 取消 确定 ); } const styles = StyleSheet.create({ screen: { flex: 1, }, flex: { flex: 1, }, pageContent: { gap: 24, }, formSurface: { paddingHorizontal: 24, gap: 20, width: '100%', }, stepIndicator: { flexDirection: 'row', gap: 12, alignItems: 'center', }, stepSegment: { flex: 1, height: 4, borderRadius: 2, }, titleBlock: { gap: 6, }, modalTitle: { fontSize: 22, fontWeight: '600', }, modalSubtitle: { fontSize: 14, lineHeight: 20, }, contentContainer: { paddingBottom: 16, gap: 20, }, stepSection: { gap: 20, }, searchField: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, borderRadius: 16, borderWidth: 1, height: 56, gap: 12, }, searchInput: { flex: 1, fontSize: 16, paddingVertical: 0, }, photoCard: { borderWidth: 1, borderRadius: 20, height: 180, overflow: 'hidden', justifyContent: 'center', }, photoPlaceholder: { alignItems: 'center', justifyContent: 'center', gap: 10, paddingHorizontal: 20, }, photoIconBadge: { width: 48, height: 48, borderRadius: 24, alignItems: 'center', justifyContent: 'center', }, photoTitle: { fontSize: 16, fontWeight: '600', }, photoSubtitle: { fontSize: 13, textAlign: 'center', }, photoPreview: { width: '100%', height: '100%', }, photoOverlay: { position: 'absolute', right: 16, top: 16, flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 12, paddingVertical: 8, backgroundColor: 'rgba(15, 23, 42, 0.55)', borderRadius: 999, }, photoOverlayText: { color: '#fff', fontSize: 13, fontWeight: '600', }, photoUploadingIndicator: { position: 'absolute', bottom: 16, left: 16, flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 12, paddingVertical: 8, backgroundColor: 'rgba(15, 23, 42, 0.6)', borderRadius: 999, }, uploadingText: { fontSize: 12, fontWeight: '600', }, photoRemoveBtn: { position: 'absolute', right: 12, bottom: 12, width: 28, height: 28, borderRadius: 14, backgroundColor: 'rgba(255,255,255,0.85)', alignItems: 'center', justifyContent: 'center', }, inputGroup: { gap: 12, }, groupLabel: { fontSize: 14, fontWeight: '600', letterSpacing: 0.2, }, dosageField: { borderWidth: 1, borderRadius: 16, paddingHorizontal: 16, paddingVertical: 12, position: 'relative', overflow: 'visible', }, dosageInput: { fontSize: 28, fontWeight: '600', flex: 1, paddingVertical: 0, }, dosageRow: { flexDirection: 'row', alignItems: 'center', gap: 12, }, unitSelector: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 12, paddingVertical: 6, borderRadius: 12, }, unitSelectorText: { fontSize: 14, fontWeight: '600', }, unitPicker: { width: '100%', }, unitPickerItem: { fontSize: 16, }, formGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, }, formOption: { flexBasis: '30%', borderWidth: 1, borderRadius: 16, paddingVertical: 6, alignItems: 'center', justifyContent: 'center', }, formIconBadge: { width: 28, height: 28, borderRadius: 16, alignItems: 'center', justifyContent: 'center', }, formLabel: { fontSize: 11, fontWeight: '600', }, frequencyRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderWidth: 1, borderRadius: 16, paddingHorizontal: 16, paddingVertical: 14, }, frequencyValue: { fontSize: 18, fontWeight: '600', }, stepperBtn: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', }, frequencyLabel: { fontSize: 16, fontWeight: '600', }, unitSwitch: { flexDirection: 'row', gap: 12, marginTop: 10, }, unitOption: { flex: 1, paddingVertical: 12, borderWidth: 1, borderRadius: 14, alignItems: 'center', }, timeList: { gap: 12, }, timeItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderWidth: 1, borderRadius: 16, paddingHorizontal: 16, paddingVertical: 12, }, timeValue: { flexDirection: 'row', alignItems: 'center', gap: 8, }, timeText: { fontSize: 18, fontWeight: '600', }, addTimeButton: { borderWidth: 1, borderRadius: 16, paddingVertical: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, }, addTimeLabel: { fontSize: 14, fontWeight: '600', }, noteInput: { borderWidth: 1, borderRadius: 20, padding: 16, paddingRight: 72, paddingBottom: 56, minHeight: 140, textAlignVertical: 'top', fontSize: 15, lineHeight: 22, }, noteInputWrapper: { position: 'relative', }, noteVoiceButton: { position: 'absolute', right: 16, bottom: 16, width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center', borderWidth: 1, }, footer: { gap: 12, }, startDateRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderWidth: 1, borderRadius: 18, paddingHorizontal: 14, paddingVertical: 12, }, startDateLeft: { flexDirection: 'row', alignItems: 'center', gap: 12, }, startDateLabel: { fontSize: 12, }, startDateValue: { fontSize: 16, fontWeight: '600', }, footerButtons: { flexDirection: 'row', gap: 12, alignItems: 'center', }, secondaryBtn: { paddingVertical: 14, paddingHorizontal: 20, borderRadius: 18, borderWidth: 1, borderColor: '#E2E8F0', }, secondaryBtnText: { fontSize: 15, fontWeight: '600', color: '#475569', }, primaryBtn: { flex: 1, paddingVertical: 16, borderRadius: 18, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, primaryBtnWrapper: { flex: 1, }, primaryBtnWrapperDisabled: { opacity: 0.6, }, glassPrimaryBtn: { borderWidth: 1, borderColor: 'rgba(255,255,255,0.2)', }, primaryBtnFallback: { justifyContent: 'center', }, primaryBtnText: { fontSize: 16, fontWeight: '700', }, pickerBackdrop: { flex: 1, backgroundColor: 'rgba(15, 23, 42, 0.4)', }, pickerSheet: { position: 'absolute', left: 20, right: 20, bottom: 40, borderRadius: 24, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 10 }, shadowOpacity: 0.2, shadowRadius: 20, elevation: 8, }, modalActions: { flexDirection: 'row', justifyContent: 'space-between', gap: 12, marginTop: 16, }, modalBtn: { flex: 1, borderRadius: 16, paddingVertical: 12, alignItems: 'center', borderWidth: 1, borderColor: '#E2E8F0', }, modalBtnPrimary: { backgroundColor: '#4F46E5', borderColor: 'transparent', }, modalBtnText: { fontSize: 15, fontWeight: '600', color: '#475569', }, modalBtnTextPrimary: { color: '#fff', }, });