feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块 - 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本 - 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示 - 完善登录页、注销流程及权限申请弹窗的双语提示信息 - 优化部分页面的 UI 细节与字体样式以适配多语言显示
This commit is contained in:
@@ -3,6 +3,7 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useWaterDataByDate } from '@/hooks/useWaterData';
|
||||
import { getQuickWaterAmount } from '@/utils/userPreferences';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { router, useLocalSearchParams } from 'expo-router';
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
import { Swipeable } from 'react-native-gesture-handler';
|
||||
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -28,6 +30,7 @@ interface WaterDetailProps {
|
||||
}
|
||||
|
||||
const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop()
|
||||
|
||||
const { selectedDate } = useLocalSearchParams<{ selectedDate?: string }>();
|
||||
@@ -37,22 +40,14 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
const [dailyGoal, setDailyGoal] = useState<string>('2000');
|
||||
const [quickAddAmount, setQuickAddAmount] = useState<string>('250');
|
||||
|
||||
// Remove modal states as they are now in separate settings page
|
||||
|
||||
// 使用新的 hook 来处理指定日期的饮水数据
|
||||
const { waterRecords, dailyWaterGoal, updateWaterGoal, removeWaterRecord } = useWaterDataByDate(selectedDate);
|
||||
|
||||
|
||||
|
||||
|
||||
// 处理设置按钮点击 - 跳转到设置页面
|
||||
const handleSettingsPress = () => {
|
||||
router.push('/water/settings');
|
||||
};
|
||||
|
||||
// Remove all modal-related functions as they are now in separate settings page
|
||||
|
||||
|
||||
// 删除饮水记录
|
||||
const handleDeleteRecord = async (recordId: string) => {
|
||||
await removeWaterRecord(recordId);
|
||||
@@ -70,13 +65,17 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
setDailyGoal(dailyWaterGoal.toString());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户偏好设置失败:', error);
|
||||
console.error(t('waterDetail.loadingUserPreferences'), error);
|
||||
}
|
||||
};
|
||||
|
||||
loadUserPreferences();
|
||||
}, [dailyWaterGoal]);
|
||||
|
||||
const totalAmount = waterRecords?.reduce((sum, record) => sum + record.amount, 0) || 0;
|
||||
const currentGoal = dailyWaterGoal || 2000;
|
||||
const progress = Math.min(100, (totalAmount / currentGoal) * 100);
|
||||
|
||||
// 新增:饮水记录卡片组件
|
||||
const WaterRecordCard = ({ record, onDelete }: { record: any; onDelete: () => void }) => {
|
||||
const swipeableRef = React.useRef<Swipeable>(null);
|
||||
@@ -84,15 +83,15 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
// 处理删除操作
|
||||
const handleDelete = () => {
|
||||
Alert.alert(
|
||||
'确认删除',
|
||||
'确定要删除这条饮水记录吗?此操作无法撤销。',
|
||||
t('waterDetail.deleteConfirm.title'),
|
||||
t('waterDetail.deleteConfirm.message'),
|
||||
[
|
||||
{
|
||||
text: '取消',
|
||||
text: t('waterDetail.deleteConfirm.cancel'),
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: '删除',
|
||||
text: t('waterDetail.deleteConfirm.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
onDelete();
|
||||
@@ -112,7 +111,6 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="trash" size={20} color="#FFFFFF" />
|
||||
<Text style={styles.deleteSwipeButtonText}>删除</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
@@ -125,29 +123,29 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
rightThreshold={40}
|
||||
overshootRight={false}
|
||||
>
|
||||
<View style={[styles.recordCard, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}>
|
||||
<View style={styles.recordCard}>
|
||||
<View style={styles.recordMainContent}>
|
||||
<View style={[styles.recordIconContainer, { backgroundColor: colorTokens.background }]}>
|
||||
<View style={styles.recordIconContainer}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/IconGlass.png')}
|
||||
style={styles.recordIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.recordInfo}>
|
||||
<Text style={[styles.recordLabel, { color: colorTokens.text }]}>水</Text>
|
||||
<Text style={styles.recordLabel}>{t('waterDetail.water')}</Text>
|
||||
<View style={styles.recordTimeContainer}>
|
||||
<Ionicons name="time-outline" size={14} color={colorTokens.textSecondary} />
|
||||
<Text style={[styles.recordTimeText, { color: colorTokens.textSecondary }]}>
|
||||
<Ionicons name="time-outline" size={14} color="#6f7ba7" />
|
||||
<Text style={styles.recordTimeText}>
|
||||
{dayjs(record.recordedAt || record.createdAt).format('HH:mm')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.recordAmountContainer}>
|
||||
<Text style={[styles.recordAmount, { color: colorTokens.text }]}>{record.amount}ml</Text>
|
||||
<Text style={styles.recordAmount}>{record.amount}ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
{record.note && (
|
||||
<Text style={[styles.recordNote, { color: colorTokens.textSecondary }]}>{record.note}</Text>
|
||||
<Text style={styles.recordNote}>{record.note}</Text>
|
||||
)}
|
||||
</View>
|
||||
</Swipeable>
|
||||
@@ -157,32 +155,47 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* 背景渐变 */}
|
||||
{/* 背景 */}
|
||||
<LinearGradient
|
||||
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#e6f6fcff']}
|
||||
style={styles.gradientBackground}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
colors={['#f3f4fb', '#f3f4fb']}
|
||||
style={StyleSheet.absoluteFillObject}
|
||||
/>
|
||||
{/* 顶部装饰性渐变 - 模仿挑战页面的柔和背景感 */}
|
||||
<LinearGradient
|
||||
colors={['rgba(229, 252, 254, 0.8)', 'rgba(243, 244, 251, 0)']}
|
||||
style={styles.topGradient}
|
||||
start={{ x: 0.5, y: 0 }}
|
||||
end={{ x: 0.5, y: 1 }}
|
||||
/>
|
||||
|
||||
{/* 装饰性圆圈 */}
|
||||
<View style={styles.decorativeCircle1} />
|
||||
<View style={styles.decorativeCircle2} />
|
||||
|
||||
<HeaderBar
|
||||
title="饮水详情"
|
||||
onBack={() => {
|
||||
// 这里会通过路由自动处理返回
|
||||
router.back();
|
||||
}}
|
||||
title={t('waterDetail.title')}
|
||||
onBack={() => router.back()}
|
||||
right={
|
||||
<TouchableOpacity
|
||||
style={styles.settingsButton}
|
||||
onPress={handleSettingsPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name="settings-outline" size={24} color={colorTokens.text} />
|
||||
</TouchableOpacity>
|
||||
isLiquidGlassAvailable() ? (
|
||||
<TouchableOpacity
|
||||
onPress={handleSettingsPress}
|
||||
activeOpacity={0.7}
|
||||
style={styles.settingsButtonWrapper}
|
||||
>
|
||||
<GlassView
|
||||
style={styles.settingsButtonGlass}
|
||||
glassEffectStyle="regular"
|
||||
tintColor="rgba(255, 255, 255, 0.4)"
|
||||
isInteractive={true}
|
||||
>
|
||||
<Ionicons name="settings-outline" size={22} color="#1c1f3a" />
|
||||
</GlassView>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={styles.settingsButtonFallback}
|
||||
onPress={handleSettingsPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name="settings-outline" size={22} color="#1c1f3a" />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -197,13 +210,37 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
}]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{/* 第二部分:饮水记录 */}
|
||||
<View style={styles.section}>
|
||||
<Text style={[styles.sectionTitle, { color: colorTokens.text }]}>
|
||||
{selectedDate ? dayjs(selectedDate).format('MM月DD日') : '今日'}饮水记录
|
||||
<View style={styles.headerBlock}>
|
||||
<Text style={styles.pageTitle}>
|
||||
{selectedDate ? dayjs(selectedDate).format('MM月DD日') : t('waterDetail.today')}
|
||||
</Text>
|
||||
<Text style={styles.pageSubtitle}>{t('waterDetail.waterRecord')}</Text>
|
||||
</View>
|
||||
|
||||
{/* 进度卡片 */}
|
||||
<View style={styles.progressCard}>
|
||||
<View style={styles.progressInfo}>
|
||||
<View>
|
||||
<Text style={styles.progressLabel}>{t('waterDetail.total')}</Text>
|
||||
<Text style={styles.progressValue}>{totalAmount}<Text style={styles.progressUnit}>ml</Text></Text>
|
||||
</View>
|
||||
<View style={{ alignItems: 'flex-end' }}>
|
||||
<Text style={styles.progressLabel}>{t('waterDetail.goal')}</Text>
|
||||
<Text style={styles.progressGoalValue}>{currentGoal}<Text style={styles.progressUnit}>ml</Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.progressBarBg}>
|
||||
<LinearGradient
|
||||
colors={['#4F5BD5', '#6B6CFF']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={[styles.progressBarFill, { width: `${progress}%` }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 记录列表 */}
|
||||
<View style={styles.section}>
|
||||
{waterRecords && waterRecords.length > 0 ? (
|
||||
<View style={styles.recordsList}>
|
||||
{waterRecords.map((record) => (
|
||||
@@ -213,29 +250,20 @@ const WaterDetail: React.FC<WaterDetailProps> = () => {
|
||||
onDelete={() => handleDeleteRecord(record.id)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 总计显示 */}
|
||||
<View style={[styles.recordsSummary, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}>
|
||||
<Text style={[styles.summaryText, { color: colorTokens.text }]}>
|
||||
总计:{waterRecords.reduce((sum, record) => sum + record.amount, 0)}ml
|
||||
</Text>
|
||||
<Text style={[styles.summaryGoal, { color: colorTokens.textSecondary }]}>
|
||||
目标:{dailyWaterGoal}ml
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.noRecordsContainer}>
|
||||
<Ionicons name="water-outline" size={48} color={colorTokens.textSecondary} />
|
||||
<Text style={[styles.noRecordsText, { color: colorTokens.textSecondary }]}>暂无饮水记录</Text>
|
||||
<Text style={[styles.noRecordsSubText, { color: colorTokens.textSecondary }]}>点击"添加记录"开始记录饮水量</Text>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/IconGlass.png')}
|
||||
style={{ width: 60, height: 60, opacity: 0.5, marginBottom: 16 }}
|
||||
/>
|
||||
<Text style={styles.noRecordsText}>{t('waterDetail.noRecords')}</Text>
|
||||
<Text style={styles.noRecordsSubText}>{t('waterDetail.noRecordsSubtitle')}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
{/* All modals have been moved to the separate water-settings page */}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -245,32 +273,12 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
backgroundColor: '#f3f4fb',
|
||||
},
|
||||
gradientBackground: {
|
||||
topGradient: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
decorativeCircle1: {
|
||||
position: 'absolute',
|
||||
top: 80,
|
||||
right: 30,
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
backgroundColor: '#4F5BD5',
|
||||
opacity: 0.08,
|
||||
},
|
||||
decorativeCircle2: {
|
||||
position: 'absolute',
|
||||
bottom: 100,
|
||||
left: -20,
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: '#4F5BD5',
|
||||
opacity: 0.06,
|
||||
height: 300,
|
||||
},
|
||||
keyboardAvoidingView: {
|
||||
flex: 1,
|
||||
@@ -279,54 +287,107 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
headerBlock: {
|
||||
paddingHorizontal: 24,
|
||||
paddingTop: 20,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 36,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
marginTop: 10,
|
||||
marginBottom: 24,
|
||||
letterSpacing: -0.5,
|
||||
color: '#1c1f3a',
|
||||
},
|
||||
subsectionTitle: {
|
||||
pageTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: '800',
|
||||
color: '#1c1f3a',
|
||||
fontFamily: 'AliBold',
|
||||
marginBottom: 4,
|
||||
},
|
||||
pageSubtitle: {
|
||||
fontSize: 16,
|
||||
color: '#6f7ba7',
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
|
||||
// 进度卡片
|
||||
progressCard: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 32,
|
||||
padding: 24,
|
||||
borderRadius: 28,
|
||||
backgroundColor: '#ffffff',
|
||||
shadowColor: 'rgba(30, 41, 59, 0.1)',
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.18,
|
||||
shadowRadius: 20,
|
||||
elevation: 8,
|
||||
},
|
||||
progressInfo: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-end',
|
||||
marginBottom: 16,
|
||||
},
|
||||
progressLabel: {
|
||||
fontSize: 14,
|
||||
color: '#6f7ba7',
|
||||
marginBottom: 6,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
progressValue: {
|
||||
fontSize: 32,
|
||||
fontWeight: '800',
|
||||
color: '#4F5BD5',
|
||||
fontFamily: 'AliBold',
|
||||
lineHeight: 32,
|
||||
},
|
||||
progressGoalValue: {
|
||||
fontSize: 24,
|
||||
fontWeight: '700',
|
||||
color: '#1c1f3a',
|
||||
fontFamily: 'AliBold',
|
||||
lineHeight: 32,
|
||||
},
|
||||
progressUnit: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 16,
|
||||
letterSpacing: -0.3,
|
||||
color: '#1c1f3a',
|
||||
},
|
||||
sectionSubtitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
lineHeight: 20,
|
||||
color: '#6f7ba7',
|
||||
marginLeft: 2,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
// 饮水记录相关样式
|
||||
progressBarBg: {
|
||||
height: 12,
|
||||
backgroundColor: '#F0F2F5',
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
progressBarFill: {
|
||||
height: '100%',
|
||||
borderRadius: 6,
|
||||
},
|
||||
|
||||
section: {
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
|
||||
// 记录列表样式
|
||||
recordsList: {
|
||||
gap: 16,
|
||||
},
|
||||
recordCardContainer: {
|
||||
// iOS 阴影效果 - 增强阴影效果
|
||||
shadowColor: 'rgba(30, 41, 59, 0.18)',
|
||||
shadowColor: 'rgba(30, 41, 59, 0.08)',
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.16,
|
||||
shadowRadius: 16,
|
||||
// Android 阴影效果
|
||||
elevation: 6,
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 12,
|
||||
elevation: 4,
|
||||
marginBottom: 2,
|
||||
},
|
||||
recordCard: {
|
||||
borderRadius: 20,
|
||||
borderRadius: 24,
|
||||
padding: 18,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
recordMainContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
recordIconContainer: {
|
||||
width: 48,
|
||||
@@ -334,7 +395,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(79, 91, 213, 0.08)',
|
||||
backgroundColor: '#f5f6ff',
|
||||
},
|
||||
recordIcon: {
|
||||
width: 24,
|
||||
@@ -345,15 +406,21 @@ const styles = StyleSheet.create({
|
||||
marginLeft: 16,
|
||||
},
|
||||
recordLabel: {
|
||||
fontSize: 17,
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#1c1f3a',
|
||||
marginBottom: 6,
|
||||
marginBottom: 4,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
recordTimeContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
gap: 4,
|
||||
},
|
||||
recordTimeText: {
|
||||
fontSize: 13,
|
||||
color: '#6f7ba7',
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
recordAmountContainer: {
|
||||
alignItems: 'flex-end',
|
||||
@@ -362,364 +429,74 @@ const styles = StyleSheet.create({
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: '#4F5BD5',
|
||||
},
|
||||
deleteSwipeButton: {
|
||||
backgroundColor: '#EF4444',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 80,
|
||||
borderRadius: 16,
|
||||
marginLeft: 12,
|
||||
},
|
||||
deleteSwipeButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
marginTop: 4,
|
||||
},
|
||||
recordTimeText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '500',
|
||||
color: '#6f7ba7',
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
recordNote: {
|
||||
marginTop: 12,
|
||||
marginTop: 14,
|
||||
padding: 12,
|
||||
backgroundColor: 'rgba(79, 91, 213, 0.04)',
|
||||
backgroundColor: '#F8F9FC',
|
||||
borderRadius: 12,
|
||||
fontSize: 14,
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 20,
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
color: '#5f6a97',
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
recordsSummary: {
|
||||
marginTop: 24,
|
||||
padding: 20,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#ffffff',
|
||||
shadowColor: 'rgba(30, 41, 59, 0.12)',
|
||||
shadowOpacity: 0.16,
|
||||
shadowRadius: 18,
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
elevation: 6,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
deleteSwipeButton: {
|
||||
backgroundColor: '#FF6B6B',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 70,
|
||||
height: '100%',
|
||||
borderRadius: 24,
|
||||
marginLeft: 12,
|
||||
},
|
||||
summaryText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#1c1f3a',
|
||||
},
|
||||
summaryGoal: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#6f7ba7',
|
||||
},
|
||||
|
||||
noRecordsContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 60,
|
||||
gap: 20,
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 28,
|
||||
shadowColor: 'rgba(30, 41, 59, 0.06)',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 12,
|
||||
},
|
||||
noRecordsText: {
|
||||
fontSize: 17,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
lineHeight: 24,
|
||||
color: '#6f7ba7',
|
||||
color: '#1c1f3a',
|
||||
marginBottom: 8,
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
noRecordsSubText: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
color: '#9ba3c7',
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
modalBackdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||
|
||||
// Settings Button
|
||||
settingsButtonWrapper: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
modalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
// iOS 阴影效果
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
// Android 阴影效果
|
||||
elevation: 16,
|
||||
},
|
||||
modalHandle: {
|
||||
width: 36,
|
||||
height: 4,
|
||||
backgroundColor: '#E0E0E0',
|
||||
borderRadius: 2,
|
||||
alignSelf: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
pickerContainer: {
|
||||
height: 200,
|
||||
marginBottom: 20,
|
||||
},
|
||||
picker: {
|
||||
height: 200,
|
||||
},
|
||||
modalActions: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 12,
|
||||
},
|
||||
modalBtn: {
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
minWidth: 80,
|
||||
settingsButtonGlass: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
modalBtnPrimary: {
|
||||
// backgroundColor will be set dynamically
|
||||
},
|
||||
modalBtnText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
modalBtnTextPrimary: {
|
||||
// color will be set dynamically
|
||||
},
|
||||
settingsButton: {
|
||||
settingsButtonFallback: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 20,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.24)',
|
||||
backgroundColor: '#ffffff',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.45)',
|
||||
},
|
||||
settingsModalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 16,
|
||||
},
|
||||
settingsModalTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
settingsMenuContainer: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
settingsMenuItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#F1F3F4',
|
||||
},
|
||||
settingsMenuItemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
settingsIconContainer: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 12,
|
||||
},
|
||||
settingsMenuItemContent: {
|
||||
flex: 1,
|
||||
},
|
||||
settingsMenuItemTitle: {
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
marginBottom: 2,
|
||||
},
|
||||
settingsMenuItemSubtitle: {
|
||||
fontSize: 12,
|
||||
marginBottom: 4,
|
||||
},
|
||||
settingsMenuItemValue: {
|
||||
fontSize: 14,
|
||||
},
|
||||
// 喝水提醒配置弹窗样式
|
||||
waterReminderModalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
maxHeight: '80%',
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 16,
|
||||
},
|
||||
waterReminderContent: {
|
||||
flex: 1,
|
||||
marginBottom: 20,
|
||||
},
|
||||
waterReminderSection: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
waterReminderSectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 8,
|
||||
},
|
||||
waterReminderSectionTitleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
waterReminderSectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
waterReminderSectionDesc: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
marginTop: 4,
|
||||
},
|
||||
timeRangeContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 16,
|
||||
marginTop: 16,
|
||||
},
|
||||
timePickerContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
timeLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
marginBottom: 8,
|
||||
},
|
||||
timePicker: {
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
timePickerText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
timePickerIcon: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
intervalContainer: {
|
||||
marginTop: 16,
|
||||
},
|
||||
intervalPickerContainer: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
intervalPicker: {
|
||||
height: 120,
|
||||
},
|
||||
// 时间选择器弹窗样式
|
||||
timePickerModalSheet: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
maxHeight: '60%',
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: -2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 16,
|
||||
},
|
||||
timePickerContent: {
|
||||
flex: 1,
|
||||
marginBottom: 20,
|
||||
},
|
||||
timePickerSection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
timePickerLabel: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
hourPickerContainer: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
hourPicker: {
|
||||
height: 160,
|
||||
},
|
||||
timeRangePreview: {
|
||||
backgroundColor: '#F0F8FF',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
marginTop: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
timeRangePreviewLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
marginBottom: 4,
|
||||
},
|
||||
timeRangePreviewText: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
},
|
||||
timeRangeWarning: {
|
||||
fontSize: 12,
|
||||
color: '#FF6B6B',
|
||||
textAlign: 'center',
|
||||
lineHeight: 18,
|
||||
borderColor: 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -22,9 +22,11 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
|
||||
const WaterReminderSettings: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop()
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
@@ -71,9 +73,9 @@ const WaterReminderSettings: React.FC = () => {
|
||||
setStartTimePickerVisible(false);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'时间设置提示',
|
||||
'开始时间不能晚于或等于结束时间,请重新选择',
|
||||
[{ text: '确定' }]
|
||||
t('waterReminderSettings.alerts.timeValidation.title'),
|
||||
t('waterReminderSettings.alerts.timeValidation.startTimeInvalid'),
|
||||
[{ text: t('waterReminderSettings.buttons.confirm') }]
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -91,9 +93,9 @@ const WaterReminderSettings: React.FC = () => {
|
||||
setEndTimePickerVisible(false);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'时间设置提示',
|
||||
'结束时间不能早于或等于开始时间,请重新选择',
|
||||
[{ text: '确定' }]
|
||||
t('waterReminderSettings.alerts.timeValidation.title'),
|
||||
t('waterReminderSettings.alerts.timeValidation.endTimeInvalid'),
|
||||
[{ text: t('waterReminderSettings.buttons.confirm') }]
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -125,18 +127,28 @@ const WaterReminderSettings: React.FC = () => {
|
||||
|
||||
if (waterReminderSettings.enabled) {
|
||||
const timeInfo = `${waterReminderSettings.startTime}-${waterReminderSettings.endTime}`;
|
||||
const intervalInfo = `每${waterReminderSettings.interval}分钟`;
|
||||
const intervalInfo = `${waterReminderSettings.interval}${t('waterReminderSettings.labels.minutes')}`;
|
||||
Alert.alert(
|
||||
'设置成功',
|
||||
`喝水提醒已开启\n\n时间段:${timeInfo}\n提醒间隔:${intervalInfo}\n\n我们将在指定时间段内定期提醒您喝水`,
|
||||
[{ text: '确定', onPress: () => router.back() }]
|
||||
t('waterReminderSettings.alerts.success.enabled'),
|
||||
t('waterReminderSettings.alerts.success.enabledMessage', {
|
||||
timeRange: timeInfo,
|
||||
interval: intervalInfo
|
||||
}),
|
||||
[{ text: t('waterReminderSettings.buttons.confirm'), onPress: () => router.back() }]
|
||||
);
|
||||
} else {
|
||||
Alert.alert('设置成功', '喝水提醒已关闭', [{ text: '确定', onPress: () => router.back() }]);
|
||||
Alert.alert(
|
||||
t('waterReminderSettings.alerts.success.disabled'),
|
||||
t('waterReminderSettings.alerts.success.disabledMessage'),
|
||||
[{ text: t('waterReminderSettings.buttons.confirm'), onPress: () => router.back() }]
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存喝水提醒设置失败:', error);
|
||||
Alert.alert('保存失败', '无法保存喝水提醒设置,请重试');
|
||||
Alert.alert(
|
||||
t('waterReminderSettings.alerts.error.title'),
|
||||
t('waterReminderSettings.alerts.error.message')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -176,7 +188,7 @@ const WaterReminderSettings: React.FC = () => {
|
||||
<View style={styles.decorativeCircle2} />
|
||||
|
||||
<HeaderBar
|
||||
title="喝水提醒"
|
||||
title={t('waterReminderSettings.title')}
|
||||
onBack={() => {
|
||||
router.back();
|
||||
}}
|
||||
@@ -198,7 +210,7 @@ const WaterReminderSettings: React.FC = () => {
|
||||
<View style={styles.waterReminderSectionHeader}>
|
||||
<View style={styles.waterReminderSectionTitleContainer}>
|
||||
<Ionicons name="notifications-outline" size={20} color={colorTokens.text} />
|
||||
<Text style={[styles.waterReminderSectionTitle, { color: colorTokens.text }]}>推送提醒</Text>
|
||||
<Text style={[styles.waterReminderSectionTitle, { color: colorTokens.text }]}>{t('waterReminderSettings.sections.notifications')}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={waterReminderSettings.enabled}
|
||||
@@ -208,7 +220,7 @@ const WaterReminderSettings: React.FC = () => {
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.waterReminderSectionDesc, { color: colorTokens.textSecondary }]}>
|
||||
开启后将在指定时间段内定期推送喝水提醒
|
||||
{t('waterReminderSettings.descriptions.notifications')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -216,15 +228,15 @@ const WaterReminderSettings: React.FC = () => {
|
||||
{waterReminderSettings.enabled && (
|
||||
<>
|
||||
<View style={styles.waterReminderSection}>
|
||||
<Text style={[styles.waterReminderSectionTitle, { color: colorTokens.text }]}>提醒时间段</Text>
|
||||
<Text style={[styles.waterReminderSectionTitle, { color: colorTokens.text }]}>{t('waterReminderSettings.sections.timeRange')}</Text>
|
||||
<Text style={[styles.waterReminderSectionDesc, { color: colorTokens.textSecondary }]}>
|
||||
只在指定时间段内发送提醒,避免打扰您的休息
|
||||
{t('waterReminderSettings.descriptions.timeRange')}
|
||||
</Text>
|
||||
|
||||
<View style={styles.timeRangeContainer}>
|
||||
{/* 开始时间 */}
|
||||
<View style={styles.timePickerContainer}>
|
||||
<Text style={[styles.timeLabel, { color: colorTokens.text }]}>开始时间</Text>
|
||||
<Text style={[styles.timeLabel, { color: colorTokens.text }]}>{t('waterReminderSettings.labels.startTime')}</Text>
|
||||
<Pressable
|
||||
style={[styles.timePicker, { backgroundColor: 'white' }]}
|
||||
onPress={openStartTimePicker}
|
||||
@@ -236,7 +248,7 @@ const WaterReminderSettings: React.FC = () => {
|
||||
|
||||
{/* 结束时间 */}
|
||||
<View style={styles.timePickerContainer}>
|
||||
<Text style={[styles.timeLabel, { color: colorTokens.text }]}>结束时间</Text>
|
||||
<Text style={[styles.timeLabel, { color: colorTokens.text }]}>{t('waterReminderSettings.labels.endTime')}</Text>
|
||||
<Pressable
|
||||
style={[styles.timePicker, { backgroundColor: 'white' }]}
|
||||
onPress={openEndTimePicker}
|
||||
@@ -250,9 +262,9 @@ const WaterReminderSettings: React.FC = () => {
|
||||
|
||||
{/* 提醒间隔设置 */}
|
||||
<View style={styles.waterReminderSection}>
|
||||
<Text style={[styles.waterReminderSectionTitle, { color: colorTokens.text }]}>提醒间隔</Text>
|
||||
<Text style={[styles.waterReminderSectionTitle, { color: colorTokens.text }]}>{t('waterReminderSettings.sections.interval')}</Text>
|
||||
<Text style={[styles.waterReminderSectionDesc, { color: colorTokens.textSecondary }]}>
|
||||
选择提醒的频率,建议30-120分钟为宜
|
||||
{t('waterReminderSettings.descriptions.interval')}
|
||||
</Text>
|
||||
|
||||
<View style={styles.intervalContainer}>
|
||||
@@ -263,7 +275,7 @@ const WaterReminderSettings: React.FC = () => {
|
||||
style={styles.intervalPicker}
|
||||
>
|
||||
{[30, 45, 60, 90, 120, 150, 180].map(interval => (
|
||||
<Picker.Item key={interval} label={`${interval}分钟`} value={interval} />
|
||||
<Picker.Item key={interval} label={`${interval}${t('waterReminderSettings.labels.minutes')}`} value={interval} />
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
@@ -279,7 +291,7 @@ const WaterReminderSettings: React.FC = () => {
|
||||
onPress={handleWaterReminderSave}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={[styles.saveButtonText, { color: colorTokens.onPrimary }]}>保存设置</Text>
|
||||
<Text style={[styles.saveButtonText, { color: colorTokens.onPrimary }]}>{t('waterReminderSettings.labels.saveSettings')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -295,11 +307,11 @@ const WaterReminderSettings: React.FC = () => {
|
||||
<Pressable style={styles.modalBackdrop} onPress={() => setStartTimePickerVisible(false)} />
|
||||
<View style={styles.timePickerModalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>选择开始时间</Text>
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>{t('waterReminderSettings.labels.startTime')}</Text>
|
||||
|
||||
<View style={styles.timePickerContent}>
|
||||
<View style={styles.timePickerSection}>
|
||||
<Text style={[styles.timePickerLabel, { color: colorTokens.text }]}>小时</Text>
|
||||
<Text style={[styles.timePickerLabel, { color: colorTokens.text }]}>{t('waterReminderSettings.labels.hours')}</Text>
|
||||
<View style={styles.hourPickerContainer}>
|
||||
<Picker
|
||||
selectedValue={tempStartHour}
|
||||
@@ -314,12 +326,12 @@ const WaterReminderSettings: React.FC = () => {
|
||||
</View>
|
||||
|
||||
<View style={styles.timeRangePreview}>
|
||||
<Text style={[styles.timeRangePreviewLabel, { color: colorTokens.textSecondary }]}>时间段预览</Text>
|
||||
<Text style={[styles.timeRangePreviewLabel, { color: colorTokens.textSecondary }]}>{t('waterReminderSettings.labels.timeRangePreview')}</Text>
|
||||
<Text style={[styles.timeRangePreviewText, { color: colorTokens.text }]}>
|
||||
{String(tempStartHour).padStart(2, '0')}:00 - {waterReminderSettings.endTime}
|
||||
</Text>
|
||||
{!isValidTimeRange(`${String(tempStartHour).padStart(2, '0')}:00`, waterReminderSettings.endTime) && (
|
||||
<Text style={styles.timeRangeWarning}>⚠️ 开始时间不能晚于或等于结束时间</Text>
|
||||
<Text style={styles.timeRangeWarning}>⚠️ {t('waterReminderSettings.alerts.timeValidation.startTimeInvalid')}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -329,13 +341,13 @@ const WaterReminderSettings: React.FC = () => {
|
||||
onPress={() => setStartTimePickerVisible(false)}
|
||||
style={[styles.modalBtn, { backgroundColor: 'white' }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>取消</Text>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>{t('waterReminderSettings.buttons.cancel')}</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={confirmStartTime}
|
||||
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colorTokens.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>确定</Text>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>{t('waterReminderSettings.buttons.confirm')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
@@ -351,11 +363,11 @@ const WaterReminderSettings: React.FC = () => {
|
||||
<Pressable style={styles.modalBackdrop} onPress={() => setEndTimePickerVisible(false)} />
|
||||
<View style={styles.timePickerModalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>选择结束时间</Text>
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>{t('waterReminderSettings.labels.endTime')}</Text>
|
||||
|
||||
<View style={styles.timePickerContent}>
|
||||
<View style={styles.timePickerSection}>
|
||||
<Text style={[styles.timePickerLabel, { color: colorTokens.text }]}>小时</Text>
|
||||
<Text style={[styles.timePickerLabel, { color: colorTokens.text }]}>{t('waterReminderSettings.labels.hours')}</Text>
|
||||
<View style={styles.hourPickerContainer}>
|
||||
<Picker
|
||||
selectedValue={tempEndHour}
|
||||
@@ -370,12 +382,12 @@ const WaterReminderSettings: React.FC = () => {
|
||||
</View>
|
||||
|
||||
<View style={styles.timeRangePreview}>
|
||||
<Text style={[styles.timeRangePreviewLabel, { color: colorTokens.textSecondary }]}>时间段预览</Text>
|
||||
<Text style={[styles.timeRangePreviewLabel, { color: colorTokens.textSecondary }]}>{t('waterReminderSettings.labels.timeRangePreview')}</Text>
|
||||
<Text style={[styles.timeRangePreviewText, { color: colorTokens.text }]}>
|
||||
{waterReminderSettings.startTime} - {String(tempEndHour).padStart(2, '0')}:00
|
||||
</Text>
|
||||
{!isValidTimeRange(waterReminderSettings.startTime, `${String(tempEndHour).padStart(2, '0')}:00`) && (
|
||||
<Text style={styles.timeRangeWarning}>⚠️ 结束时间不能早于或等于开始时间</Text>
|
||||
<Text style={styles.timeRangeWarning}>⚠️ {t('waterReminderSettings.alerts.timeValidation.endTimeInvalid')}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -385,13 +397,13 @@ const WaterReminderSettings: React.FC = () => {
|
||||
onPress={() => setEndTimePickerVisible(false)}
|
||||
style={[styles.modalBtn, { backgroundColor: 'white' }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>取消</Text>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>{t('waterReminderSettings.buttons.cancel')}</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={confirmEndTime}
|
||||
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colorTokens.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>确定</Text>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>{t('waterReminderSettings.buttons.confirm')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -21,9 +21,11 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
|
||||
const WaterSettings: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop()
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
@@ -74,7 +76,10 @@ const WaterSettings: React.FC = () => {
|
||||
setGoalModalVisible(false);
|
||||
|
||||
// 这里可以添加保存到本地存储或发送到后端的逻辑
|
||||
Alert.alert('设置成功', `每日饮水目标已设置为 ${tempGoal}ml`);
|
||||
Alert.alert(
|
||||
t('waterSettings.alerts.goalSuccess.title'),
|
||||
t('waterSettings.alerts.goalSuccess.message', { amount: tempGoal })
|
||||
);
|
||||
};
|
||||
|
||||
// 处理快速添加默认值确认
|
||||
@@ -84,9 +89,15 @@ const WaterSettings: React.FC = () => {
|
||||
|
||||
try {
|
||||
await setQuickWaterAmount(tempQuickAdd);
|
||||
Alert.alert('设置成功', `快速添加默认值已设置为 ${tempQuickAdd}ml`);
|
||||
Alert.alert(
|
||||
t('waterSettings.alerts.quickAddSuccess.title'),
|
||||
t('waterSettings.alerts.quickAddSuccess.message', { amount: tempQuickAdd })
|
||||
);
|
||||
} catch {
|
||||
Alert.alert('设置失败', '无法保存快速添加默认值,请重试');
|
||||
Alert.alert(
|
||||
t('waterSettings.alerts.quickAddFailed.title'),
|
||||
t('waterSettings.alerts.quickAddFailed.message')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,7 +112,7 @@ const WaterSettings: React.FC = () => {
|
||||
const reminderSettings = await getWaterReminderSettings();
|
||||
setWaterReminderSettings(reminderSettings);
|
||||
} catch (error) {
|
||||
console.error('加载用户偏好设置失败:', error);
|
||||
console.error('Failed to load user preferences:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -132,7 +143,7 @@ const WaterSettings: React.FC = () => {
|
||||
<View style={styles.decorativeCircle2} />
|
||||
|
||||
<HeaderBar
|
||||
title="饮水设置"
|
||||
title={t('waterSettings.title')}
|
||||
onBack={() => {
|
||||
router.back();
|
||||
}}
|
||||
@@ -156,8 +167,8 @@ const WaterSettings: React.FC = () => {
|
||||
<Ionicons name="flag-outline" size={20} color="#9370DB" />
|
||||
</View>
|
||||
<View style={styles.settingsMenuItemContent}>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>每日饮水目标</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>{currentWaterGoal}ml</Text>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>{t('waterSettings.sections.dailyGoal')}</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>{currentWaterGoal}{t('waterSettings.labels.ml')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#CCCCCC" />
|
||||
@@ -169,11 +180,11 @@ const WaterSettings: React.FC = () => {
|
||||
<Ionicons name="add-outline" size={20} color="#9370DB" />
|
||||
</View>
|
||||
<View style={styles.settingsMenuItemContent}>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>快速添加默认值</Text>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>{t('waterSettings.sections.quickAdd')}</Text>
|
||||
<Text style={[styles.settingsMenuItemSubtitle, { color: colorTokens.textSecondary }]}>
|
||||
设置点击"+"按钮时添加的默认饮水量
|
||||
{t('waterSettings.descriptions.quickAdd')}
|
||||
</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>{quickAddAmount}ml</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>{quickAddAmount}{t('waterSettings.labels.ml')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color="#CCCCCC" />
|
||||
@@ -185,12 +196,19 @@ const WaterSettings: React.FC = () => {
|
||||
<Ionicons name="notifications-outline" size={20} color="#3498DB" />
|
||||
</View>
|
||||
<View style={styles.settingsMenuItemContent}>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>喝水提醒</Text>
|
||||
<Text style={[styles.settingsMenuItemTitle, { color: colorTokens.text }]}>{t('waterSettings.sections.reminder')}</Text>
|
||||
<Text style={[styles.settingsMenuItemSubtitle, { color: colorTokens.textSecondary }]}>
|
||||
设置定时提醒您补充水分
|
||||
{t('waterSettings.descriptions.reminder')}
|
||||
</Text>
|
||||
<Text style={[styles.settingsMenuItemValue, { color: colorTokens.textSecondary }]}>
|
||||
{waterReminderSettings.enabled ? `${waterReminderSettings.startTime}-${waterReminderSettings.endTime}, 每${waterReminderSettings.interval}分钟` : '已关闭'}
|
||||
{waterReminderSettings.enabled
|
||||
? t('waterSettings.status.reminderEnabled', {
|
||||
startTime: waterReminderSettings.startTime,
|
||||
endTime: waterReminderSettings.endTime,
|
||||
interval: waterReminderSettings.interval
|
||||
})
|
||||
: t('waterSettings.labels.disabled')
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -211,7 +229,7 @@ const WaterSettings: React.FC = () => {
|
||||
<Pressable style={styles.modalBackdrop} onPress={() => setGoalModalVisible(false)} />
|
||||
<View style={styles.modalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>每日饮水目标</Text>
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>{t('waterSettings.sections.dailyGoal')}</Text>
|
||||
<View style={styles.pickerContainer}>
|
||||
<Picker
|
||||
selectedValue={tempGoal}
|
||||
@@ -219,7 +237,7 @@ const WaterSettings: React.FC = () => {
|
||||
style={styles.picker}
|
||||
>
|
||||
{Array.from({ length: 96 }, (_, i) => 500 + i * 100).map(goal => (
|
||||
<Picker.Item key={goal} label={`${goal}ml`} value={goal} />
|
||||
<Picker.Item key={goal} label={`${goal}${t('waterSettings.labels.ml')}`} value={goal} />
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
@@ -228,13 +246,13 @@ const WaterSettings: React.FC = () => {
|
||||
onPress={() => setGoalModalVisible(false)}
|
||||
style={[styles.modalBtn, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>取消</Text>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>{t('waterSettings.buttons.cancel')}</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleGoalConfirm}
|
||||
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colorTokens.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>确定</Text>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>{t('waterSettings.buttons.confirm')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
@@ -250,7 +268,7 @@ const WaterSettings: React.FC = () => {
|
||||
<Pressable style={styles.modalBackdrop} onPress={() => setQuickAddModalVisible(false)} />
|
||||
<View style={styles.modalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>快速添加默认值</Text>
|
||||
<Text style={[styles.modalTitle, { color: colorTokens.text }]}>{t('waterSettings.sections.quickAdd')}</Text>
|
||||
<View style={styles.pickerContainer}>
|
||||
<Picker
|
||||
selectedValue={tempQuickAdd}
|
||||
@@ -258,7 +276,7 @@ const WaterSettings: React.FC = () => {
|
||||
style={styles.picker}
|
||||
>
|
||||
{Array.from({ length: 41 }, (_, i) => 50 + i * 10).map(amount => (
|
||||
<Picker.Item key={amount} label={`${amount}ml`} value={amount} />
|
||||
<Picker.Item key={amount} label={`${amount}${t('waterSettings.labels.ml')}`} value={amount} />
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
@@ -267,13 +285,13 @@ const WaterSettings: React.FC = () => {
|
||||
onPress={() => setQuickAddModalVisible(false)}
|
||||
style={[styles.modalBtn, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>取消</Text>
|
||||
<Text style={[styles.modalBtnText, { color: colorTokens.text }]}>{t('waterSettings.buttons.cancel')}</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleQuickAddConfirm}
|
||||
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colorTokens.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>确定</Text>
|
||||
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary, { color: colorTokens.onPrimary }]}>{t('waterSettings.buttons.confirm')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user