import { ThemedText } from '@/components/ThemedText'; import { ConfirmationSheet } from '@/components/ui/ConfirmationSheet'; import { HeaderBar } from '@/components/ui/HeaderBar'; import InfoCard from '@/components/ui/InfoCard'; import { Colors } from '@/constants/Colors'; import { DOSAGE_UNITS, DOSAGE_VALUES, FORM_LABELS, FORM_OPTIONS } from '@/constants/Medication'; import { ROUTES } from '@/constants/Routes'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { medicationNotificationService } from '@/services/medicationNotifications'; import { getMedicationById, getMedicationRecords } from '@/services/medications'; import { deleteMedicationAction, fetchMedications, selectMedications, updateMedicationAction, } from '@/store/medicationsSlice'; import type { Medication, MedicationForm } from '@/types/medication'; import { Ionicons } from '@expo/vector-icons'; 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 { LinearGradient } from 'expo-linear-gradient'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Alert, Keyboard, Modal, Platform, Pressable, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import ImageViewing from 'react-native-image-viewing'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const DEFAULT_IMAGE = require('@/assets/images/medicine/image-medicine.png'); type RecordsSummary = { takenCount: number; startedDays: number | null; }; export default function MedicationDetailScreen() { const params = useLocalSearchParams<{ medicationId?: string }>(); const medicationId = Array.isArray(params.medicationId) ? params.medicationId[0] : params.medicationId; const dispatch = useAppDispatch(); const scheme = (useColorScheme() ?? 'light') as keyof typeof Colors; const colors = Colors[scheme]; const insets = useSafeAreaInsets(); const router = useRouter(); const medications = useAppSelector(selectMedications); const medicationFromStore = medications.find((item) => item.id === medicationId); const [medication, setMedication] = useState(medicationFromStore ?? null); const [loading, setLoading] = useState(!medicationFromStore); const [summary, setSummary] = useState({ takenCount: 0, startedDays: null, }); const [summaryLoading, setSummaryLoading] = useState(true); const [updatePending, setUpdatePending] = useState(false); const [error, setError] = useState(null); const [noteModalVisible, setNoteModalVisible] = useState(false); const [noteDraft, setNoteDraft] = useState(medication?.note ?? ''); const [noteSaving, setNoteSaving] = useState(false); const [dictationActive, setDictationActive] = useState(false); const [dictationLoading, setDictationLoading] = useState(false); const isDictationSupported = Platform.OS === 'ios'; const [keyboardHeight, setKeyboardHeight] = useState(0); const [deleteSheetVisible, setDeleteSheetVisible] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); const [deactivateSheetVisible, setDeactivateSheetVisible] = useState(false); const [deactivateLoading, setDeactivateLoading] = useState(false); const [showImagePreview, setShowImagePreview] = useState(false); // 剂量选择相关状态 const [dosagePickerVisible, setDosagePickerVisible] = useState(false); const [dosageValuePicker, setDosageValuePicker] = useState( medicationFromStore?.dosageValue ?? 1 ); const [dosageUnitPicker, setDosageUnitPicker] = useState( medicationFromStore?.dosageUnit ?? DOSAGE_UNITS[0] ); // 剂型选择相关状态 const [formPickerVisible, setFormPickerVisible] = useState(false); const [formPicker, setFormPicker] = useState( medicationFromStore?.form ?? 'capsule' ); useEffect(() => { if (!medicationFromStore) { dispatch(fetchMedications()); } }, [dispatch, medicationFromStore]); useEffect(() => { if (medicationFromStore) { setMedication(medicationFromStore); setLoading(false); } }, [medicationFromStore]); useEffect(() => { // 同步剂量选择器和剂型选择器的默认值 if (medication) { setDosageValuePicker(medication.dosageValue); setDosageUnitPicker(medication.dosageUnit); setFormPicker(medication.form); } }, [medication?.dosageValue, medication?.dosageUnit, medication?.form]); useEffect(() => { setNoteDraft(medication?.note ?? ''); }, [medication?.note]); useEffect(() => { let isMounted = true; let abortController = new AbortController(); console.log('[MEDICATION_DETAIL] useEffect triggered', { medicationId, hasMedicationFromStore: !!medicationFromStore, deleteLoading }); // 如果正在删除操作中,不执行任何操作 if (deleteLoading) { console.log('[MEDICATION_DETAIL] Delete operation in progress, skipping useEffect'); return () => { isMounted = false; abortController.abort(); }; } if (!medicationId || medicationFromStore) { console.log('[MEDICATION_DETAIL] Early return from useEffect', { hasMedicationId: !!medicationId, hasMedicationFromStore: !!medicationFromStore }); return () => { isMounted = false; abortController.abort(); }; } console.log('[MEDICATION_DETAIL] Starting API call for medication', medicationId); setLoading(true); getMedicationById(medicationId) .then((data) => { if (!isMounted || abortController.signal.aborted) return; console.log('[MEDICATION_DETAIL] API call successful', data); setMedication(data); setError(null); }) .catch((err) => { if (abortController.signal.aborted) return; console.error('加载药品详情失败', err); console.log('[MEDICATION_DETAIL] API call failed for medication', medicationId, err); if (isMounted) { setError('暂时无法获取该药品的信息,请稍后重试。'); } }) .finally(() => { if (isMounted && !abortController.signal.aborted) { setLoading(false); } }); return () => { isMounted = false; abortController.abort(); }; }, [medicationId, medicationFromStore, deleteLoading]); useEffect(() => { let isMounted = true; if (!medicationId) { return () => { isMounted = false; }; } setSummaryLoading(true); getMedicationRecords({ medicationId }) .then((records) => { if (!isMounted) return; const takenCount = records.filter((record) => record.status === 'taken').length; const earliestRecord = records.reduce((earliest, record) => { const current = new Date(record.scheduledTime); if (!earliest || current < earliest) { return current; } return earliest; }, null); const startedDaysRaw = earliestRecord ? dayjs().diff(dayjs(earliestRecord), 'day') : medication ? dayjs().diff(dayjs(medication.startDate), 'day') : null; const startedDays = typeof startedDaysRaw === 'number' ? Math.max(startedDaysRaw, 0) : null; setSummary({ takenCount, startedDays: startedDays ?? null, }); }) .catch((err) => { console.error('加载服药记录失败', err); }) .finally(() => { if (isMounted) { setSummaryLoading(false); } }); return () => { isMounted = false; }; }, [medicationId, medication]); const appendDictationResult = useCallback((text: string) => { const clean = text.trim(); if (!clean) return; setNoteDraft((prev) => { if (!prev) return clean; return `${prev}${prev.endsWith('\n') ? '' : '\n'}${clean}`; }); }, []); useEffect(() => { if (!isDictationSupported || !noteModalVisible) { 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_DETAIL] voice error', error); setDictationActive(false); setDictationLoading(false); Alert.alert('语音识别不可用', '无法使用语音输入,请检查权限设置后重试'); }; return () => { Voice.destroy() .then(() => { Voice.removeAllListeners(); }) .catch(() => {}); }; }, [appendDictationResult, isDictationSupported, noteModalVisible]); useEffect(() => { if (!noteModalVisible) { setKeyboardHeight(0); return; } const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; const handleShow = (event: any) => { const height = event?.endCoordinates?.height ?? 0; setKeyboardHeight(height); }; const handleHide = () => setKeyboardHeight(0); const showSub = Keyboard.addListener(showEvent, handleShow); const hideSub = Keyboard.addListener(hideEvent, handleHide); return () => { showSub.remove(); hideSub.remove(); }; }, [noteModalVisible]); 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 { // ignore if not recording } await Voice.start('zh-CN'); } catch (error) { console.log('[MEDICATION_DETAIL] unable to start dictation', error); setDictationLoading(false); Alert.alert('无法启动语音输入', '请检查麦克风与语音识别权限后重试'); } }, [dictationActive, dictationLoading, isDictationSupported]); const closeNoteModal = useCallback(() => { setNoteModalVisible(false); if (dictationActive) { Voice.stop().catch(() => {}); } setDictationActive(false); setDictationLoading(false); setKeyboardHeight(0); }, [dictationActive]); const handleToggleMedication = async (nextValue: boolean) => { if (!medication || updatePending) return; // 如果是关闭激活状态,显示确认弹窗 if (!nextValue) { setDeactivateSheetVisible(true); return; } // 如果是开启激活状态,直接执行 try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, isActive: nextValue, }) ).unwrap(); setMedication(updated); // 重新安排药品通知 try { if (nextValue) { // 如果激活了药品,安排通知 await medicationNotificationService.scheduleMedicationNotifications(updated); } } catch (error) { console.error('[MEDICATION] 处理药品通知失败:', error); // 不影响药品状态切换的成功流程,只记录错误 } } catch (err) { console.error('切换药品状态失败', err); Alert.alert('操作失败', '切换提醒状态时出现问题,请稍后重试。'); } finally { setUpdatePending(false); } }; const handleDeactivateMedication = useCallback(async () => { if (!medication || deactivateLoading) return; try { setDeactivateLoading(true); setDeactivateSheetVisible(false); // 立即关闭确认对话框 const updated = await dispatch( updateMedicationAction({ id: medication.id, isActive: false, }) ).unwrap(); setMedication(updated); // 取消该药品的通知 try { await medicationNotificationService.cancelMedicationNotifications(updated.id); } catch (error) { console.error('[MEDICATION] 取消药品通知失败:', error); // 不影响药品状态切换的成功流程,只记录错误 } } catch (error) { console.error('停用药物失败', error); Alert.alert('操作失败', '停用药物时发生问题,请稍后重试。'); } finally { setDeactivateLoading(false); } }, [dispatch, medication, deactivateLoading]); const formLabel = medication ? FORM_LABELS[medication.form] : ''; const dosageLabel = medication ? `${medication.dosageValue} ${medication.dosageUnit}` : '--'; const startDateLabel = medication ? dayjs(medication.startDate).format('YYYY年M月D日') : '--'; // 计算服药周期显示 const medicationPeriodLabel = useMemo(() => { if (!medication) return '--'; const startDate = dayjs(medication.startDate).format('YYYY年M月D日'); if (medication.endDate) { // 有结束日期,显示开始日期到结束日期 const endDate = dayjs(medication.endDate).format('YYYY年M月D日'); return `${startDate} - ${endDate}`; } else { // 没有结束日期,显示长期 return `${startDate} - 长期`; } }, [medication]); const reminderTimes = medication?.medicationTimes?.length ? medication.medicationTimes.join('、') : '尚未设置'; const frequencyLabel = useMemo(() => { if (!medication) return '--'; switch (medication.repeatPattern) { case 'daily': return `每日 ${medication.timesPerDay} 次`; case 'weekly': return `每周 ${medication.timesPerDay} 次`; default: return `自定义 · ${medication.timesPerDay} 次/日`; } }, [medication]); const handleOpenNoteModal = useCallback(() => { setNoteDraft(medication?.note ?? ''); setNoteModalVisible(true); }, [medication?.note]); const handleSaveNote = useCallback(async () => { if (!medication) return; const trimmed = noteDraft.trim(); setNoteSaving(true); try { const updated = await dispatch( updateMedicationAction({ id: medication.id, note: trimmed || undefined, }) ).unwrap(); setMedication(updated); closeNoteModal(); } catch (err) { console.error('保存备注失败', err); Alert.alert('保存失败', '提交备注时出现问题,请稍后重试。'); } finally { setNoteSaving(false); } }, [closeNoteModal, dispatch, medication, noteDraft]); const statusLabel = medication?.isActive ? '提醒已开启' : '提醒已关闭'; const noteText = medication?.note?.trim() ? medication.note : '暂无备注信息'; const dayStreakText = typeof summary.startedDays === 'number' ? `已坚持 ${summary.startedDays} 天` : medication ? `开始于 ${dayjs(medication.startDate).format('YYYY年M月D日')}` : '暂无开始日期'; const handleDeleteMedication = useCallback(async () => { if (!medication || deleteLoading) { console.log('[MEDICATION_DETAIL] Delete aborted', { hasMedication: !!medication, deleteLoading }); return; } console.log('[MEDICATION_DETAIL] Starting delete operation for medication', medication.id); try { setDeleteLoading(true); setDeleteSheetVisible(false); // 立即关闭确认对话框 // 先取消该药品的通知 try { await medicationNotificationService.cancelMedicationNotifications(medication.id); } catch (error) { console.error('[MEDICATION] 取消药品通知失败:', error); // 不影响药品删除的成功流程,只记录错误 } await dispatch(deleteMedicationAction(medication.id)).unwrap(); console.log('[MEDICATION_DETAIL] Delete operation successful, navigating back'); router.back(); } catch (err) { console.error('删除药品失败', err); Alert.alert('删除失败', '移除该药品时出现问题,请稍后再试。'); } finally { setDeleteLoading(false); } }, [deleteLoading, dispatch, medication, router]); const handleImagePreview = useCallback(() => { if (medication?.photoUrl) { setShowImagePreview(true); } }, [medication?.photoUrl]); const handleStartDatePress = useCallback(() => { if (!medication) return; const startDate = dayjs(medication.startDate).format('YYYY年M月D日'); let message = `开始服药日期:${startDate}`; if (medication.endDate) { const endDate = dayjs(medication.endDate).format('YYYY年M月D日'); message += `\n结束服药日期:${endDate}`; } else { message += `\n服药计划:长期服药`; } Alert.alert('服药周期', message); }, [medication]); const handleTimePress = useCallback(() => { Alert.alert('服药时间', `设置的时间:${reminderTimes}`); }, [reminderTimes]); const handleDosagePress = useCallback(() => { if (!medication) return; setDosagePickerVisible(true); }, [medication]); const handleFormPress = useCallback(() => { if (!medication) return; setFormPickerVisible(true); }, [medication]); const handleFrequencyPress = useCallback(() => { if (!medication) return; // 跳转到独立的频率编辑页面 router.push({ pathname: ROUTES.MEDICATION_EDIT_FREQUENCY, params: { medicationId: medication.id, medicationName: medication.name, repeatPattern: medication.repeatPattern, timesPerDay: medication.timesPerDay.toString(), medicationTimes: medication.medicationTimes.join(','), }, }); }, [medication, router]); const confirmDosagePicker = useCallback(async () => { if (!medication || updatePending) return; setDosagePickerVisible(false); // 如果值没有变化,不需要更新 if (dosageValuePicker === medication.dosageValue && dosageUnitPicker === medication.dosageUnit) { return; } try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, dosageValue: dosageValuePicker, dosageUnit: dosageUnitPicker, }) ).unwrap(); setMedication(updated); // 重新安排药品通知 try { await medicationNotificationService.scheduleMedicationNotifications(updated); } catch (error) { console.error('[MEDICATION] 安排药品通知失败:', error); // 不影响药品更新的成功流程,只记录错误 } } catch (err) { console.error('更新剂量失败', err); Alert.alert('更新失败', '更新剂量时出现问题,请稍后重试。'); } finally { setUpdatePending(false); } }, [dispatch, dosageUnitPicker, dosageValuePicker, medication, updatePending]); const confirmFormPicker = useCallback(async () => { if (!medication || updatePending) return; setFormPickerVisible(false); // 如果值没有变化,不需要更新 if (formPicker === medication.form) { return; } try { setUpdatePending(true); const updated = await dispatch( updateMedicationAction({ id: medication.id, form: formPicker, }) ).unwrap(); setMedication(updated); // 重新安排药品通知 try { await medicationNotificationService.scheduleMedicationNotifications(updated); } catch (error) { console.error('[MEDICATION] 安排药品通知失败:', error); // 不影响药品更新的成功流程,只记录错误 } } catch (err) { console.error('更新剂型失败', err); Alert.alert('更新失败', '更新剂型时出现问题,请稍后重试。'); } finally { setUpdatePending(false); } }, [dispatch, formPicker, medication, updatePending]); if (!medicationId) { return ( {/* 背景渐变 */} {/* 装饰性圆圈 */} 未找到药品信息 请从用药列表重新进入此页面。 ); } const isLoadingState = loading && !medication; const contentBottomPadding = Math.max(insets.bottom, 16) + 140; return ( {/* 背景渐变 */} {/* 装饰性圆圈 */} {isLoadingState ? ( 正在载入... ) : error ? ( {error} 请检查网络后重试,或返回上一页。 ) : medication ? ( {medication.photoUrl && ( )} {medication.name} {dosageLabel} · {formLabel}
频率 {frequencyLabel}
药品备注 {noteText}
{summaryLoading ? '统计中...' : `累计服药 ${summary.takenCount} 次`} {summaryLoading ? '正在计算坚持天数' : dayStreakText}
) : null} {medication ? ( setDeleteSheetVisible(true)} > {isLiquidGlassAvailable() ? ( 删除该药品 ) : ( 删除该药品 )} ) : null} 编辑备注 {isDictationSupported && ( {dictationLoading ? ( ) : ( )} )} {!isDictationSupported && ( 当前设备暂不支持语音转文字,可直接输入备注 )} {noteSaving ? ( ) : ( 保存 )} setDosagePickerVisible(false)} > setDosagePickerVisible(false)} /> 选择剂量 剂量值 setDosageValuePicker(Number(value))} itemStyle={styles.pickerItem} style={styles.picker} > {DOSAGE_VALUES.map((value) => ( ))} 单位 setDosageUnitPicker(String(value))} itemStyle={styles.pickerItem} style={styles.picker} > {DOSAGE_UNITS.map((unit) => ( ))} setDosagePickerVisible(false)} style={[styles.pickerBtn, { borderColor: colors.border }]} > 取消 确定 setFormPickerVisible(false)} > setFormPickerVisible(false)} /> 选择剂型 setFormPicker(value as MedicationForm)} itemStyle={styles.pickerItem} style={styles.picker} > {FORM_OPTIONS.map((option) => ( ))} setFormPickerVisible(false)} style={[styles.pickerBtn, { borderColor: colors.border }]} > 取消 确定 {medication ? ( setDeleteSheetVisible(false)} onConfirm={handleDeleteMedication} title={`删除 ${medication.name}?`} description="删除后将清除与该药品相关的提醒与历史记录,且无法恢复。" confirmText="删除" cancelText="取消" destructive loading={deleteLoading} /> ) : null} {medication ? ( setDeactivateSheetVisible(false)} onConfirm={handleDeactivateMedication} title={`停用 ${medication.name}?`} description="停用后,当天已生成的用药计划会一并删除,且无法恢复。" confirmText="确认停用" cancelText="取消" destructive loading={deactivateLoading} /> ) : null} {/* 图片预览 */} {medication?.photoUrl && ( setShowImagePreview(false)} swipeToCloseEnabled={true} doubleTapToZoomEnabled={true} HeaderComponent={() => ( {medication.name} )} FooterComponent={() => ( setShowImagePreview(false)} > 关闭 )} /> )}
); } const Section = ({ title, children, color, }: { title: string; children: React.ReactNode; color: string; }) => { return ( {title} {children} ); }; const styles = StyleSheet.create({ container: { flex: 1, position: 'relative', }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, decorativeCircle1: { position: 'absolute', top: 40, right: 20, width: 60, height: 60, borderRadius: 30, backgroundColor: '#0EA5E9', opacity: 0.1, }, decorativeCircle2: { position: 'absolute', bottom: -15, left: -15, width: 40, height: 40, borderRadius: 20, backgroundColor: '#0EA5E9', opacity: 0.05, }, content: { paddingHorizontal: 20, gap: 24, }, centered: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12, }, loadingText: { marginTop: 8, fontSize: 14, }, emptyTitle: { fontSize: 18, fontWeight: '600', textAlign: 'center', }, emptySubtitle: { fontSize: 14, textAlign: 'center', }, heroCard: { borderRadius: 28, padding: 20, flexDirection: 'row', justifyContent: 'space-between', backgroundColor: '#FFFFFF', alignItems: 'center', gap: 12, }, heroInfo: { flexDirection: 'row', alignItems: 'center', gap: 14, flex: 1, }, heroImageWrapper: { width: 64, height: 64, borderRadius: 20, backgroundColor: '#F2F2F2', alignItems: 'center', justifyContent: 'center', position: 'relative', }, heroImage: { width: '60%', height: '60%', borderRadius: '20%' }, heroTitle: { fontSize: 20, fontWeight: '700', }, heroMeta: { marginTop: 4, fontSize: 13, fontWeight: '500', }, heroToggle: { alignItems: 'flex-end', gap: 6, }, section: { gap: 12, }, sectionTitle: { fontSize: 18, fontWeight: '700', }, row: { flexDirection: 'row', gap: 12, }, fullCard: { borderRadius: 22, padding: 18, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, fullCardLeading: { flexDirection: 'row', alignItems: 'center', gap: 8, }, fullCardLabel: { fontSize: 15, fontWeight: '600', }, fullCardTrailing: { flexDirection: 'row', alignItems: 'center', gap: 6, }, fullCardValue: { fontSize: 16, fontWeight: '600', }, noteCard: { flexDirection: 'row', alignItems: 'center', gap: 14, borderRadius: 24, paddingHorizontal: 18, paddingVertical: 16 }, noteBody: { flex: 1, gap: 4, }, noteLabel: { fontSize: 14, fontWeight: '600', }, noteValue: { fontSize: 14, lineHeight: 20, }, summaryCard: { flexDirection: 'row', alignItems: 'center', borderRadius: 24, padding: 18, gap: 16, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 3 }, elevation: 3, borderWidth: 1, borderColor: 'rgba(0, 0, 0, 0.04)', }, summaryIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: '#EEF1FF', alignItems: 'center', justifyContent: 'center', }, summaryBody: { flex: 1, gap: 4, }, summaryHighlight: { fontSize: 16, fontWeight: '700', }, summaryMeta: { fontSize: 14, }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.35)', justifyContent: 'flex-end', }, modalBackdrop: { flex: 1, }, modalContainer: { width: '100%', paddingHorizontal: 20, }, modalCard: { borderTopLeftRadius: 28, borderTopRightRadius: 28, paddingHorizontal: 20, paddingTop: 20, paddingBottom: 32, gap: 16, }, modalHandle: { width: 60, height: 5, borderRadius: 2.5, backgroundColor: 'rgba(0,0,0,0.12)', alignSelf: 'center', marginBottom: 12, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, modalTitle: { fontSize: 18, fontWeight: '700', }, noteEditorWrapper: { borderWidth: 1, borderRadius: 24, paddingHorizontal: 18, paddingVertical: 24, minHeight: 120, paddingRight: 70, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 3, }, noteEditorInput: { minHeight: 50, fontSize: 15, lineHeight: 22, }, voiceButton: { position: 'absolute', right: 16, top: 16, width: 40, height: 40, borderRadius: 20, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, voiceHint: { fontSize: 12, }, modalActionGhostText: { fontSize: 16, fontWeight: '600', }, modalActionPrimary: { borderRadius: 22, height: 52, alignItems: 'center', justifyContent: 'center', marginHorizontal: 8, shadowOpacity: 0.25, shadowRadius: 12, shadowOffset: { width: 0, height: 8 }, elevation: 4, }, modalActionPrimaryText: { fontSize: 17, fontWeight: '700', }, modalActionContainer: { marginTop: 8, }, footerBar: { position: 'absolute', left: 0, right: 0, bottom: 0, paddingHorizontal: 20, paddingTop: 16, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: 'rgba(15,23,42,0.06)', }, deleteButton: { height: 56, borderRadius: 24, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, overflow: 'hidden', // 保证玻璃边界圆角效果 }, fallbackDeleteButton: { backgroundColor: '#EF4444', shadowColor: 'rgba(239,68,68,0.4)', shadowOffset: { width: 0, height: 10 }, shadowOpacity: 1, shadowRadius: 20, elevation: 6, }, deleteButtonText: { fontSize: 17, fontWeight: '700', color: '#fff', }, // Picker 相关样式 pickerBackdrop: { flex: 1, backgroundColor: 'rgba(15, 23, 42, 0.4)', }, pickerSheet: { position: 'absolute', left: 20, right: 20, bottom: 40, borderRadius: 24, padding: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 10 }, shadowOpacity: 0.2, shadowRadius: 20, elevation: 8, }, pickerTitle: { fontSize: 20, fontWeight: '700', marginBottom: 20, textAlign: 'center', }, pickerRow: { flexDirection: 'row', gap: 16, marginBottom: 20, }, pickerColumn: { flex: 1, gap: 8, }, pickerLabel: { fontSize: 14, fontWeight: '600', textAlign: 'center', }, picker: { width: '100%', height: 150, }, pickerItem: { fontSize: 18, height: 150, }, pickerActions: { flexDirection: 'row', gap: 12, marginTop: 16, }, pickerBtn: { flex: 1, paddingVertical: 14, borderRadius: 16, alignItems: 'center', borderWidth: 1, }, pickerBtnPrimary: { borderWidth: 0, }, pickerBtnText: { fontSize: 16, fontWeight: '600', }, imagePreviewHint: { position: 'absolute', top: 4, right: 4, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: 10, padding: 4, }, // ImageViewing 组件样式 imageViewerHeader: { position: 'absolute', top: 60, left: 20, right: 20, backgroundColor: 'rgba(0, 0, 0, 0.7)', borderRadius: 12, paddingHorizontal: 16, paddingVertical: 12, zIndex: 1, }, imageViewerHeaderText: { color: '#FFF', fontSize: 14, fontWeight: '500', textAlign: 'center', }, imageViewerFooter: { position: 'absolute', bottom: 60, left: 20, right: 20, alignItems: 'center', zIndex: 1, }, imageViewerFooterButton: { backgroundColor: 'rgba(0, 0, 0, 0.7)', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 20, }, imageViewerFooterButtonText: { color: '#FFF', fontSize: 16, fontWeight: '500', }, });