diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index df122a3..f487ad2 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -12,7 +12,9 @@ import { IconSymbol } from '@/components/ui/IconSymbol';
import { Colors } from '@/constants/Colors';
import { ROUTES } from '@/constants/Routes';
import { TAB_BAR_BOTTOM_OFFSET, TAB_BAR_HEIGHT } from '@/constants/TabBar';
+import { useAppSelector } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
+import { selectEnabledTabs } from '@/store/tabBarConfigSlice';
// Tab configuration
type TabConfig = {
@@ -34,6 +36,9 @@ export default function TabLayout() {
const colorTokens = Colors[theme];
const pathname = usePathname();
const glassEffectAvailable = isLiquidGlassAvailable();
+
+ // 获取已启用的标签配置(按自定义顺序)
+ const enabledTabs = useAppSelector(selectEnabledTabs);
// Helper function to determine if a tab is selected
const isTabSelected = (routeName: string): boolean => {
@@ -174,42 +179,45 @@ export default function TabLayout() {
tabBarShowLabel: false,
});
+ // 根据配置渲染标签页
if (glassEffectAvailable) {
- return
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ return (
+
+ {enabledTabs.map((tab) => {
+ const tabConfig = TAB_CONFIGS[tab.id];
+ if (!tabConfig) return null;
+
+ return (
+
+
+
+
+ );
+ })}
+
+ );
}
+ // 确定初始路由(第一个启用的标签)
+ const initialRouteName = enabledTabs.length > 0 ? enabledTabs[0].id : 'statistics';
+
return (
getScreenOptions(route.name)}
>
-
-
-
-
-
-
+ {enabledTabs.map((tab) => {
+ const tabConfig = TAB_CONFIGS[tab.id];
+ if (!tabConfig) return null;
+
+ return (
+
+ );
+ })}
);
}
diff --git a/app/(tabs)/medications.tsx b/app/(tabs)/medications.tsx
index 7420d16..88178fc 100644
--- a/app/(tabs)/medications.tsx
+++ b/app/(tabs)/medications.tsx
@@ -1,6 +1,7 @@
import CelebrationAnimation, { CelebrationAnimationRef } from '@/components/CelebrationAnimation';
import { DateSelector } from '@/components/DateSelector';
import { MedicationCard } from '@/components/medication/MedicationCard';
+import { TakenMedicationsStack } from '@/components/medication/TakenMedicationsStack';
import { ThemedText } from '@/components/ThemedText';
import { IconSymbol } from '@/components/ui/IconSymbol';
import { MedicalDisclaimerSheet } from '@/components/ui/MedicalDisclaimerSheet';
@@ -189,6 +190,16 @@ export default function MedicationsScreen() {
return medicationsWithImages.filter((item: any) => item.status === activeFilter);
}, [activeFilter, medicationsWithImages]);
+ const activeMedications = useMemo(() => {
+ if (activeFilter !== 'all') return filteredMedications;
+ return filteredMedications.filter((item: any) => item.status !== 'taken' && item.status !== 'skipped');
+ }, [activeFilter, filteredMedications]);
+
+ const completedMedications = useMemo(() => {
+ if (activeFilter !== 'all') return [];
+ return filteredMedications.filter((item: any) => item.status === 'taken' || item.status === 'skipped');
+ }, [activeFilter, filteredMedications]);
+
const counts = useMemo(() => {
const taken = medicationsWithImages.filter((item: any) => item.status === 'taken').length;
// "未服用"计数包含 missed(已错过)和 upcoming(待服用)
@@ -354,7 +365,8 @@ export default function MedicationsScreen() {
) : (
- {filteredMedications.map((item: any) => (
+ {/* 渲染未服用的药物 */}
+ {activeMedications.map((item: any) => (
))}
+
+ {/* 渲染已完成(服用/跳过)的药物堆叠 */}
+ {completedMedications.length > 0 && (
+ handleOpenMedicationDetails(item.medicationId)}
+ onCelebrate={handleMedicationTakenCelebration}
+ />
+ )}
)}
diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx
index a43f50f..e107b81 100644
--- a/app/(tabs)/personal.tsx
+++ b/app/(tabs)/personal.tsx
@@ -1,6 +1,7 @@
import ActivityHeatMap from '@/components/ActivityHeatMap';
import { BadgeShowcaseModal } from '@/components/badges/BadgeShowcaseModal';
import { PRIVACY_POLICY_URL, USER_AGREEMENT_URL } from '@/constants/Agree';
+import { palette } from '@/constants/Colors';
import { ROUTES } from '@/constants/Routes';
import { getTabBarBottomPadding } from '@/constants/TabBar';
import { useMembershipModal } from '@/contexts/MembershipModalContext';
@@ -586,6 +587,16 @@ export default function PersonalScreen() {
},
],
},
+ {
+ title: t('personal.sections.customization'),
+ items: [
+ {
+ icon: 'albums-outline' as React.ComponentProps['name'],
+ title: t('personal.menu.tabBarConfig'),
+ onPress: () => router.push(ROUTES.TAB_BAR_CONFIG),
+ },
+ ],
+ },
// 开发者section(需要连续点击三次用户名激活)
...(showDeveloperSection ? [{
title: t('personal.sections.developer'),
@@ -698,16 +709,12 @@ export default function PersonalScreen() {
{/* 背景渐变 */}
- {/* 装饰性圆圈 */}
-
-
-
{/* 左边logo */}
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 436cc66..3f78be0 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -35,6 +35,7 @@ import { useAuthGuard } from '@/hooks/useAuthGuard';
import { STORAGE_KEYS, setUnauthorizedHandler } from '@/services/api';
import { BackgroundTaskManager } from '@/services/backgroundTaskManagerV2';
import { fetchChallenges } from '@/store/challengesSlice';
+import { loadTabBarConfigs } from '@/store/tabBarConfigSlice';
import AsyncStorage from '@/utils/kvStore';
import { logger } from '@/utils/logger';
import { Provider } from 'react-redux';
@@ -120,6 +121,11 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
}
}, [isLoggedIn]);
+ // 初始化底部栏配置
+ useEffect(() => {
+ dispatch(loadTabBarConfigs());
+ }, [dispatch]);
+
// ==================== 基础服务初始化(不需要权限,总是执行)====================
React.useEffect(() => {
const initializeBasicServices = async () => {
@@ -515,6 +521,7 @@ export default function RootLayout() {
options={{ headerShown: false }}
/>
+
diff --git a/app/medications/[medicationId].tsx b/app/medications/[medicationId].tsx
index f537695..050d278 100644
--- a/app/medications/[medicationId].tsx
+++ b/app/medications/[medicationId].tsx
@@ -34,6 +34,7 @@ import { Image } from 'expo-image';
import * as ImagePicker from 'expo-image-picker';
import { LinearGradient } from 'expo-linear-gradient';
import { useLocalSearchParams, useRouter } from 'expo-router';
+import LottieView from 'lottie-react-native';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
@@ -1299,8 +1300,13 @@ export default function MedicationDetailScreen() {
{aiAnalysisLoading && (
-
-
+
+
{t('medications.detail.aiAnalysis.analyzing')}
@@ -2276,13 +2282,19 @@ const styles = StyleSheet.create({
fontSize: 12,
fontWeight: '700',
},
- aiLoadingRow: {
- flexDirection: 'row',
+ aiLoadingContainer: {
alignItems: 'center',
- gap: 8,
+ justifyContent: 'center',
+ paddingVertical: 24,
+ gap: 12,
+ },
+ aiLoadingAnimation: {
+ width: 120,
+ height: 120,
},
aiLoadingText: {
- fontSize: 13,
+ fontSize: 14,
+ fontWeight: '500',
},
aiHeroRow: {
flexDirection: 'row',
diff --git a/app/settings/tab-bar-config.tsx b/app/settings/tab-bar-config.tsx
new file mode 100644
index 0000000..c1fd530
--- /dev/null
+++ b/app/settings/tab-bar-config.tsx
@@ -0,0 +1,266 @@
+import { useAppDispatch, useAppSelector } from '@/hooks/redux';
+import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
+import {
+ resetToDefault,
+ selectTabBarConfigs,
+ toggleTabEnabled,
+ type TabConfig,
+} from '@/store/tabBarConfigSlice';
+import { Ionicons } from '@expo/vector-icons';
+import { isLiquidGlassAvailable } from 'expo-glass-effect';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useRouter } from 'expo-router';
+import React, { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ Alert,
+ ScrollView,
+ StyleSheet,
+ Switch,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+
+import { HeaderBar } from '@/components/ui/HeaderBar';
+import { IconSymbol } from '@/components/ui/IconSymbol';
+import { palette } from '@/constants/Colors';
+
+export default function TabBarConfigScreen() {
+ const { t } = useTranslation();
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const safeAreaTop = useSafeAreaTop(60);
+ const configs = useAppSelector(selectTabBarConfigs);
+ const isGlassAvailable = isLiquidGlassAvailable();
+
+ // 处理开关切换
+ const handleToggle = useCallback(
+ (tabId: string) => {
+ dispatch(toggleTabEnabled(tabId));
+ },
+ [dispatch]
+ );
+
+ // 恢复默认设置
+ const handleReset = useCallback(() => {
+ Alert.alert(
+ t('personal.tabBarConfig.resetConfirm.title'),
+ t('personal.tabBarConfig.resetConfirm.message'),
+ [
+ {
+ text: t('personal.tabBarConfig.resetConfirm.cancel'),
+ style: 'cancel',
+ },
+ {
+ text: t('personal.tabBarConfig.resetConfirm.confirm'),
+ style: 'destructive',
+ onPress: () => {
+ dispatch(resetToDefault());
+ Alert.alert('', t('personal.tabBarConfig.resetSuccess'));
+ },
+ },
+ ]
+ );
+ }, [dispatch, t]);
+
+ // 渲染单个 Tab 行
+ const renderTabRow = useCallback(
+ (item: TabConfig, index: number, total: number) => {
+ return (
+
+
+ {/* Tab 图标和名称 */}
+
+
+
+
+
+ {t(item.titleKey)}
+ {!item.canBeDisabled && (
+
+ {t('personal.tabBarConfig.cannotDisable')}
+
+ )}
+
+
+
+ {/* 开关 */}
+ handleToggle(item.id)}
+ disabled={!item.canBeDisabled}
+ trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
+ thumbColor="#FFFFFF"
+ style={styles.switch}
+ />
+
+
+ {/* 分割线 - 最后一项不显示 */}
+ {index < total - 1 && (
+
+
+
+ )}
+
+ );
+ },
+ [handleToggle, t]
+ );
+
+ return (
+
+
+
+ {/* 顶部导航栏 */}
+ router.back()}
+ right={
+
+
+ {t('personal.tabBarConfig.resetButton')}
+
+
+ }
+ />
+
+ {/* 主内容区 */}
+
+ {/* 说明区域 */}
+
+ {t('personal.tabBarConfig.subtitle')}
+
+
+
+
+ {t('personal.tabBarConfig.description')}
+
+
+
+
+
+ {/* Tab 列表 - 聚合在一个卡片中 */}
+
+ {configs.map((item, index) => renderTabRow(item, index, configs.length))}
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#F5F5F5',
+ },
+ gradientBackground: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ height: '60%', // 渐变覆盖上半部分即可
+ },
+ content: {
+ flex: 1,
+ },
+ scrollContent: {
+ paddingHorizontal: 16,
+ paddingBottom: 40,
+ },
+ headerSection: {
+ marginBottom: 16,
+ },
+ subtitle: {
+ fontSize: 14,
+ color: '#6C757D',
+ marginBottom: 12,
+ },
+ descriptionCard: {
+ backgroundColor: 'rgba(255, 255, 255, 0.6)',
+ borderRadius: 12,
+ padding: 12,
+ gap: 8,
+ borderWidth: 1,
+ borderColor: 'rgba(147, 112, 219, 0.1)',
+ },
+ hintRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ descriptionText: {
+ flex: 1,
+ fontSize: 13,
+ color: '#2C3E50',
+ lineHeight: 18,
+ },
+ sectionContainer: {
+ backgroundColor: '#FFFFFF',
+ borderRadius: 20,
+ marginBottom: 20,
+ overflow: 'hidden',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.03,
+ shadowRadius: 8,
+ elevation: 2,
+ },
+ tabItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 16,
+ paddingVertical: 16,
+ },
+ separatorContainer: {
+ paddingLeft: 68, // 40(icon) + 12(gap) + 16(padding)
+ paddingRight: 16,
+ },
+ separator: {
+ height: 1,
+ backgroundColor: '#F0F0F0',
+ },
+ tabInfo: {
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 12,
+ },
+ iconContainer: {
+ width: 40,
+ height: 40,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ tabTextContainer: {
+ flex: 1,
+ },
+ tabTitle: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#2C3E50',
+ marginBottom: 2,
+ },
+ tabSubtitle: {
+ fontSize: 12,
+ color: '#9370DB',
+ },
+ switch: {
+ transform: [{ scaleX: 0.9 }, { scaleY: 0.9 }],
+ },
+ headerRightButton: {
+ fontSize: 15,
+ fontWeight: '600',
+ color: '#9370DB', // 使用主色调
+ },
+});
\ No newline at end of file
diff --git a/assets/logo.png b/assets/logo.png
index 03b906b..cf3a6a8 100644
Binary files a/assets/logo.png and b/assets/logo.png differ
diff --git a/assets/lottie/loading-blue.json b/assets/lottie/loading-blue.json
new file mode 100644
index 0000000..deadbc9
--- /dev/null
+++ b/assets/lottie/loading-blue.json
@@ -0,0 +1 @@
+{"v":"5.6.10","fr":30,"ip":30,"op":210,"w":800,"h":600,"nm":"åæ 1","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"å½¢ç¶å¾å± 18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":340,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":30,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"å½¢ç¶å¾å± 17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":320,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":40,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":10,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"å½¢ç¶å¾å± 16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":300,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"t":50,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":20,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"å½¢ç¶å¾å± 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":280,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"t":60,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":30,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"å½¢ç¶å¾å± 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":260,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[0]},{"t":70,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":40,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"å½¢ç¶å¾å± 13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":240,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[0]},{"t":80,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":50,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"å½¢ç¶å¾å± 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":220,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"t":90,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":60,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"å½¢ç¶å¾å± 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":200,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":70,"s":[0]},{"t":100,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":70,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"å½¢ç¶å¾å± 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":80,"s":[0]},{"t":110,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":80,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"å½¢ç¶å¾å± 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":160,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[0]},{"t":120,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":90,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"å½¢ç¶å¾å± 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":140,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":100,"s":[0]},{"t":130,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":100,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"å½¢ç¶å¾å± 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":120,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":110,"s":[0]},{"t":140,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":110,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"å½¢ç¶å¾å± 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":100,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[0]},{"t":150,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":420,"st":120,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"å½¢ç¶å¾å± 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":80,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":130,"s":[0]},{"t":160,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":430,"st":130,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"å½¢ç¶å¾å± 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":60,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[0]},{"t":170,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":440,"st":140,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"å½¢ç¶å¾å± 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":40,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":450,"st":150,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"å½¢ç¶å¾å± 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":20,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":160,"s":[0]},{"t":190,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":460,"st":160,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"å½¢ç¶å¾å± 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":170,"s":[0]},{"t":200,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":470,"st":170,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"å½¢ç¶å¾å± 24","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":340,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":180,"s":[0]},{"t":210,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":301,"st":180,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"å½¢ç¶å¾å± 23","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":320,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":190,"s":[0]},{"t":220,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":301,"st":190,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"å½¢ç¶å¾å± 22","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":300,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":200,"s":[0]},{"t":230,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":301,"st":200,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"å½¢ç¶å¾å± 21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":280,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":210,"s":[0]},{"t":240,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":301,"st":210,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"å½¢ç¶å¾å± 20","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":260,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":220,"s":[0]},{"t":250,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":301,"st":220,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":"å½¢ç¶å¾å± 19","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":240,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[1.258,2.078,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[200,200],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"æ¤åè·¯å¾ 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":5,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":230,"s":[0]},{"t":260,"s":[120]}],"ix":3},"m":1,"ix":2,"nm":"ä¿®åªè·¯å¾ 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.09019607843137255,0.3843137254901961,0.8745098039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"æè¾¹ 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.258,2.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"忢"}],"nm":"æ¤å 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":301,"st":230,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"é¢åæ 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,300,0],"ix":2},"a":{"a":0,"k":[400,300,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"ç®åé»å¡å·¥å
·","np":4,"mn":"ADBE Simple Choker","ix":1,"en":1,"ef":[{"ty":7,"nm":"è§å¾","mn":"ADBE Simple Choker-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{"ty":0,"nm":"é»å¡é®ç½©","mn":"ADBE Simple Choker-0002","ix":2,"v":{"a":0,"k":14,"ix":2}}]},{"ty":5,"nm":"梯度æ¸å","np":10,"mn":"ADBE Ramp","ix":2,"en":1,"ef":[{"ty":3,"nm":"æ¸åèµ·ç¹","mn":"ADBE Ramp-0001","ix":1,"v":{"a":0,"k":[400,0],"ix":1}},{"ty":2,"nm":"èµ·å§é¢è²","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[0.263066768646,1,0.867243647575,1],"ix":2}},{"ty":3,"nm":"æ¸åç»ç¹","mn":"ADBE Ramp-0003","ix":3,"v":{"a":0,"k":[400,600],"ix":3}},{"ty":2,"nm":"ç»æé¢è²","mn":"ADBE Ramp-0004","ix":4,"v":{"a":0,"k":[0.354580283165,1,0.820018112659,1],"ix":4}},{"ty":7,"nm":"æ¸åå½¢ç¶","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"æ¸åæ£å°","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"ä¸åå§å¾åæ··å","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]}],"w":800,"h":600,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"é¢åæ 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":56,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,320,0],"ix":2},"a":{"a":0,"k":[400,300,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"ç®åé»å¡å·¥å
·","np":4,"mn":"ADBE Simple Choker","ix":1,"en":1,"ef":[{"ty":7,"nm":"è§å¾","mn":"ADBE Simple Choker-0001","ix":1,"v":{"a":0,"k":1,"ix":1}},{"ty":0,"nm":"é»å¡é®ç½©","mn":"ADBE Simple Choker-0002","ix":2,"v":{"a":0,"k":14,"ix":2}}]},{"ty":5,"nm":"梯度æ¸å","np":10,"mn":"ADBE Ramp","ix":2,"en":1,"ef":[{"ty":3,"nm":"æ¸åèµ·ç¹","mn":"ADBE Ramp-0001","ix":1,"v":{"a":0,"k":[400,0],"ix":1}},{"ty":2,"nm":"èµ·å§é¢è²","mn":"ADBE Ramp-0002","ix":2,"v":{"a":0,"k":[0.263066768646,1,0.867243647575,1],"ix":2}},{"ty":3,"nm":"æ¸åç»ç¹","mn":"ADBE Ramp-0003","ix":3,"v":{"a":0,"k":[400,600],"ix":3}},{"ty":2,"nm":"ç»æé¢è²","mn":"ADBE Ramp-0004","ix":4,"v":{"a":0,"k":[0.354580283165,1,0.820018112659,1],"ix":4}},{"ty":7,"nm":"æ¸åå½¢ç¶","mn":"ADBE Ramp-0005","ix":5,"v":{"a":0,"k":1,"ix":5}},{"ty":0,"nm":"æ¸åæ£å°","mn":"ADBE Ramp-0006","ix":6,"v":{"a":0,"k":0,"ix":6}},{"ty":0,"nm":"ä¸åå§å¾åæ··å","mn":"ADBE Ramp-0007","ix":7,"v":{"a":0,"k":0,"ix":7}},{"ty":6,"nm":"","mn":"ADBE Ramp-0008","ix":8,"v":0}]},{"ty":29,"nm":"髿¯æ¨¡ç³","np":5,"mn":"ADBE Gaussian Blur 2","ix":3,"en":1,"ef":[{"ty":0,"nm":"模ç³åº¦","mn":"ADBE Gaussian Blur 2-0001","ix":1,"v":{"a":0,"k":41.3,"ix":1}},{"ty":7,"nm":"æ¨¡ç³æ¹å","mn":"ADBE Gaussian Blur 2-0002","ix":2,"v":{"a":0,"k":1,"ix":2}},{"ty":7,"nm":"éå¤è¾¹ç¼åç´ ","mn":"ADBE Gaussian Blur 2-0003","ix":3,"v":{"a":0,"k":0,"ix":3}}]}],"w":800,"h":600,"ip":0,"op":300,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/assets/machine.png b/assets/machine.png
new file mode 100644
index 0000000..0882900
Binary files /dev/null and b/assets/machine.png differ
diff --git a/components/medication/MedicationCard.tsx b/components/medication/MedicationCard.tsx
index 6b71f3a..aae7b55 100644
--- a/components/medication/MedicationCard.tsx
+++ b/components/medication/MedicationCard.tsx
@@ -327,11 +327,11 @@ export function MedicationCard({ medication, colors, selectedDate, onOpenDetails
const styles = StyleSheet.create({
card: {
- borderRadius: 18,
+ borderRadius: 24,
position: 'relative',
},
cardSurface: {
- borderRadius: 18,
+ borderRadius: 24,
overflow: 'hidden',
},
cardBody: {
@@ -354,7 +354,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
- borderRadius: 18,
+ borderRadius: 24,
},
thumbnailImage: {
width: '70%',
diff --git a/components/medication/TakenMedicationsStack.tsx b/components/medication/TakenMedicationsStack.tsx
new file mode 100644
index 0000000..4a5d926
--- /dev/null
+++ b/components/medication/TakenMedicationsStack.tsx
@@ -0,0 +1,272 @@
+import { useI18n } from '@/hooks/useI18n';
+import type { MedicationDisplayItem } from '@/types/medication';
+import React, { useEffect } from 'react';
+import { StyleSheet, TouchableOpacity, View } from 'react-native';
+import Animated, {
+ Extrapolation,
+ interpolate,
+ type SharedValue,
+ useAnimatedStyle,
+ useSharedValue,
+ withSpring,
+} from 'react-native-reanimated';
+import { MedicationCard } from './MedicationCard';
+
+type Props = {
+ medications: MedicationDisplayItem[];
+ colors: (typeof import('@/constants/Colors').Colors)[keyof typeof import('@/constants/Colors').Colors];
+ selectedDate: any;
+ onOpenDetails: (medication: MedicationDisplayItem) => void;
+ onCelebrate?: () => void;
+};
+
+const STACK_OFFSET = 12;
+const STACK_SCALE_STEP = 0.04;
+const MAX_STACK_VISIBLE = 3;
+
+export function TakenMedicationsStack({
+ medications,
+ colors,
+ selectedDate,
+ onOpenDetails,
+ onCelebrate,
+}: Props) {
+ const { t } = useI18n();
+ const [isExpanded, setIsExpanded] = React.useState(false);
+ const progress = useSharedValue(0);
+
+ useEffect(() => {
+ progress.value = withSpring(isExpanded ? 1 : 0, {
+ damping: 20,
+ stiffness: 200, // Faster spring
+ mass: 0.8,
+ });
+ }, [isExpanded, progress]);
+
+ const handleToggle = () => {
+ setIsExpanded(!isExpanded);
+ };
+
+ // Header arrow rotation style
+ const arrowStyle = useAnimatedStyle(() => {
+ return {
+ transform: [
+ {
+ rotate: `${interpolate(progress.value, [0, 1], [0, 180])}deg`,
+ },
+ ],
+ };
+ });
+
+ if (medications.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {/* Stack/List Container */}
+
+ {medications.map((item, index) => (
+
+ ))}
+
+
+ );
+}
+
+const CardItem = ({
+ item,
+ index,
+ total,
+ progress,
+ isExpanded,
+ colors,
+ selectedDate,
+ onOpenDetails,
+ onCelebrate,
+ onToggle,
+}: {
+ item: MedicationDisplayItem;
+ index: number;
+ total: number;
+ progress: SharedValue;
+ isExpanded: boolean;
+ colors: (typeof import('@/constants/Colors').Colors)[keyof typeof import('@/constants/Colors').Colors];
+ selectedDate: any;
+ onOpenDetails: (medication: MedicationDisplayItem) => void;
+ onCelebrate?: () => void;
+ onToggle: () => void;
+}) => {
+ // Only render top 3 cards when collapsed to save performance/visuals
+ // But we need to render all when expanding.
+ // We'll hide index >= MAX_STACK_VISIBLE when collapsed via opacity/zIndex.
+
+ const style = useAnimatedStyle(() => {
+ // Stack state (progress = 0)
+ const stackTranslateY = index * STACK_OFFSET;
+ const stackScale = 1 - index * STACK_SCALE_STEP;
+ const stackOpacity = index < MAX_STACK_VISIBLE ? 1 - index * 0.15 : 0;
+ const stackZIndex = total - index;
+
+ // List state (progress = 1)
+ // In list state, we rely on layout (relative positioning).
+ // However, to animate smoothly from absolute (stack) to relative (list),
+ // we need a strategy.
+ // Strategy: Always Absolute? No, height is dynamic.
+ // Strategy: Use negative margins for stack?
+
+ // Let's try:
+ // Collapsed: marginTop = -(height - offset).
+ // Expanded: marginTop = 16 (gap).
+ // But we don't know height.
+
+ // Alternative:
+ // Use 'top' offset relative to the first card?
+ // This is hard without measuring.
+
+ // Let's go with the "Transform" approach assuming standard card height for the stack effect,
+ // but switching to relative layout when expanded.
+ // Wait, switching 'position' prop is not animatable by useAnimatedStyle directly (requires Layout Animation).
+
+ // Let's keep it simple:
+ // When collapsed (progress 0):
+ // Items > 0 are absolutely positioned relative to the container (which wraps them all).
+ // Item 0 is relative.
+ // When expanded (progress 1):
+ // All items are relative.
+
+ // To smooth this, we can use interpolate for translateY.
+
+ return {
+ zIndex: stackZIndex,
+ opacity: interpolate(progress.value, [0, 1], [stackOpacity, 1]),
+ transform: [
+ {
+ scale: interpolate(progress.value, [0, 1], [stackScale, 1]),
+ },
+ {
+ translateY: interpolate(
+ progress.value,
+ [0, 1],
+ [stackTranslateY, 0] // In stack, they go down. In list, translation is 0 (relative flow handles pos).
+ ),
+ },
+ ],
+ };
+ });
+
+ // Logic for positioning:
+ // We'll use a container View for each card.
+ // When collapsed, the container height for index > 0 should be 0?
+ // That would pull them up.
+
+ const containerStyle = useAnimatedStyle(() => {
+ // We can animate the height of the wrapper view.
+ // But we don't know the content height.
+ // Assuming ~140px for card.
+ const approxHeight = 140;
+
+ if (index === 0) return {}; // First card always takes space
+
+ // For others:
+ // Collapsed: height is 0 (so they stack on top of first one, roughly)
+ // Expanded: height is 'auto' (we can't animate to auto easily in RN without LayoutAnimation)
+
+ return {
+ marginTop: interpolate(progress.value, [0, 1], [-approxHeight + STACK_OFFSET, 16], Extrapolation.CLAMP),
+ };
+ });
+
+ // Using Layout Animation for the actual position change support
+ // requires the parent to handle it.
+
+ // Simpler Visual Hack:
+ // When collapsed, we just set marginTop to a negative value that overlaps them.
+ // Since MedicationCard is roughly constant height, we can tune this.
+ // MedicationCard height is roughly 130-150.
+ // Let's guess -130 + 12.
+
+ const cardContainerStyle = useAnimatedStyle(() => {
+ // We assume a fixed height for the negative margin calculation logic.
+ // A better way is needed if heights vary wildly.
+ // But for now, let's use a safe estimated overlap.
+ const cardHeight = 140;
+ const collapsedMarginTop = index === 0 ? 0 : -(cardHeight - STACK_OFFSET);
+ const expandedMarginTop = index === 0 ? 0 : 16;
+
+ return {
+ marginTop: interpolate(progress.value, [0, 1], [collapsedMarginTop, expandedMarginTop]),
+ zIndex: total - index,
+ };
+ });
+
+ return (
+
+ {/* When collapsed, clicking any card should expand. When expanded, open details. */}
+ {/* We can intercept touches if !isExpanded */}
+
+ {/* Overlay to intercept clicks when collapsed */}
+ {!isExpanded && (
+
+ )}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ marginTop: 8,
+ gap: 12,
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 8,
+ paddingHorizontal: 4,
+ },
+ headerContent: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ iconContainer: {
+ width: 28,
+ height: 28,
+ borderRadius: 14,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ headerTitle: {
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ stackContainer: {
+ position: 'relative',
+ // minHeight ensures space for the stack when collapsed
+ },
+});
\ No newline at end of file
diff --git a/constants/Routes.ts b/constants/Routes.ts
index eba78bb..85fa029 100644
--- a/constants/Routes.ts
+++ b/constants/Routes.ts
@@ -83,6 +83,9 @@ export const ROUTES = {
// 药品相关路由
MEDICATION_EDIT_FREQUENCY: '/medications/edit-frequency',
MEDICATION_MANAGE: '/medications/manage-medications',
+
+ // 底部栏配置路由
+ TAB_BAR_CONFIG: '/settings/tab-bar-config',
} as const;
// 路由参数常量
diff --git a/i18n/index.ts b/i18n/index.ts
index be0dda2..cfbc577 100644
--- a/i18n/index.ts
+++ b/i18n/index.ts
@@ -47,6 +47,7 @@ const personalScreenResources = {
language: '语言',
healthData: '健康数据授权',
medicalSources: '医学建议来源',
+ customization: '个性化',
},
menu: {
notificationSettings: '通知设置',
@@ -59,6 +60,7 @@ const personalScreenResources = {
deleteAccount: '注销帐号',
healthDataPermissions: '健康数据授权说明',
whoSource: '世界卫生组织 (WHO)',
+ tabBarConfig: '底部栏配置',
},
language: {
title: '语言',
@@ -77,6 +79,20 @@ const personalScreenResources = {
},
},
},
+ tabBarConfig: {
+ title: '底部栏配置',
+ subtitle: '自定义你的底部导航栏',
+ description: '使用开关控制标签的显示和隐藏',
+ resetButton: '恢复默认',
+ cannotDisable: '此标签不可关闭',
+ resetConfirm: {
+ title: '恢复默认设置?',
+ message: '将重置所有底部栏配置和显示状态',
+ cancel: '取消',
+ confirm: '确认恢复',
+ },
+ resetSuccess: '已恢复默认设置',
+ },
};
const badgesScreenResources = {
@@ -458,6 +474,9 @@ const medicationsResources = {
title: '今日暂无用药安排',
subtitle: '还未添加任何用药计划,快来补充吧。',
},
+ stack: {
+ completed: '已完成 ({{count}})',
+ },
dateFormats: {
today: '今天,{{date}}',
other: '{{date}}',
@@ -894,6 +913,7 @@ const resources = {
language: 'Language',
healthData: 'Health data permissions',
medicalSources: 'Medical Advice Sources',
+ customization: 'Customization',
},
menu: {
notificationSettings: 'Notification settings',
@@ -906,6 +926,7 @@ const resources = {
deleteAccount: 'Delete account',
healthDataPermissions: 'Health data disclosure',
whoSource: 'World Health Organization (WHO)',
+ tabBarConfig: 'Tab Bar Settings',
},
language: {
title: 'Language',
@@ -1224,6 +1245,9 @@ const resources = {
title: 'No medications scheduled for today',
subtitle: 'No medication plans added yet. Let\'s add some.',
},
+ stack: {
+ completed: 'Completed ({{count}})',
+ },
dateFormats: {
today: 'Today, {{date}}',
other: '{{date}}',
@@ -1607,6 +1631,20 @@ const resources = {
},
},
},
+ tabBarConfig: {
+ title: 'Tab Bar Settings',
+ subtitle: 'Customize your bottom navigation',
+ description: 'Use toggle to show or hide tabs',
+ resetButton: 'Reset to Default',
+ cannotDisable: 'This tab cannot be disabled',
+ resetConfirm: {
+ title: 'Reset to default?',
+ message: 'This will reset all tab bar settings and visibility',
+ cancel: 'Cancel',
+ confirm: 'Reset',
+ },
+ resetSuccess: 'Settings reset to default',
+ },
},
},
};
diff --git a/ios/OutLive/Images.xcassets/AppIcon.appiconset/logo.png b/ios/OutLive/Images.xcassets/AppIcon.appiconset/logo.png
index 03b906b..cf3a6a8 100644
Binary files a/ios/OutLive/Images.xcassets/AppIcon.appiconset/logo.png and b/ios/OutLive/Images.xcassets/AppIcon.appiconset/logo.png differ
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/Contents.json b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/Contents.json
index 4e12055..f702e96 100644
--- a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/Contents.json
+++ b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/Contents.json
@@ -1,17 +1,17 @@
{
"images" : [
{
- "filename" : "logo.png",
+ "filename" : "onBoarding.png",
"idiom" : "universal",
"scale" : "1x"
},
{
- "filename" : "logo 1.png",
+ "filename" : "onBoarding 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
- "filename" : "logo 2.png",
+ "filename" : "onBoarding 2.png",
"idiom" : "universal",
"scale" : "3x"
}
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo 1.png b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo 1.png
deleted file mode 100644
index 03b906b..0000000
Binary files a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo 1.png and /dev/null differ
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo 2.png b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo 2.png
deleted file mode 100644
index 03b906b..0000000
Binary files a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo 2.png and /dev/null differ
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo.png b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo.png
deleted file mode 100644
index 03b906b..0000000
Binary files a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/logo.png and /dev/null differ
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding 1.png b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding 1.png
new file mode 100644
index 0000000..0882900
Binary files /dev/null and b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding 1.png differ
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding 2.png b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding 2.png
new file mode 100644
index 0000000..0882900
Binary files /dev/null and b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding 2.png differ
diff --git a/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding.png b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding.png
new file mode 100644
index 0000000..0882900
Binary files /dev/null and b/ios/OutLive/Images.xcassets/SplashScreenLogo.imageset/onBoarding.png differ
diff --git a/ios/OutLive/SplashScreen.storyboard b/ios/OutLive/SplashScreen.storyboard
index 5afb2d2..654bf67 100644
--- a/ios/OutLive/SplashScreen.storyboard
+++ b/ios/OutLive/SplashScreen.storyboard
@@ -1,15 +1,15 @@
-
+
-
+
-
+
@@ -17,16 +17,16 @@
-
-
+
+
-
-
-
-
+
+
+
+
@@ -35,12 +35,9 @@
-
-
-
-
+
-
+
-
\ No newline at end of file
+
diff --git a/store/index.ts b/store/index.ts
index fa1fc56..b952150 100644
--- a/store/index.ts
+++ b/store/index.ts
@@ -20,6 +20,7 @@ import membershipReducer from './membershipSlice';
import moodReducer from './moodSlice';
import nutritionReducer from './nutritionSlice';
import scheduleExerciseReducer from './scheduleExerciseSlice';
+import tabBarConfigReducer from './tabBarConfigSlice';
import trainingPlanReducer from './trainingPlanSlice';
import userReducer from './userSlice';
import waterReducer from './waterSlice';
@@ -113,6 +114,7 @@ export const store = configureStore({
fasting: fastingReducer,
medications: medicationsReducer,
badges: badgesReducer,
+ tabBarConfig: tabBarConfigReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().prepend(listenerMiddleware.middleware),
diff --git a/store/tabBarConfigSlice.ts b/store/tabBarConfigSlice.ts
new file mode 100644
index 0000000..4a3f166
--- /dev/null
+++ b/store/tabBarConfigSlice.ts
@@ -0,0 +1,203 @@
+import AsyncStorage from '@/utils/kvStore';
+import { logger } from '@/utils/logger';
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import type { RootState } from './index';
+
+// Tab 配置接口
+export interface TabConfig {
+ id: string; // tab 标识符
+ icon: string; // SF Symbol 图标名
+ titleKey: string; // i18n 翻译 key
+ enabled: boolean; // 是否启用
+ canBeDisabled: boolean; // 是否可以被禁用
+ order: number; // 显示顺序
+}
+
+// State 接口
+interface TabBarConfigState {
+ configs: TabConfig[];
+ isInitialized: boolean;
+}
+
+// 默认配置
+export const DEFAULT_TAB_CONFIGS: TabConfig[] = [
+ {
+ id: 'statistics',
+ icon: 'chart.pie.fill',
+ titleKey: 'statistics.tabs.health',
+ enabled: true,
+ canBeDisabled: false,
+ order: 1,
+ },
+ {
+ id: 'medications',
+ icon: 'pills.fill',
+ titleKey: 'statistics.tabs.medications',
+ enabled: true,
+ canBeDisabled: false,
+ order: 2,
+ },
+ {
+ id: 'fasting',
+ icon: 'timer',
+ titleKey: 'statistics.tabs.fasting',
+ enabled: true,
+ canBeDisabled: true, // 只有断食可以被关闭
+ order: 3,
+ },
+ {
+ id: 'challenges',
+ icon: 'trophy.fill',
+ titleKey: 'statistics.tabs.challenges',
+ enabled: true,
+ canBeDisabled: false,
+ order: 4,
+ },
+ {
+ id: 'personal',
+ icon: 'person.fill',
+ titleKey: 'statistics.tabs.personal',
+ enabled: true,
+ canBeDisabled: false,
+ order: 5,
+ },
+];
+
+// AsyncStorage key
+const STORAGE_KEY = 'tab_bar_config';
+
+// 初始状态
+const initialState: TabBarConfigState = {
+ configs: DEFAULT_TAB_CONFIGS,
+ isInitialized: false,
+};
+
+const tabBarConfigSlice = createSlice({
+ name: 'tabBarConfig',
+ initialState,
+ reducers: {
+ // 设置配置(用于从 AsyncStorage 恢复)
+ setConfigs: (state, action: PayloadAction) => {
+ state.configs = action.payload;
+ state.isInitialized = true;
+ },
+
+ // 切换 tab 启用状态
+ toggleTabEnabled: (state, action: PayloadAction) => {
+ const tabId = action.payload;
+ const config = state.configs.find(c => c.id === tabId);
+
+ if (config && config.canBeDisabled) {
+ config.enabled = !config.enabled;
+ // 自动持久化到 AsyncStorage
+ saveConfigsToStorage(state.configs);
+ }
+ },
+
+ // 更新 tab 顺序(拖拽后)
+ reorderTabs: (state, action: PayloadAction) => {
+ // 更新顺序,同时保持其他属性不变
+ const newConfigs = action.payload.map((config, index) => ({
+ ...config,
+ order: index + 1,
+ }));
+ state.configs = newConfigs;
+ // 自动持久化到 AsyncStorage
+ saveConfigsToStorage(newConfigs);
+ },
+
+ // 恢复默认配置
+ resetToDefault: (state) => {
+ state.configs = DEFAULT_TAB_CONFIGS;
+ // 持久化到 AsyncStorage
+ saveConfigsToStorage(DEFAULT_TAB_CONFIGS);
+ },
+
+ // 标记已初始化
+ markInitialized: (state) => {
+ state.isInitialized = true;
+ },
+ },
+});
+
+// 持久化配置到 AsyncStorage
+const saveConfigsToStorage = async (configs: TabConfig[]) => {
+ try {
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(configs));
+ logger.info('底部栏配置已保存');
+ } catch (error) {
+ logger.error('保存底部栏配置失败:', error);
+ }
+};
+
+// 从 AsyncStorage 加载配置
+export const loadTabBarConfigs = () => async (dispatch: any) => {
+ try {
+ const stored = await AsyncStorage.getItem(STORAGE_KEY);
+
+ if (stored) {
+ const configs = JSON.parse(stored) as TabConfig[];
+
+ // 验证配置有效性
+ if (Array.isArray(configs) && configs.length > 0) {
+ // 合并默认配置,确保新增的 tab 也能显示
+ const mergedConfigs = mergeWithDefaults(configs);
+ dispatch(setConfigs(mergedConfigs));
+ logger.info('底部栏配置已加载');
+ return;
+ }
+ }
+
+ // 如果没有存储或无效,使用默认配置
+ dispatch(setConfigs(DEFAULT_TAB_CONFIGS));
+ dispatch(markInitialized());
+ } catch (error) {
+ logger.error('加载底部栏配置失败:', error);
+ // 出错时使用默认配置
+ dispatch(setConfigs(DEFAULT_TAB_CONFIGS));
+ dispatch(markInitialized());
+ }
+};
+
+// 合并存储的配置和默认配置
+const mergeWithDefaults = (storedConfigs: TabConfig[]): TabConfig[] => {
+ const merged = [...storedConfigs];
+
+ // 检查是否有新增的默认 tab
+ DEFAULT_TAB_CONFIGS.forEach(defaultConfig => {
+ const exists = merged.find(c => c.id === defaultConfig.id);
+ if (!exists) {
+ // 新增的 tab,添加到末尾
+ merged.push({
+ ...defaultConfig,
+ order: merged.length + 1,
+ });
+ }
+ });
+
+ // 按 order 排序
+ return merged.sort((a, b) => a.order - b.order);
+};
+
+// Actions
+export const {
+ setConfigs,
+ toggleTabEnabled,
+ reorderTabs,
+ resetToDefault,
+ markInitialized,
+} = tabBarConfigSlice.actions;
+
+// Selectors
+export const selectTabBarConfigs = (state: RootState) => state.tabBarConfig.configs;
+export const selectEnabledTabs = (state: RootState) =>
+ state.tabBarConfig.configs
+ .filter(config => config.enabled)
+ .sort((a, b) => a.order - b.order);
+export const selectIsInitialized = (state: RootState) => state.tabBarConfig.isInitialized;
+
+// 按 id 获取配置
+export const selectTabConfigById = (tabId: string) => (state: RootState) =>
+ state.tabBarConfig.configs.find(config => config.id === tabId);
+
+export default tabBarConfigSlice.reducer;
\ No newline at end of file