887 lines
25 KiB
TypeScript
887 lines
25 KiB
TypeScript
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)',
|
|
},
|
|
});
|