feat(ui): 添加底部标签栏自定义配置功能和药物堆叠展示

- 新增底部标签栏配置页面,支持切换标签显示/隐藏和恢复默认设置
- 实现已服用药物的堆叠卡片展示,优化药物列表视觉层次
- 集成Redux状态管理底部标签栏配置,支持本地持久化
- 优化个人中心页面背景渐变效果,移除装饰性圆圈元素
- 更新启动页和应用图标为新的品牌视觉
- 药物详情页AI分析加载动画替换为Lottie动画
- 调整药物卡片圆角半径提升视觉一致性
- 新增多语言支持(中英文)用于标签栏配置界面

主要改进:
1. 用户可以自定义底部导航栏显示内容
2. 已完成的药物以堆叠形式展示,节省空间
3. 配置数据通过AsyncStorage持久化保存
4. 支持默认配置恢复功能
This commit is contained in:
richarjiang
2025-11-20 17:55:17 +08:00
parent 84abfa2506
commit 29942feee9
25 changed files with 906 additions and 86 deletions

View File

@@ -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 <NativeTabs>
<NativeTabs.Trigger name="statistics">
<Label>{t('statistics.tabs.health')}</Label>
<Icon sf="chart.pie.fill" drawable="custom_android_drawable" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="medications">
<Icon sf="pills.fill" drawable="custom_android_drawable" />
<Label>{t('statistics.tabs.medications')}</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="fasting">
<Icon sf="timer" drawable="custom_android_drawable" />
<Label>{t('statistics.tabs.fasting')}</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="challenges">
<Icon sf="trophy.fill" drawable="custom_android_drawable" />
<Label>{t('statistics.tabs.challenges')}</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="personal">
<Icon sf="person.fill" drawable="custom_settings_drawable" />
<Label>{t('statistics.tabs.personal')}</Label>
</NativeTabs.Trigger>
</NativeTabs>
return (
<NativeTabs>
{enabledTabs.map((tab) => {
const tabConfig = TAB_CONFIGS[tab.id];
if (!tabConfig) return null;
return (
<NativeTabs.Trigger key={tab.id} name={tab.id}>
<Icon sf={tabConfig.icon as any} drawable="custom_android_drawable" />
<Label>{t(tabConfig.titleKey)}</Label>
</NativeTabs.Trigger>
);
})}
</NativeTabs>
);
}
// 确定初始路由(第一个启用的标签)
const initialRouteName = enabledTabs.length > 0 ? enabledTabs[0].id : 'statistics';
return (
<Tabs
initialRouteName="statistics"
initialRouteName={initialRouteName}
screenOptions={({ route }) => getScreenOptions(route.name)}
>
<Tabs.Screen name="statistics" options={{ title: t('statistics.tabs.health') }} />
<Tabs.Screen name="medications" options={{ title: t('statistics.tabs.medications') }} />
<Tabs.Screen name="fasting" options={{ title: t('statistics.tabs.fasting') }} />
<Tabs.Screen name="challenges" options={{ title: t('statistics.tabs.challenges') }} />
<Tabs.Screen name="personal" options={{ title: t('statistics.tabs.personal') }} />
{enabledTabs.map((tab) => {
const tabConfig = TAB_CONFIGS[tab.id];
if (!tabConfig) return null;
return (
<Tabs.Screen
key={tab.id}
name={tab.id}
options={{ title: t(tabConfig.titleKey) }}
/>
);
})}
</Tabs>
);
}