feat(用药管理): 集成AI智能分析功能,提供用药依从性深度洞察和专业健康建议
This commit is contained in:
@@ -5,6 +5,7 @@ import { TakenMedicationsStack } from '@/components/medication/TakenMedicationsS
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { MedicalDisclaimerSheet } from '@/components/ui/MedicalDisclaimerSheet';
|
||||
import { MedicationAiSummaryInfoSheet } from '@/components/ui/MedicationAiSummaryInfoSheet';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useMembershipModal } from '@/contexts/MembershipModalContext';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
@@ -59,6 +60,7 @@ export default function MedicationsScreen() {
|
||||
const [isCelebrationVisible, setIsCelebrationVisible] = useState(false);
|
||||
const [disclaimerVisible, setDisclaimerVisible] = useState(false);
|
||||
const [pendingAction, setPendingAction] = useState<'manual' | null>(null);
|
||||
const [aiSummaryInfoVisible, setAiSummaryInfoVisible] = useState(false);
|
||||
|
||||
// 从 Redux 获取数据
|
||||
const selectedKey = selectedDate.format('YYYY-MM-DD');
|
||||
@@ -115,6 +117,33 @@ export default function MedicationsScreen() {
|
||||
setPendingAction(null);
|
||||
}, []);
|
||||
|
||||
const handleOpenAiSummary = useCallback(async () => {
|
||||
// 先检查登录状态
|
||||
const isLoggedIn = await ensureLoggedIn();
|
||||
if (!isLoggedIn) return;
|
||||
|
||||
// 检查 VIP 权限
|
||||
const access = checkServiceAccess();
|
||||
if (!access.canUseService) {
|
||||
// 非会员显示介绍弹窗
|
||||
setAiSummaryInfoVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 会员直接跳转到 AI 总结页面
|
||||
router.push('/medications/ai-summary');
|
||||
}, [checkServiceAccess, ensureLoggedIn]);
|
||||
|
||||
const handleAiSummaryInfoConfirm = useCallback(() => {
|
||||
setAiSummaryInfoVisible(false);
|
||||
// 点击"我要订阅"后,弹出会员订阅弹窗
|
||||
openMembershipModal();
|
||||
}, [openMembershipModal]);
|
||||
|
||||
const handleAiSummaryInfoClose = useCallback(() => {
|
||||
setAiSummaryInfoVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleOpenMedicationManagement = useCallback(() => {
|
||||
router.push('/medications/manage-medications');
|
||||
}, []);
|
||||
@@ -285,31 +314,59 @@ export default function MedicationsScreen() {
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View style={styles.headerActions}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleOpenMedicationManagement}
|
||||
>
|
||||
{isLiquidGlassAvailable() ? (
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleOpenAiSummary}
|
||||
>
|
||||
<GlassView
|
||||
style={styles.headerAddButton}
|
||||
glassEffectStyle="clear"
|
||||
tintColor="rgba(255, 255, 255, 0.3)"
|
||||
tintColor="rgba(255, 255, 255, 0.36)"
|
||||
isInteractive={true}
|
||||
>
|
||||
<IconSymbol name="pills.fill" size={18} color="#333" />
|
||||
<IconSymbol name="sparkles" size={18} color="#333" />
|
||||
</GlassView>
|
||||
) : (
|
||||
<View style={[styles.headerAddButton, styles.fallbackAddButton]}>
|
||||
<IconSymbol name="pills.fill" size={18} color="#333" />
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleOpenAiSummary}
|
||||
style={[styles.headerAddButton, styles.fallbackAddButton]}
|
||||
>
|
||||
<IconSymbol name="sparkles" size={18} color="#333" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleAddMedication}
|
||||
>
|
||||
{isLiquidGlassAvailable() ? (
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleOpenMedicationManagement}
|
||||
>
|
||||
<GlassView
|
||||
style={styles.headerAddButton}
|
||||
glassEffectStyle="clear"
|
||||
tintColor="rgba(255, 255, 255, 0.3)"
|
||||
isInteractive={true}
|
||||
>
|
||||
<IconSymbol name="pills.fill" size={18} color="#333" />
|
||||
</GlassView>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleOpenMedicationManagement}
|
||||
style={[styles.headerAddButton, styles.fallbackAddButton]}
|
||||
>
|
||||
<IconSymbol name="pills.fill" size={18} color="#333" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleAddMedication}
|
||||
>
|
||||
<GlassView
|
||||
style={styles.headerAddButton}
|
||||
glassEffectStyle="clear"
|
||||
@@ -318,12 +375,16 @@ export default function MedicationsScreen() {
|
||||
>
|
||||
<IconSymbol name="plus" size={18} color="#333" />
|
||||
</GlassView>
|
||||
) : (
|
||||
<View style={[styles.headerAddButton, styles.fallbackAddButton]}>
|
||||
<IconSymbol name="plus" size={18} color="#333" />
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleAddMedication}
|
||||
style={[styles.headerAddButton, styles.fallbackAddButton]}
|
||||
>
|
||||
<IconSymbol name="plus" size={18} color="#333" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -430,6 +491,13 @@ export default function MedicationsScreen() {
|
||||
onClose={handleDisclaimerClose}
|
||||
onConfirm={handleDisclaimerConfirm}
|
||||
/>
|
||||
|
||||
{/* AI 用药总结介绍弹窗 */}
|
||||
<MedicationAiSummaryInfoSheet
|
||||
visible={aiSummaryInfoVisible}
|
||||
onClose={handleAiSummaryInfoClose}
|
||||
onConfirm={handleAiSummaryInfoConfirm}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
886
app/medications/ai-summary.tsx
Normal file
886
app/medications/ai-summary.tsx
Normal file
@@ -0,0 +1,886 @@
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { getMedicationAiSummary } from '@/services/medications';
|
||||
import { type MedicationAiSummary, type MedicationAiSummaryItem } from '@/types/medication';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export default function MedicationAiSummaryScreen() {
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const [summary, setSummary] = useState<MedicationAiSummary | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [lastUpdated, setLastUpdated] = useState<string>('');
|
||||
const [showInfoModal, setShowInfoModal] = useState(false);
|
||||
const [showCompletionInfoModal, setShowCompletionInfoModal] = useState(false);
|
||||
|
||||
const fetchSummary = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await getMedicationAiSummary();
|
||||
setSummary(data);
|
||||
setLastUpdated(dayjs().format('YYYY.MM.DD HH:mm'));
|
||||
} catch (err: any) {
|
||||
const status = err?.status;
|
||||
if (status === 403) {
|
||||
setError(t('medications.aiSummary.error403'));
|
||||
} else {
|
||||
setError(err?.message || t('medications.aiSummary.genericError'));
|
||||
}
|
||||
setSummary(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
fetchSummary();
|
||||
}, [fetchSummary])
|
||||
);
|
||||
|
||||
const handleExplainRefresh = useCallback(() => {
|
||||
setShowInfoModal(true);
|
||||
}, []);
|
||||
|
||||
const handleExplainCompletion = useCallback(() => {
|
||||
setShowCompletionInfoModal(true);
|
||||
}, []);
|
||||
|
||||
const medicationItems = summary?.medicationAnalysis ?? [];
|
||||
const isEmpty = !loading && !error && medicationItems.length === 0;
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const plannedDoses = medicationItems.reduce((acc, item) => acc + (item.plannedDoses || 0), 0);
|
||||
const takenDoses = medicationItems.reduce((acc, item) => acc + (item.takenDoses || 0), 0);
|
||||
const completion = plannedDoses > 0 ? takenDoses / plannedDoses : 0;
|
||||
const avgCompletion =
|
||||
medicationItems.length > 0
|
||||
? medicationItems.reduce((acc, item) => acc + (item.completionRate || 0), 0) /
|
||||
medicationItems.length
|
||||
: 0;
|
||||
const plannedDays = medicationItems.reduce((acc, item) => acc + (item.plannedDays || 0), 0);
|
||||
|
||||
return {
|
||||
plannedDoses,
|
||||
takenDoses,
|
||||
completion,
|
||||
avgCompletion,
|
||||
plannedDays,
|
||||
activePlans: medicationItems.length,
|
||||
};
|
||||
}, [medicationItems]);
|
||||
|
||||
const completionPercent = Math.min(100, Math.round(stats.completion * 100));
|
||||
|
||||
const renderMedicationCard = (item: MedicationAiSummaryItem) => {
|
||||
const percent = Math.min(100, Math.round((item.completionRate || 0) * 100));
|
||||
return (
|
||||
<View key={item.id} style={styles.planCard}>
|
||||
<View style={styles.planHeader}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<ThemedText style={styles.planName}>{item.name}</ThemedText>
|
||||
<ThemedText style={styles.planMeta}>
|
||||
{t('medications.aiSummary.daysLabel', {
|
||||
days: item.plannedDays,
|
||||
times: item.timesPerDay,
|
||||
})}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View style={styles.planChip}>
|
||||
<IconSymbol name="sparkles" size={14} color="#d6b37f" />
|
||||
<ThemedText style={styles.planChipText}>
|
||||
{t('medications.aiSummary.badges.adherence')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.progressRow}>
|
||||
<View style={styles.progressTrack}>
|
||||
<View style={[styles.progressFill, { width: `${percent}%` }]} />
|
||||
</View>
|
||||
<ThemedText style={styles.progressValue}>
|
||||
{t('medications.aiSummary.completionLabel', { value: percent })}
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
<View style={styles.planFooter}>
|
||||
<ThemedText style={styles.planStat}>
|
||||
{t('medications.aiSummary.doseSummary', {
|
||||
taken: item.takenDoses,
|
||||
planned: item.plannedDoses,
|
||||
})}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.planDate}>
|
||||
{dayjs(item.startDate).format('YYYY.MM.DD')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const headerTitle = (
|
||||
<View style={styles.headerTitle}>
|
||||
<ThemedText style={styles.title}>{t('medications.aiSummary.title')}</ThemedText>
|
||||
<ThemedText style={styles.subtitle}>{t('medications.aiSummary.subtitle')}</ThemedText>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<LinearGradient
|
||||
colors={['#0a0e16', '#0b101a', '#0b0f16']}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<View style={styles.glowTop} />
|
||||
<View style={styles.glowBottom} />
|
||||
|
||||
<HeaderBar
|
||||
title={headerTitle}
|
||||
tone="dark"
|
||||
transparent
|
||||
variant="minimal"
|
||||
right={
|
||||
<TouchableOpacity
|
||||
style={styles.iconButton}
|
||||
onPress={handleExplainRefresh}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<IconSymbol name="info.circle" size={20} color="#dfe8ff" />
|
||||
</TouchableOpacity>
|
||||
}
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
styles.scrollContent,
|
||||
{ paddingBottom: insets.bottom + 32, paddingTop: insets.top + 80 },
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#131a28', '#0f1623']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.heroCard}
|
||||
>
|
||||
<View style={styles.heroHeader}>
|
||||
<ThemedText style={styles.heroLabel}>
|
||||
{t('medications.aiSummary.overviewTitle')}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.updatedAt}>
|
||||
{lastUpdated ? t('medications.aiSummary.updatedAt', { time: lastUpdated }) : ' '}
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
<View style={styles.heroMainRow}>
|
||||
<View style={styles.heroLeft}>
|
||||
<ThemedText style={styles.heroValue}>{completionPercent}%</ThemedText>
|
||||
<ThemedText style={styles.heroCaption}>
|
||||
{t('medications.aiSummary.doseSummary', {
|
||||
taken: stats.takenDoses,
|
||||
planned: stats.plannedDoses,
|
||||
})}
|
||||
</ThemedText>
|
||||
<View style={styles.heroProgressTrack}>
|
||||
<View style={[styles.heroProgressFill, { width: `${completionPercent}%` }]} />
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.heroChip}>
|
||||
<ThemedText style={styles.heroChipLabel}>
|
||||
{t('medications.aiSummary.badges.safety')}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.heroChipValue}>{stats.activePlans}</ThemedText>
|
||||
<ThemedText style={styles.heroChipHint}>
|
||||
{t('medications.aiSummary.stats.activePlans')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.heroStatsRow}>
|
||||
<View style={styles.heroStatItem}>
|
||||
<ThemedText style={styles.heroStatLabel}>
|
||||
{t('medications.aiSummary.stats.avgCompletion')}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.heroStatValue}>
|
||||
{Math.round(stats.avgCompletion * 100)}%
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View style={styles.heroStatItem}>
|
||||
<ThemedText style={styles.heroStatLabel}>
|
||||
{t('medications.aiSummary.stats.activeDays')}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.heroStatValue}>{stats.plannedDays}</ThemedText>
|
||||
</View>
|
||||
<View style={styles.heroStatItem}>
|
||||
<ThemedText style={styles.heroStatLabel}>
|
||||
{t('medications.aiSummary.stats.takenDoses')}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.heroStatValue}>{stats.takenDoses}</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{error ? (
|
||||
<View style={styles.errorCard}>
|
||||
<ThemedText style={styles.errorTitle}>{error}</ThemedText>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={fetchSummary} activeOpacity={0.85}>
|
||||
<ThemedText style={styles.retryText}>{t('medications.aiSummary.retry')}</ThemedText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View style={styles.sectionCard}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<ThemedText style={styles.sectionTitle}>
|
||||
{t('medications.aiSummary.keyInsights')}
|
||||
</ThemedText>
|
||||
<View style={styles.pillChip}>
|
||||
<IconSymbol name="sparkles" size={14} color="#0b0f16" />
|
||||
<ThemedText style={styles.pillChipText}>
|
||||
{t('medications.aiSummary.pillChip')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
<ThemedText style={styles.insightText}>
|
||||
{summary?.keyInsights || t('medications.aiSummary.keyInsightPlaceholder')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
<View style={styles.sectionCard}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<ThemedText style={styles.sectionTitle}>
|
||||
{t('medications.aiSummary.listTitle')}
|
||||
</ThemedText>
|
||||
<TouchableOpacity
|
||||
style={styles.infoIconButton}
|
||||
onPress={handleExplainCompletion}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<IconSymbol name="info.circle" size={16} color="#8b94a8" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{loading ? (
|
||||
<View style={styles.loadingRow}>
|
||||
<ActivityIndicator color="#d6b37f" />
|
||||
<ThemedText style={styles.loadingText}>
|
||||
{t('medications.aiSummary.refresh')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
) : isEmpty ? (
|
||||
<View style={styles.emptyState}>
|
||||
<ThemedText style={styles.emptyTitle}>
|
||||
{t('medications.aiSummary.emptyTitle')}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.emptySubtitle}>
|
||||
{t('medications.aiSummary.emptyDescription')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.planList}>{medicationItems.map(renderMedicationCard)}</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<Modal
|
||||
visible={showInfoModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setShowInfoModal(false)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.infoOverlay}
|
||||
activeOpacity={1}
|
||||
onPress={() => setShowInfoModal(false)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={(e) => e.stopPropagation()}
|
||||
style={styles.infoModal}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#111827', '#0b1220']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.infoGradient}
|
||||
>
|
||||
<View style={styles.infoHeader}>
|
||||
<ThemedText style={styles.infoBadge}>{t('medications.aiSummary.infoModal.badge')}</ThemedText>
|
||||
<ThemedText style={styles.infoTitle}>{t('medications.aiSummary.infoModal.title')}</ThemedText>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowInfoModal(false)}
|
||||
style={styles.infoClose}
|
||||
accessibilityLabel="close"
|
||||
>
|
||||
<IconSymbol name="xmark" size={18} color="#e5e7eb" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.infoContent}>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.infoModal.point1')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.infoModal.point2')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.infoModal.point3')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.infoModal.point4')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.infoButtonContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowInfoModal(false)}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#d6b37f', '#c59b63']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={styles.infoButton}
|
||||
>
|
||||
<Text style={styles.infoButtonText}>{t('medications.aiSummary.infoModal.button')}</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
visible={showCompletionInfoModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setShowCompletionInfoModal(false)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.infoOverlay}
|
||||
activeOpacity={1}
|
||||
onPress={() => setShowCompletionInfoModal(false)}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={(e) => e.stopPropagation()}
|
||||
style={styles.infoModal}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#111827', '#0b1220']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.infoGradient}
|
||||
>
|
||||
<View style={styles.infoHeader}>
|
||||
<ThemedText style={styles.infoBadge}>{t('medications.aiSummary.completionInfoModal.badge')}</ThemedText>
|
||||
<ThemedText style={styles.infoTitle}>{t('medications.aiSummary.completionInfoModal.title')}</ThemedText>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowCompletionInfoModal(false)}
|
||||
style={styles.infoClose}
|
||||
accessibilityLabel="close"
|
||||
>
|
||||
<IconSymbol name="xmark" size={18} color="#e5e7eb" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.infoContent}>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.completionInfoModal.point1')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.completionInfoModal.point2')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.completionInfoModal.point3')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.completionInfoModal.point4')}
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{t('medications.aiSummary.completionInfoModal.point5')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.infoButtonContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowCompletionInfoModal(false)}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#d6b37f', '#c59b63']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={styles.infoButton}
|
||||
>
|
||||
<Text style={styles.infoButtonText}>{t('medications.aiSummary.completionInfoModal.button')}</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#0b0f16',
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 20,
|
||||
gap: 20,
|
||||
},
|
||||
glowTop: {
|
||||
position: 'absolute',
|
||||
top: -80,
|
||||
left: -40,
|
||||
width: 200,
|
||||
height: 200,
|
||||
backgroundColor: '#1b2a44',
|
||||
opacity: 0.35,
|
||||
borderRadius: 140,
|
||||
},
|
||||
glowBottom: {
|
||||
position: 'absolute',
|
||||
bottom: -120,
|
||||
right: -60,
|
||||
width: 240,
|
||||
height: 240,
|
||||
backgroundColor: '#123125',
|
||||
opacity: 0.25,
|
||||
borderRadius: 200,
|
||||
},
|
||||
iconButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.08)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(255,255,255,0.04)',
|
||||
},
|
||||
headerTitle: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
gap: 6,
|
||||
},
|
||||
badge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
backgroundColor: '#d6b37f',
|
||||
},
|
||||
badgeText: {
|
||||
color: '#0b0f16',
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
title: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 22,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
subtitle: {
|
||||
color: '#b9c2d3',
|
||||
fontSize: 14,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
heroCard: {
|
||||
borderRadius: 24,
|
||||
padding: 18,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.06)',
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 16,
|
||||
gap: 14,
|
||||
},
|
||||
heroHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
heroLabel: {
|
||||
color: '#f5f6fb',
|
||||
fontSize: 16,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
updatedAt: {
|
||||
color: '#8b94a8',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
heroMainRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
},
|
||||
heroLeft: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
heroValue: {
|
||||
color: '#36d0a5',
|
||||
fontSize: 38,
|
||||
lineHeight: 42,
|
||||
fontFamily: 'AliBold',
|
||||
letterSpacing: 0.5,
|
||||
flexShrink: 1,
|
||||
},
|
||||
heroCaption: {
|
||||
color: '#c2ccdf',
|
||||
fontSize: 13,
|
||||
fontFamily: 'AliRegular',
|
||||
marginTop: 4,
|
||||
},
|
||||
heroProgressTrack: {
|
||||
marginTop: 12,
|
||||
height: 10,
|
||||
borderRadius: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.08)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
heroProgressFill: {
|
||||
height: '100%',
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#36d0a5',
|
||||
},
|
||||
heroChip: {
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 18,
|
||||
backgroundColor: 'rgba(214, 179, 127, 0.12)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(214, 179, 127, 0.3)',
|
||||
minWidth: 120,
|
||||
alignItems: 'flex-start',
|
||||
gap: 4,
|
||||
},
|
||||
heroChipLabel: {
|
||||
color: '#d6b37f',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
heroChipValue: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 20,
|
||||
fontFamily: 'AliBold',
|
||||
lineHeight: 24,
|
||||
},
|
||||
heroChipHint: {
|
||||
color: '#b9c2d3',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
heroStatsRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
heroStatItem: {
|
||||
flex: 1,
|
||||
padding: 12,
|
||||
borderRadius: 14,
|
||||
backgroundColor: 'rgba(255,255,255,0.04)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.04)',
|
||||
},
|
||||
heroStatLabel: {
|
||||
color: '#9dabc4',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
heroStatValue: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 18,
|
||||
marginTop: 6,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
sectionCard: {
|
||||
borderRadius: 20,
|
||||
padding: 16,
|
||||
backgroundColor: 'rgba(255,255,255,0.03)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.05)',
|
||||
gap: 12,
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
sectionTitle: {
|
||||
color: '#f5f6fb',
|
||||
fontSize: 16,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
pillChip: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
backgroundColor: '#d6b37f',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
},
|
||||
pillChipText: {
|
||||
color: '#0b0f16',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
insightText: {
|
||||
color: '#d9e2f2',
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
planList: {
|
||||
gap: 12,
|
||||
},
|
||||
planCard: {
|
||||
borderRadius: 16,
|
||||
padding: 14,
|
||||
backgroundColor: 'rgba(255,255,255,0.04)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.06)',
|
||||
gap: 10,
|
||||
},
|
||||
planHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
planName: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 16,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
planMeta: {
|
||||
color: '#9dabc4',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliRegular',
|
||||
marginTop: 2,
|
||||
},
|
||||
planChip: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
backgroundColor: 'rgba(214, 179, 127, 0.15)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(214, 179, 127, 0.35)',
|
||||
},
|
||||
planChipText: {
|
||||
color: '#d6b37f',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
progressRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
progressTrack: {
|
||||
flex: 1,
|
||||
height: 10,
|
||||
borderRadius: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.08)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
progressFill: {
|
||||
height: '100%',
|
||||
backgroundColor: '#36d0a5',
|
||||
borderRadius: 10,
|
||||
},
|
||||
progressValue: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
planFooter: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
planStat: {
|
||||
color: '#c7d1e4',
|
||||
fontSize: 13,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
planDate: {
|
||||
color: '#7f8aa4',
|
||||
fontSize: 12,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
errorCard: {
|
||||
padding: 16,
|
||||
borderRadius: 16,
|
||||
backgroundColor: 'rgba(255, 86, 86, 0.08)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 86, 86, 0.3)',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
errorTitle: {
|
||||
color: '#ff9c9c',
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
retryButton: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 999,
|
||||
backgroundColor: '#ff9c9c',
|
||||
},
|
||||
retryText: {
|
||||
color: '#0b0f16',
|
||||
fontSize: 13,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
loadingRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
loadingText: {
|
||||
color: '#c7d1e4',
|
||||
fontSize: 13,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
emptyState: {
|
||||
paddingVertical: 12,
|
||||
gap: 6,
|
||||
},
|
||||
emptyTitle: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 15,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
emptySubtitle: {
|
||||
color: '#9dabc4',
|
||||
fontSize: 13,
|
||||
fontFamily: 'AliRegular',
|
||||
lineHeight: 20,
|
||||
},
|
||||
infoOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
infoModal: {
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
borderRadius: 24,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.1)',
|
||||
},
|
||||
infoGradient: {
|
||||
padding: 24,
|
||||
gap: 20,
|
||||
},
|
||||
infoHeader: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 4,
|
||||
},
|
||||
infoBadge: {
|
||||
color: '#d6b37f',
|
||||
fontSize: 24,
|
||||
lineHeight: 28,
|
||||
fontFamily: 'AliBold',
|
||||
marginBottom: 10,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
infoTitle: {
|
||||
color: '#f6f7fb',
|
||||
fontSize: 16,
|
||||
fontFamily: 'AliBold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
infoClose: {
|
||||
position: 'absolute',
|
||||
right: -4,
|
||||
top: -4,
|
||||
padding: 8,
|
||||
width: 36,
|
||||
height: 36,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 18,
|
||||
backgroundColor: 'rgba(255,255,255,0.05)',
|
||||
},
|
||||
infoContent: {
|
||||
gap: 14,
|
||||
},
|
||||
infoText: {
|
||||
color: '#d9e2f2',
|
||||
fontSize: 14,
|
||||
lineHeight: 18,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
infoButtonContainer: {
|
||||
marginTop: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
infoButtonWrapper: {
|
||||
// minWidth: 120,
|
||||
// maxWidth: 180,
|
||||
},
|
||||
infoButton: {
|
||||
borderRadius: 12,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 28,
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
infoButtonGlass: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 28,
|
||||
alignItems: 'center',
|
||||
},
|
||||
infoButtonText: {
|
||||
color: '#0b0f16',
|
||||
fontSize: 15,
|
||||
fontFamily: 'AliBold',
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
infoIconButton: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(139, 148, 168, 0.1)',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user