import { DateSelector } from '@/components/DateSelector'; import { MedicationCard } from '@/components/medication/MedicationCard'; import { ThemedText } from '@/components/ThemedText'; import { IconSymbol } from '@/components/ui/IconSymbol'; import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { medicationNotificationService } from '@/services/medicationNotifications'; import { fetchMedicationRecords, fetchMedications, selectMedicationDisplayItemsByDate } from '@/store/medicationsSlice'; import { DEFAULT_MEMBER_NAME } from '@/store/userSlice'; import { useFocusEffect } from '@react-navigation/native'; import dayjs, { Dayjs } from 'dayjs'; import 'dayjs/locale/zh-cn'; 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, useEffect, useMemo, useState } from 'react'; import { ScrollView, StyleSheet, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; dayjs.locale('zh-cn'); type MedicationFilter = 'all' | 'taken' | 'missed'; type ThemeColors = (typeof Colors)[keyof typeof Colors]; export default function MedicationsScreen() { const dispatch = useAppDispatch(); const insets = useSafeAreaInsets(); const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colors: ThemeColors = Colors[theme]; const userProfile = useAppSelector((state) => state.user.profile); const [selectedDate, setSelectedDate] = useState(dayjs()); const [selectedDateIndex, setSelectedDateIndex] = useState(selectedDate.date() - 1); const [activeFilter, setActiveFilter] = useState('all'); // 从 Redux 获取数据 const selectedKey = selectedDate.format('YYYY-MM-DD'); const medicationsForDay = useAppSelector((state) => selectMedicationDisplayItemsByDate(selectedKey)(state)); const handleOpenAddMedication = useCallback(() => { router.push('/medications/add-medication'); }, []); const handleOpenMedicationManagement = useCallback(() => { router.push('/medications/manage-medications'); }, []); const handleOpenMedicationDetails = useCallback((medicationId: string) => { router.push({ pathname: '/medications/[medicationId]', params: { medicationId }, }); }, []); // 加载药物和记录数据 useEffect(() => { dispatch(fetchMedications()); dispatch(fetchMedicationRecords({ date: selectedKey })); }, [dispatch, selectedKey]); // 页面聚焦时刷新数据,确保从添加页面返回时能看到最新数据 useFocusEffect( useCallback(() => { // 重新安排药品通知并刷新数据 const refreshDataAndRescheduleNotifications = async () => { try { // 只获取一次药物数据,然后复用结果 const medications = await dispatch(fetchMedications({ isActive: true })).unwrap(); // 并行执行获取药物记录和安排通知 await Promise.all([ dispatch(fetchMedicationRecords({ date: selectedKey })), medicationNotificationService.rescheduleAllMedicationNotifications(medications), ]); } catch (error) { console.error('刷新数据或重新安排药品通知失败:', error); } }; refreshDataAndRescheduleNotifications(); }, [dispatch, selectedKey]) ); useEffect(() => { setActiveFilter('all'); }, [selectedDate]); // 为每个药物添加默认图片(如果没有图片) const medicationsWithImages = useMemo(() => { return medicationsForDay.map((med: any) => ({ ...med, image: med.image || require('@/assets/images/medicine/image-medicine.png'), // 默认使用瓶子图标 })); }, [medicationsForDay]); const filteredMedications = useMemo(() => { if (activeFilter === 'all') { return medicationsWithImages; } return medicationsWithImages.filter((item: any) => item.status === activeFilter); }, [activeFilter, medicationsWithImages]); const counts = useMemo(() => { const taken = medicationsWithImages.filter((item: any) => item.status === 'taken').length; const missed = medicationsWithImages.filter((item: any) => item.status === 'missed').length; return { all: medicationsWithImages.length, taken, missed, }; }, [medicationsWithImages]); const displayName = userProfile.name?.trim() || DEFAULT_MEMBER_NAME; const headerDateLabel = selectedDate.isSame(dayjs(), 'day') ? `今天,${selectedDate.format('M月D日')}` : selectedDate.format('M月D日 dddd'); const emptyState = filteredMedications.length === 0; return ( {/* 背景渐变 */} {/* 装饰性圆圈 */} 你好,{displayName} 欢迎来到用药助手! {isLiquidGlassAvailable() ? ( ) : ( )} {isLiquidGlassAvailable() ? ( ) : ( )} { setSelectedDate(dayjs(date)); setSelectedDateIndex(index); }} disableFutureDates containerStyle={styles.dateSelectorContainer} /> 今日用药 {(['all', 'taken', 'missed'] as MedicationFilter[]).map((filter) => { const isActive = activeFilter === filter; const labelMap: Record = { all: '全部', taken: '已服用', missed: '未服用', }; return ( setActiveFilter(filter)} style={[ styles.segment, isActive && { backgroundColor: colors.primary }, ]} > {labelMap[filter]} {counts[filter]} ); })} {emptyState ? ( 今日暂无用药安排 还未添加任何用药计划,快来补充吧。 ) : ( {filteredMedications.map((item: any) => ( handleOpenMedicationDetails(item.medicationId)} /> ))} )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, 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, }, scrollContent: { paddingHorizontal: 20, gap: 24, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, headerActions: { flexDirection: 'row', alignItems: 'center', gap: 10, }, headerAddButton: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, fallbackAddButton: { backgroundColor: 'rgba(255, 255, 255, 0.9)', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.3)', }, avatar: { width: 60, height: 60, borderRadius: 30, }, greeting: { fontSize: 24, fontWeight: '600', }, welcome: { marginTop: 6, fontSize: 14, }, sectionSpacing: { gap: 16, }, dateSelectorContainer: { paddingRight: 0, }, sectionTitle: { fontSize: 16, fontWeight: '500', }, sectionHeader: { fontSize: 20, fontWeight: '600', }, segmentedControl: { flexDirection: 'row', borderRadius: 18, padding: 6, gap: 6, }, segment: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, borderRadius: 14, paddingVertical: 10, }, segmentLabel: { fontSize: 14, fontWeight: '600', }, segmentBadge: { minWidth: 24, height: 24, borderRadius: 12, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 6, }, segmentBadgeText: { fontSize: 12, fontWeight: '600', }, emptyState: { alignItems: 'center', paddingHorizontal: 24, paddingVertical: 32, borderRadius: 24, gap: 16, }, emptyIllustration: { width: 160, height: 160, resizeMode: 'contain', }, emptyTitle: { textAlign: 'center', fontSize: 18, fontWeight: '600', }, emptySubtitle: { textAlign: 'center', fontSize: 14, lineHeight: 20, }, primaryButton: { marginTop: 8, paddingVertical: 14, paddingHorizontal: 32, borderRadius: 22, flexDirection: 'row', alignItems: 'center', gap: 8, }, primaryButtonText: { fontSize: 16, fontWeight: '600', }, cardsWrapper: { gap: 16, }, loadingContainer: { alignItems: 'center', paddingHorizontal: 24, paddingVertical: 48, borderRadius: 24, gap: 16, }, loadingText: { fontSize: 14, }, });