import { ThemedText } from '@/components/ThemedText'; import { useAppDispatch } from '@/hooks/redux'; import { useI18n } from '@/hooks/useI18n'; import { skipMedicationAction, takeMedicationAction } from '@/store/medicationsSlice'; import type { MedicationDisplayItem } from '@/types/medication'; import { Ionicons } from '@expo/vector-icons'; import dayjs, { Dayjs } from 'dayjs'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { Image } from 'expo-image'; import React, { useEffect, useState } from 'react'; import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native'; export type MedicationCardProps = { medication: MedicationDisplayItem; colors: (typeof import('@/constants/Colors').Colors)[keyof typeof import('@/constants/Colors').Colors]; selectedDate: Dayjs; onOpenDetails?: (medication: MedicationDisplayItem) => void; onCelebrate?: () => void; }; export function MedicationCard({ medication, colors, selectedDate, onOpenDetails, onCelebrate }: MedicationCardProps) { const dispatch = useAppDispatch(); const { t } = useI18n(); const [isSubmitting, setIsSubmitting] = useState(false); const [imageError, setImageError] = useState(false); const scheduledDate = dayjs(`${selectedDate.format('YYYY-MM-DD')} ${medication.scheduledTime}`); const timeDiffMinutes = scheduledDate.diff(dayjs(), 'minute'); // 当药品变化时重置图片错误状态 useEffect(() => { setImageError(false); }, [medication.id]); /** * 处理服药操作 */ const handleTakeMedication = async () => { // 检查 recordId 是否存在 if (!medication.recordId || isSubmitting) { return; } // 判断是否早于服药时间1小时以上 if (timeDiffMinutes > 60) { // 显示二次确认弹窗 Alert.alert( t('medications.card.earlyTakeAlert.title'), t('medications.card.earlyTakeAlert.message', { time: medication.scheduledTime }), [ { text: t('medications.card.earlyTakeAlert.cancel'), style: 'cancel', onPress: () => { // 用户取消,不执行任何操作 console.log('用户取消提前服药'); }, }, { text: t('medications.card.earlyTakeAlert.confirm'), style: 'default', onPress: () => { // 用户确认,执行服药逻辑 executeTakeMedication(medication.recordId!); }, }, ] ); } else { // 在正常时间范围内,直接执行服药逻辑 executeTakeMedication(medication.recordId); } }; /** * 执行服药操作(提取公共逻辑) */ const executeTakeMedication = async (recordId: string) => { setIsSubmitting(true); try { // 调用 Redux action 标记为已服用 await dispatch(takeMedicationAction({ recordId: recordId, actualTime: new Date().toISOString(), })).unwrap(); onCelebrate?.(); // 可选:显示成功提示 // Alert.alert('服药成功', '已记录本次服药'); } catch (error) { console.error('[MEDICATION_CARD] 服药操作失败', error); Alert.alert( t('medications.card.takeError.title'), error instanceof Error ? error.message : t('medications.card.takeError.message'), [{ text: t('medications.card.takeError.confirm') }] ); } finally { setIsSubmitting(false); } }; /** * 处理跳过操作 */ const handleSkipMedication = async () => { // 检查 recordId 是否存在 if (!medication.recordId || isSubmitting) { return; } // 显示二次确认弹窗 Alert.alert( t('medications.card.skipAlert.title'), t('medications.card.skipAlert.message'), [ { text: t('medications.card.skipAlert.cancel'), style: 'cancel', onPress: () => { console.log('用户取消跳过'); }, }, { text: t('medications.card.skipAlert.confirm'), style: 'destructive', onPress: () => { executeSkipMedication(medication.recordId!); }, }, ] ); }; /** * 执行跳过操作 */ const executeSkipMedication = async (recordId: string) => { setIsSubmitting(true); try { // 调用 Redux action 标记为已跳过 await dispatch(skipMedicationAction({ recordId: recordId, })).unwrap(); // 可选:显示成功提示 // Alert.alert('跳过成功', '已跳过本次用药'); } catch (error) { console.error('[MEDICATION_CARD] 跳过操作失败', error); Alert.alert( t('medications.card.skipError.title'), error instanceof Error ? error.message : t('medications.card.skipError.message'), [{ text: t('medications.card.skipError.confirm') }] ); } finally { setIsSubmitting(false); } }; const renderStatusBadge = () => { if (medication.status === 'missed') { return ( {t('medications.card.status.missed')} ); } if (medication.status === 'upcoming') { if (timeDiffMinutes <= 0) { return ( {t('medications.card.status.timeToTake')} ); } const hours = Math.floor(timeDiffMinutes / 60); const minutes = timeDiffMinutes % 60; const formatted = hours > 0 ? `${hours}小时${minutes > 0 ? `${minutes}分钟` : ''}` : `${minutes}分钟`; return ( {t('medications.card.status.remaining', { time: formatted })} ); } return null; }; const renderAction = () => { // 已服用状态 if (medication.status === 'taken') { return ( {t('medications.card.action.taken')} ); } // 已跳过状态 if (medication.status === 'skipped') { return ( {t('medications.card.action.skipped')} ); } // 待服用或已错过状态,显示操作按钮 return ( {/* 跳过按钮 */} {isLiquidGlassAvailable() ? ( {t('medications.card.action.skip')} ) : ( {t('medications.card.action.skip')} )} {/* 立即服用按钮 */} {isLiquidGlassAvailable() ? ( {isSubmitting ? t('medications.card.action.submitting') : t('medications.card.action.takeNow')} ) : ( {isSubmitting ? t('medications.card.action.submitting') : t('medications.card.action.takeNow')} )} ); }; const statusChip = renderStatusBadge(); return ( onOpenDetails?.(medication)} disabled={!onOpenDetails} > {statusChip ? {statusChip} : null} setImageError(true)} key={medication.id} // 重新渲染时重置状态 /> {medication.name} {medication.dosage} {medication.scheduledTime} | {medication.frequency} {renderAction()} ); } const styles = StyleSheet.create({ card: { borderRadius: 24, position: 'relative', }, cardSurface: { borderRadius: 24, overflow: 'hidden', }, cardBody: { paddingHorizontal: 10, paddingBottom: 10, paddingTop: 10, }, cardContent: { flexDirection: 'row', alignItems: 'center', gap: 20, }, thumbnailWrapper: { width: 148, height: 110, }, thumbnailSurface: { flex: 1, backgroundColor: '#F1F4FF', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', borderRadius: 24, }, thumbnailImage: { width: '70%', height: '70%', resizeMode: 'contain', }, infoSection: { flex: 1, }, cardTitle: { fontSize: 16, fontWeight: '700', }, cardDosage: { fontSize: 12, marginTop: 4, }, scheduleRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, cardSchedule: { fontSize: 12, }, scheduleIcon: { marginTop: -1, }, actionContainer: { marginTop: 8, }, actionButtonsRow: { flexDirection: 'row', gap: 8, }, skipButtonWrapper: { flex: 1, }, takeButtonWrapper: { flex: 2, }, actionButton: { alignSelf: 'stretch', flexDirection: 'row', alignItems: 'center', gap: 6, justifyContent: 'center', height: 38, borderRadius: 10, overflow: 'hidden', }, actionButtonUpcoming: { backgroundColor: '#1363FF', }, actionButtonTaken: { backgroundColor: '#1FBF4B', }, actionButtonSkipped: { backgroundColor: '#9CA3AF', }, actionButtonSkip: { backgroundColor: '#E5E7EB', }, actionButtonMissed: { backgroundColor: '#9CA3AF', }, fallbackActionButton: { borderWidth: 1, borderColor: 'rgba(19, 99, 255, 0.3)', backgroundColor: 'rgba(19, 99, 255, 0.9)', }, fallbackActionButtonSkip: { borderWidth: 1, borderColor: 'rgba(156, 163, 175, 0.2)', backgroundColor: 'rgba(229, 231, 235, 0.9)', }, fallbackActionButtonMissed: { borderWidth: 1, borderColor: 'rgba(156, 163, 175, 0.3)', backgroundColor: 'rgba(156, 163, 175, 0.9)', }, actionButtonText: { fontSize: 14, fontWeight: '700', color: '#fff', }, actionButtonTextSkip: { fontSize: 14, fontWeight: '600', color: '#6B7280', }, actionButtonTextMissed: { fontSize: 14, fontWeight: '700', color: '#fff', }, statusChipWrapper: { position: 'absolute', top: 0, right: 0, }, statusChip: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 10, height: 28, borderBottomLeftRadius: 20, borderTopRightRadius: 0, borderBottomRightRadius: 0, backgroundColor: '#1363FF', }, statusChipUpcoming: { backgroundColor: '#1363FF', }, statusChipMissed: { backgroundColor: '#FF3B30', }, statusChipText: { fontSize: 10, fontWeight: '600', color: '#fff', }, });