import { ThemedText } from '@/components/ThemedText'; import { ConfirmationSheet } from '@/components/ui/ConfirmationSheet'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { IconSymbol } from '@/components/ui/IconSymbol'; import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useI18n } from '@/hooks/useI18n'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { fetchMedications, selectMedications, selectMedicationsLoading, updateMedicationAction, } from '@/store/medicationsSlice'; import { selectUserProfile } from '@/store/userSlice'; import type { Medication, MedicationForm } from '@/types/medication'; import { useFocusEffect } from '@react-navigation/native'; import dayjs from 'dayjs'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { router } from 'expo-router'; import React, { useCallback, useMemo, useState } from 'react'; import { ActivityIndicator, Alert, ScrollView, StyleSheet, Switch, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; type FilterType = 'all' | 'active' | 'inactive'; // 这些常量将在组件内部定义,以便使用翻译函数 const DEFAULT_IMAGE = require('@/assets/images/medicine/image-medicine.png'); export default function ManageMedicationsScreen() { const { t } = useI18n(); const dispatch = useAppDispatch(); const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colors = Colors[theme]; const safeAreaTop = useSafeAreaTop(); const insets = useSafeAreaInsets(); const medications = useAppSelector(selectMedications); const loading = useAppSelector(selectMedicationsLoading); const userProfile = useAppSelector(selectUserProfile); const [activeFilter, setActiveFilter] = useState('all'); const [pendingMedicationId, setPendingMedicationId] = useState(null); const [deactivateSheetVisible, setDeactivateSheetVisible] = useState(false); const [medicationToDeactivate, setMedicationToDeactivate] = useState(null); const [deactivateLoading, setDeactivateLoading] = useState(false); const listLoading = loading.medications && medications.length === 0; useFocusEffect( useCallback(() => { dispatch(fetchMedications()); }, [dispatch]) ); // 优化:使用更精确的依赖项,只有当药品数量或激活状态改变时才重新计算 const medicationsHash = useMemo(() => { return medications.map(m => `${m.id}-${m.isActive}`).join('|'); }, [medications]); const counts = useMemo>(() => { const active = medications.filter((med) => med.isActive).length; const inactive = medications.length - active; return { all: medications.length, active, inactive, }; }, [medicationsHash]); const filteredMedications = useMemo(() => { switch (activeFilter) { case 'active': return medications.filter((med) => med.isActive); case 'inactive': return medications.filter((med) => !med.isActive); default: return medications; } }, [activeFilter, medicationsHash]); const handleToggleMedication = useCallback( async (medication: Medication, nextValue: boolean) => { if (pendingMedicationId) return; // 如果是关闭激活状态,显示确认弹窗 if (!nextValue) { setMedicationToDeactivate(medication); setDeactivateSheetVisible(true); return; } // 如果是开启激活状态,直接执行 try { setPendingMedicationId(medication.id); await dispatch( updateMedicationAction({ id: medication.id, isActive: nextValue, }) ).unwrap(); } catch (error) { console.error('更新药物状态失败', error); Alert.alert(t('medications.manage.toggleError.title'), t('medications.manage.toggleError.message')); } finally { setPendingMedicationId(null); } }, [dispatch, pendingMedicationId] ); const handleDeactivateMedication = useCallback(async () => { if (!medicationToDeactivate || deactivateLoading) return; try { setDeactivateLoading(true); setDeactivateSheetVisible(false); // 立即关闭确认对话框 await dispatch( updateMedicationAction({ id: medicationToDeactivate.id, isActive: false, }) ).unwrap(); } catch (error) { console.error('停用药物失败', error); Alert.alert(t('medications.manage.deactivate.error.title'), t('medications.manage.deactivate.error.message')); } finally { setDeactivateLoading(false); setMedicationToDeactivate(null); } }, [dispatch, medicationToDeactivate, deactivateLoading]); // 创建独立的药品卡片组件,使用 React.memo 优化渲染 const MedicationCard = React.memo(({ medication, onPress }: { medication: Medication; onPress: () => void }) => { // 使用翻译函数获取剂型标签 const FORM_LABELS: Record = { capsule: t('medications.manage.formLabels.capsule'), pill: t('medications.manage.formLabels.pill'), injection: t('medications.manage.formLabels.injection'), spray: t('medications.manage.formLabels.spray'), drop: t('medications.manage.formLabels.drop'), syrup: t('medications.manage.formLabels.syrup'), other: t('medications.manage.formLabels.other'), }; const dosageLabel = `${medication.dosageValue} ${medication.dosageUnit || ''} ${FORM_LABELS[medication.form] ?? ''}`.trim(); const frequencyLabel = `${medication.repeatPattern === 'daily' ? t('medications.manage.frequency.daily') : medication.repeatPattern === 'weekly' ? t('medications.manage.frequency.weekly') : t('medications.manage.frequency.custom')} | ${dosageLabel}`; const startDateLabel = dayjs(medication.startDate).isValid() ? dayjs(medication.startDate).format('M月D日') : t('medications.manage.unknownDate'); const reminderLabel = medication.medicationTimes?.length ? medication.medicationTimes.join('、') : `${medication.timesPerDay} ${t('medications.manage.cardMeta.reminderNotSet')}`; return ( {medication.name} {frequencyLabel} {t('medications.manage.cardMeta', { date: startDateLabel, reminder: reminderLabel })} handleToggleMedication(medication, value)} disabled={pendingMedicationId === medication.id} trackColor={{ false: '#D9D9D9', true: colors.primary }} thumbColor={medication.isActive ? '#fff' : '#fff'} ios_backgroundColor="#D9D9D9" /> {pendingMedicationId === medication.id && ( )} ); }, (prevProps, nextProps) => { // 自定义比较函数,只有当药品的 isActive 状态或 ID 改变时才重新渲染 return ( prevProps.medication.id === nextProps.medication.id && prevProps.medication.isActive === nextProps.medication.isActive && prevProps.medication.name === nextProps.medication.name && prevProps.medication.photoUrl === nextProps.medication.photoUrl ); }); MedicationCard.displayName = 'MedicationCard'; const handleOpenMedicationDetails = useCallback((medicationId: string) => { router.push({ pathname: '/medications/[medicationId]', params: { medicationId }, }); }, []); const renderMedicationCard = useCallback( (medication: Medication) => { return ( handleOpenMedicationDetails(medication.id)} /> ); }, [handleToggleMedication, pendingMedicationId, colors, handleOpenMedicationDetails] ); return ( {/* 背景渐变 */} {/* 装饰性圆圈 */} router.back()} variant="minimal" transparent /> {t('medications.greeting', { name: userProfile.name || '朋友' })} {t('medications.manage.subtitle')} router.push('/medications/add-medication')} > {isLiquidGlassAvailable() ? ( ) : ( )} {[ { key: 'all' as FilterType, label: t('medications.manage.filters.all') }, { key: 'active' as FilterType, label: t('medications.manage.filters.active') }, { key: 'inactive' as FilterType, label: t('medications.manage.filters.inactive') }, ].map((filter) => { const isActive = filter.key === activeFilter; return ( setActiveFilter(filter.key)} > {filter.label} {counts[filter.key] ?? 0} ); })} {listLoading ? ( {t('medications.manage.loading')} ) : filteredMedications.length === 0 ? ( {t('medications.manage.empty.title')} {t('medications.manage.empty.subtitle')} ) : ( {filteredMedications.map(renderMedicationCard)} )} {/* 停用药品确认弹窗 */} {medicationToDeactivate ? ( { setDeactivateSheetVisible(false); setMedicationToDeactivate(null); }} onConfirm={handleDeactivateMedication} title={t('medications.manage.deactivate.title', { name: medicationToDeactivate.name })} description={t('medications.manage.deactivate.description')} confirmText={t('medications.manage.deactivate.confirm')} cancelText={t('medications.manage.deactivate.cancel')} destructive loading={deactivateLoading} /> ) : null} ); } 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: 20, }, pageHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, title: { fontSize: 24, fontWeight: '600', }, subtitle: { marginTop: 6, fontSize: 14, }, addButton: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, fallbackAddButton: { backgroundColor: 'rgba(255, 255, 255, 0.9)', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.3)', }, segmented: { flexDirection: 'row', padding: 6, borderRadius: 20, gap: 6, }, segmentButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderRadius: 16, paddingVertical: 10, gap: 8, }, segmentLabel: { fontSize: 15, fontWeight: '600', }, segmentBadge: { minWidth: 28, paddingHorizontal: 8, height: 24, borderRadius: 12, alignItems: 'center', justifyContent: 'center', }, segmentBadgeLabel: { fontSize: 12, fontWeight: '700', }, list: { gap: 14, }, card: { borderRadius: 22, padding: 14, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 6, shadowOffset: { width: 0, height: 4 }, elevation: 1, }, cardInfo: { flexDirection: 'row', alignItems: 'center', gap: 12, flex: 1, }, cardImage: { width: 52, height: 52, borderRadius: 16, backgroundColor: '#F2F2F2', }, cardTexts: { flex: 1, gap: 4, }, cardTitle: { fontSize: 16, fontWeight: '600', }, cardMeta: { fontSize: 13, }, loading: { borderRadius: 22, paddingVertical: 32, alignItems: 'center', gap: 12, }, loadingText: { fontSize: 14, }, empty: { borderRadius: 22, paddingVertical: 32, alignItems: 'center', gap: 12, paddingHorizontal: 16, }, emptyImage: { width: 120, height: 120, }, emptyTitle: { fontSize: 18, fontWeight: '600', }, emptySubtitle: { fontSize: 14, textAlign: 'center', }, switchContainer: { alignItems: 'center', justifyContent: 'center', position: 'relative', }, switchLoading: { position: 'absolute', marginLeft: 30, // 确保加载指示器显示在开关旁边 }, });