- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块 - 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本 - 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示 - 完善登录页、注销流程及权限申请弹窗的双语提示信息 - 优化部分页面的 UI 细节与字体样式以适配多语言显示
264 lines
6.8 KiB
TypeScript
264 lines
6.8 KiB
TypeScript
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 { LinearGradient } from 'expo-linear-gradient';
|
|
import { useRouter } from 'expo-router';
|
|
import React, { useCallback } from 'react';
|
|
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';
|
|
import { useI18n } from '@/hooks/useI18n';
|
|
|
|
export default function TabBarConfigScreen() {
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
const dispatch = useAppDispatch();
|
|
const safeAreaTop = useSafeAreaTop(60);
|
|
const configs = useAppSelector(selectTabBarConfigs);
|
|
|
|
// 处理开关切换
|
|
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 (
|
|
<View key={item.id}>
|
|
<View style={styles.tabItem}>
|
|
{/* Tab 图标和名称 */}
|
|
<View style={styles.tabInfo}>
|
|
<View style={styles.iconContainer}>
|
|
<IconSymbol name={item.icon as any} size={24} color="#9370DB" />
|
|
</View>
|
|
<View style={styles.tabTextContainer}>
|
|
<Text style={styles.tabTitle}>{t(item.titleKey)}</Text>
|
|
{!item.canBeDisabled && (
|
|
<Text style={styles.tabSubtitle}>
|
|
{t('personal.tabBarConfig.cannotDisable')}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* 开关 */}
|
|
<Switch
|
|
value={item.enabled}
|
|
onValueChange={() => handleToggle(item.id)}
|
|
disabled={!item.canBeDisabled}
|
|
trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
|
|
thumbColor="#FFFFFF"
|
|
style={styles.switch}
|
|
/>
|
|
</View>
|
|
|
|
{/* 分割线 - 最后一项不显示 */}
|
|
{index < total - 1 && (
|
|
<View style={styles.separatorContainer}>
|
|
<View style={styles.separator} />
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
},
|
|
[handleToggle, t]
|
|
);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<LinearGradient
|
|
colors={[palette.purple[100], '#F5F5F5']}
|
|
start={{ x: 1, y: 0 }}
|
|
end={{ x: 0.3, y: 0.4 }}
|
|
style={styles.gradientBackground}
|
|
/>
|
|
|
|
{/* 顶部导航栏 */}
|
|
<HeaderBar
|
|
title={t('personal.tabBarConfig.title')}
|
|
onBack={() => router.back()}
|
|
right={
|
|
<TouchableOpacity onPress={handleReset} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
|
|
<Text style={styles.headerRightButton}>
|
|
{t('personal.tabBarConfig.resetButton')}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
}
|
|
/>
|
|
|
|
{/* 主内容区 */}
|
|
<ScrollView
|
|
style={styles.content}
|
|
contentContainerStyle={[styles.scrollContent, { paddingTop: safeAreaTop }]} // 增加顶部间距,因为 HeaderBar 现在是 absolute 的
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* 说明区域 */}
|
|
<View style={styles.headerSection}>
|
|
<Text style={styles.subtitle}>{t('personal.tabBarConfig.subtitle')}</Text>
|
|
<View style={styles.descriptionCard}>
|
|
<View style={styles.hintRow}>
|
|
<Ionicons name="information-circle-outline" size={20} color="#9370DB" />
|
|
<Text style={styles.descriptionText}>
|
|
{t('personal.tabBarConfig.description')}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Tab 列表 - 聚合在一个卡片中 */}
|
|
<View style={styles.sectionContainer}>
|
|
{configs.map((item, index) => renderTabRow(item, index, configs.length))}
|
|
</View>
|
|
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
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', // 使用主色调
|
|
},
|
|
}); |