339 lines
9.3 KiB
TypeScript
339 lines
9.3 KiB
TypeScript
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||
import {
|
||
getWorkoutNotificationPreferences,
|
||
resetWorkoutNotificationPreferences,
|
||
saveWorkoutNotificationPreferences,
|
||
WorkoutNotificationPreferences
|
||
} from '@/utils/workoutPreferences';
|
||
import { useRouter } from 'expo-router';
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
Alert,
|
||
ScrollView,
|
||
StyleSheet,
|
||
Switch,
|
||
Text,
|
||
TouchableOpacity,
|
||
View,
|
||
} from 'react-native';
|
||
|
||
const WORKOUT_TYPES = [
|
||
{ key: 'running', label: '跑步' },
|
||
{ key: 'cycling', label: '骑行' },
|
||
{ key: 'swimming', label: '游泳' },
|
||
{ key: 'yoga', label: '瑜伽' },
|
||
{ key: 'functionalstrengthtraining', label: '功能性力量训练' },
|
||
{ key: 'traditionalstrengthtraining', label: '传统力量训练' },
|
||
{ key: 'highintensityintervaltraining', label: '高强度间歇训练' },
|
||
{ key: 'walking', label: '步行' },
|
||
{ key: 'other', label: '其他运动' },
|
||
];
|
||
|
||
export default function WorkoutNotificationSettingsScreen() {
|
||
const router = useRouter();
|
||
const [preferences, setPreferences] = useState<WorkoutNotificationPreferences>({
|
||
enabled: true,
|
||
startTimeHour: 8,
|
||
endTimeHour: 22,
|
||
enabledWorkoutTypes: [],
|
||
});
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
loadPreferences();
|
||
}, []);
|
||
|
||
const loadPreferences = async () => {
|
||
try {
|
||
const prefs = await getWorkoutNotificationPreferences();
|
||
setPreferences(prefs);
|
||
} catch (error) {
|
||
console.error('加载偏好设置失败:', error);
|
||
Alert.alert('错误', '加载设置失败,请重试');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const savePreferences = async (newPreferences: Partial<WorkoutNotificationPreferences>) => {
|
||
try {
|
||
await saveWorkoutNotificationPreferences(newPreferences);
|
||
setPreferences(prev => ({ ...prev, ...newPreferences }));
|
||
} catch (error) {
|
||
console.error('保存偏好设置失败:', error);
|
||
Alert.alert('错误', '保存设置失败,请重试');
|
||
}
|
||
};
|
||
|
||
const handleEnabledToggle = (enabled: boolean) => {
|
||
savePreferences({ enabled });
|
||
};
|
||
|
||
const handleTimeRangeChange = (type: 'start' | 'end', hour: number) => {
|
||
if (type === 'start') {
|
||
savePreferences({ startTimeHour: hour });
|
||
} else {
|
||
savePreferences({ endTimeHour: hour });
|
||
}
|
||
};
|
||
|
||
const handleWorkoutTypeToggle = (workoutType: string) => {
|
||
const currentTypes = preferences.enabledWorkoutTypes;
|
||
let newTypes: string[];
|
||
|
||
if (currentTypes.includes(workoutType)) {
|
||
newTypes = currentTypes.filter(type => type !== workoutType);
|
||
} else {
|
||
newTypes = [...currentTypes, workoutType];
|
||
}
|
||
|
||
savePreferences({ enabledWorkoutTypes: newTypes });
|
||
};
|
||
|
||
const handleReset = () => {
|
||
Alert.alert(
|
||
'重置设置',
|
||
'确定要重置所有锻炼通知设置为默认值吗?',
|
||
[
|
||
{ text: '取消', style: 'cancel' },
|
||
{
|
||
text: '重置',
|
||
style: 'destructive',
|
||
onPress: async () => {
|
||
try {
|
||
await resetWorkoutNotificationPreferences();
|
||
await loadPreferences();
|
||
Alert.alert('成功', '设置已重置为默认值');
|
||
} catch (error) {
|
||
Alert.alert('错误', '重置设置失败');
|
||
}
|
||
},
|
||
},
|
||
]
|
||
);
|
||
};
|
||
|
||
const formatHour = (hour: number) => {
|
||
return `${hour.toString().padStart(2, '0')}:00`;
|
||
};
|
||
|
||
const TimeSelector = ({
|
||
label,
|
||
value,
|
||
onValueChange
|
||
}: {
|
||
label: string;
|
||
value: number;
|
||
onValueChange: (hour: number) => void;
|
||
}) => (
|
||
<View style={styles.timeSelector}>
|
||
<Text style={styles.timeLabel}>{label}</Text>
|
||
<View style={styles.timeButtons}>
|
||
<TouchableOpacity
|
||
style={[styles.timeButton, value === 0 && styles.timeButtonDisabled]}
|
||
onPress={() => onValueChange(Math.max(0, value - 1))}
|
||
disabled={value === 0}
|
||
>
|
||
<Text style={styles.timeButtonText}>-</Text>
|
||
</TouchableOpacity>
|
||
<Text style={styles.timeValue}>{formatHour(value)}</Text>
|
||
<TouchableOpacity
|
||
style={[styles.timeButton, value === 23 && styles.timeButtonDisabled]}
|
||
onPress={() => onValueChange(Math.min(23, value + 1))}
|
||
disabled={value === 23}
|
||
>
|
||
<Text style={styles.timeButtonText}>+</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
if (loading) {
|
||
return (
|
||
<View style={styles.container}>
|
||
<HeaderBar title="锻炼通知设置" onBack={() => router.back()} />
|
||
<View style={styles.loadingContainer}>
|
||
<Text>加载中...</Text>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<HeaderBar title="锻炼通知设置" onBack={() => router.back()} />
|
||
|
||
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||
{/* 主开关 */}
|
||
<View style={styles.section}>
|
||
<View style={styles.settingItem}>
|
||
<Text style={styles.settingLabel}>锻炼完成通知</Text>
|
||
<Switch
|
||
value={preferences.enabled}
|
||
onValueChange={handleEnabledToggle}
|
||
trackColor={{ false: '#E5E5E5', true: '#4CAF50' }}
|
||
thumbColor={preferences.enabled ? '#FFFFFF' : '#FFFFFF'}
|
||
/>
|
||
</View>
|
||
<Text style={styles.settingDescription}>
|
||
当您完成锻炼时,发送个性化的鼓励通知
|
||
</Text>
|
||
</View>
|
||
|
||
{preferences.enabled && (
|
||
<>
|
||
{/* 时间范围设置 */}
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>通知时间范围</Text>
|
||
<Text style={styles.sectionDescription}>
|
||
只在指定时间段内发送通知,避免深夜打扰
|
||
</Text>
|
||
|
||
<TimeSelector
|
||
label="开始时间"
|
||
value={preferences.startTimeHour}
|
||
onValueChange={(hour) => handleTimeRangeChange('start', hour)}
|
||
/>
|
||
|
||
<TimeSelector
|
||
label="结束时间"
|
||
value={preferences.endTimeHour}
|
||
onValueChange={(hour) => handleTimeRangeChange('end', hour)}
|
||
/>
|
||
</View>
|
||
|
||
{/* 锻炼类型设置 */}
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>锻炼类型</Text>
|
||
<Text style={styles.sectionDescription}>
|
||
选择要接收通知的锻炼类型,不选择表示接收所有类型
|
||
</Text>
|
||
|
||
{WORKOUT_TYPES.map((type) => (
|
||
<View key={type.key} style={styles.settingItem}>
|
||
<Text style={styles.settingLabel}>{type.label}</Text>
|
||
<Switch
|
||
value={
|
||
preferences.enabledWorkoutTypes.length === 0 ||
|
||
preferences.enabledWorkoutTypes.includes(type.key)
|
||
}
|
||
onValueChange={() => handleWorkoutTypeToggle(type.key)}
|
||
trackColor={{ false: '#E5E5E5', true: '#4CAF50' }}
|
||
thumbColor="#FFFFFF"
|
||
/>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</>
|
||
)}
|
||
|
||
{/* 重置按钮 */}
|
||
<View style={styles.section}>
|
||
<TouchableOpacity style={styles.resetButton} onPress={handleReset}>
|
||
<Text style={styles.resetButtonText}>重置为默认设置</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</ScrollView>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: '#F8F9FA',
|
||
},
|
||
loadingContainer: {
|
||
flex: 1,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
content: {
|
||
flex: 1,
|
||
padding: 16,
|
||
},
|
||
section: {
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 12,
|
||
padding: 16,
|
||
marginBottom: 16,
|
||
},
|
||
sectionTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
color: '#1A1A1A',
|
||
marginBottom: 8,
|
||
},
|
||
sectionDescription: {
|
||
fontSize: 14,
|
||
color: '#666666',
|
||
marginBottom: 16,
|
||
lineHeight: 20,
|
||
},
|
||
settingItem: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
paddingVertical: 12,
|
||
},
|
||
settingLabel: {
|
||
fontSize: 16,
|
||
color: '#1A1A1A',
|
||
},
|
||
settingDescription: {
|
||
fontSize: 14,
|
||
color: '#666666',
|
||
marginTop: -8,
|
||
marginBottom: 8,
|
||
lineHeight: 20,
|
||
},
|
||
timeSelector: {
|
||
marginBottom: 16,
|
||
},
|
||
timeLabel: {
|
||
fontSize: 16,
|
||
color: '#1A1A1A',
|
||
marginBottom: 8,
|
||
},
|
||
timeButtons: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
timeButton: {
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: 20,
|
||
backgroundColor: '#4CAF50',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginHorizontal: 16,
|
||
},
|
||
timeButtonDisabled: {
|
||
backgroundColor: '#E5E5E5',
|
||
},
|
||
timeButtonText: {
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
color: '#FFFFFF',
|
||
},
|
||
timeValue: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#1A1A1A',
|
||
minWidth: 60,
|
||
textAlign: 'center',
|
||
},
|
||
resetButton: {
|
||
backgroundColor: '#F44336',
|
||
paddingVertical: 12,
|
||
paddingHorizontal: 24,
|
||
borderRadius: 8,
|
||
alignItems: 'center',
|
||
},
|
||
resetButtonText: {
|
||
color: '#FFFFFF',
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
},
|
||
}); |