Files
digital-pilates/components/model/CreateGoalModal.tsx
richarjiang 533b40a12d feat: 更新 CoachScreen 和欢迎消息生成逻辑
- 在 CoachScreen 中优化欢迎消息的生成,整合用户配置文件数据,支持选择选项和表情
- 更新欢迎消息生成函数,返回包含内容、选择和交互类型的结构
- 在多个组件中调整样式,提升用户体验和界面一致性
- 在 Statistics 组件中添加记录更新时间,确保数据展示的准确性
- 在 FitnessRingsCard 中修正卡路里和运动时间的显示,确保数值四舍五入
2025-08-27 08:15:42 +08:00

1107 lines
35 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { CreateGoalRequest, GoalPriority, RepeatType, UpdateGoalRequest } from '@/types/goals';
import { Ionicons } from '@expo/vector-icons';
import DateTimePicker from '@react-native-community/datetimepicker';
import { LinearGradient } from 'expo-linear-gradient';
import React, { useEffect, useState } from 'react';
import {
Alert,
Image,
Modal,
Platform,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import WheelPickerExpo from 'react-native-wheel-picker-expo';
interface CreateGoalModalProps {
visible: boolean;
onClose: () => void;
onSubmit: (goalData: CreateGoalRequest) => void;
onUpdate?: (goalId: string, goalData: UpdateGoalRequest) => void;
onSuccess?: () => void;
loading?: boolean;
initialData?: Partial<CreateGoalRequest>;
editGoalId?: string;
}
const REPEAT_TYPE_OPTIONS: { value: RepeatType; label: string }[] = [
{ value: 'daily', label: '每日' },
{ value: 'weekly', label: '每周' },
{ value: 'monthly', label: '每月' },
];
const FREQUENCY_OPTIONS = Array.from({ length: 30 }, (_, i) => i + 1);
export const CreateGoalModal: React.FC<CreateGoalModalProps> = ({
visible,
onClose,
onSubmit,
onUpdate,
onSuccess,
loading = false,
initialData,
editGoalId,
}) => {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const colorTokens = Colors[theme];
// 表单状态
const [title, setTitle] = useState(initialData?.title || '');
const [description, setDescription] = useState(initialData?.description || '');
const [repeatType, setRepeatType] = useState<RepeatType>(initialData?.repeatType || 'daily');
const [frequency, setFrequency] = useState(initialData?.frequency || 1);
const [hasReminder, setHasReminder] = useState(initialData?.hasReminder || false);
const [showFrequencyPicker, setShowFrequencyPicker] = useState(false);
const [showRepeatTypePicker, setShowRepeatTypePicker] = useState(false);
const [reminderTime, setReminderTime] = useState(initialData?.reminderTime || '20:00');
const [category, setCategory] = useState(initialData?.category || '');
const [priority, setPriority] = useState<GoalPriority>(initialData?.priority || 5);
const [showTimePicker, setShowTimePicker] = useState(false);
const [tempSelectedTime, setTempSelectedTime] = useState<Date | null>(null);
// 周几选择状态
const [selectedWeekdays, setSelectedWeekdays] = useState<number[]>(
initialData?.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]
); // 默认周一到周五
// 每月日期选择状态
const [selectedMonthDays, setSelectedMonthDays] = useState<number[]>(
initialData?.customRepeatRule?.dayOfMonth || [1, 15]
); // 默认1号和15号
// 结束日期选择状态
const [endDate, setEndDate] = useState<string | null>(initialData?.endDate || null);
const [showDatePicker, setShowDatePicker] = useState(false);
const [tempSelectedDate, setTempSelectedDate] = useState<Date | null>(null);
// 当 initialData 变化时更新表单状态
useEffect(() => {
if (initialData) {
setTitle(initialData.title || '');
setDescription(initialData.description || '');
setRepeatType(initialData.repeatType || 'daily');
setFrequency(initialData.frequency || 1);
setHasReminder(initialData.hasReminder || false);
setReminderTime(initialData.reminderTime || '20:00');
setCategory(initialData.category || '');
setPriority(initialData.priority || 5);
setSelectedWeekdays(initialData.customRepeatRule?.weekdays || [1, 2, 3, 4, 5]);
setSelectedMonthDays(initialData.customRepeatRule?.dayOfMonth || [1, 15]);
setEndDate(initialData.endDate || null);
}
}, [initialData]);
// 重置表单
const resetForm = () => {
setTitle('');
setDescription('');
setRepeatType('daily');
setFrequency(1);
setHasReminder(false);
setReminderTime('20:00');
setCategory('');
setPriority(5);
setSelectedWeekdays([1, 2, 3, 4, 5]);
setSelectedMonthDays([1, 15]);
setEndDate(null);
};
// 处理关闭
const handleClose = () => {
if (!loading) {
resetForm();
onClose();
}
};
// 处理提交
const handleSubmit = () => {
if (!title.trim()) {
Alert.alert('提示', '请输入目标标题');
return;
}
// 计算startTime从reminderTime中获取小时和分钟转换为当天的分钟数
let startTime: number | undefined;
if (reminderTime) {
const [hours, minutes] = reminderTime.split(':').map(Number);
startTime = hours * 60 + minutes;
}
// 根据是否是编辑模式决定数据结构
if (editGoalId && onUpdate) {
// 更新模式:使用 UpdateGoalRequest 结构
const updateData: UpdateGoalRequest = {
title: title.trim(),
description: description.trim() || undefined,
repeatType,
frequency,
category: category.trim() || undefined,
priority,
hasReminder,
reminderTime: hasReminder ? reminderTime : undefined,
customRepeatRule: {
weekdays: repeatType === 'weekly' ? selectedWeekdays : [1, 2, 3, 4, 5, 6, 0],
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined,
},
endDate: endDate || undefined,
};
console.log('updateData', updateData);
onUpdate(editGoalId, updateData);
} else {
// 创建模式:使用 CreateGoalRequest 结构
const goalData: CreateGoalRequest = {
title: title.trim(),
description: description.trim() || undefined,
repeatType,
frequency,
category: category.trim() || undefined,
priority,
hasReminder,
reminderTime: hasReminder ? reminderTime : undefined,
customRepeatRule: {
weekdays: repeatType === 'weekly' ? selectedWeekdays : [1, 2, 3, 4, 5, 6, 0],
dayOfMonth: repeatType === 'monthly' ? selectedMonthDays : undefined,
},
startTime,
endDate: endDate || undefined,
};
console.log('goalData', goalData);
onSubmit(goalData);
}
// 通知父组件提交成功
if (onSuccess) {
onSuccess();
}
};
// 时间选择器
const handleTimeChange = (event: any, selectedDate?: Date) => {
if (Platform.OS === 'android') {
// Android: 用户点击系统确认按钮后自动关闭
if (event.type === 'set' && selectedDate) {
const hours = selectedDate.getHours().toString().padStart(2, '0');
const minutes = selectedDate.getMinutes().toString().padStart(2, '0');
setReminderTime(`${hours}:${minutes}`);
}
setShowTimePicker(false);
} else {
// iOS: 只在用户点击自定义确认按钮时更新
if (selectedDate) {
setTempSelectedTime(selectedDate);
}
}
};
const handleConfirmTime = () => {
setShowTimePicker(false);
if (tempSelectedTime) {
const hours = tempSelectedTime.getHours().toString().padStart(2, '0');
const minutes = tempSelectedTime.getMinutes().toString().padStart(2, '0');
setReminderTime(`${hours}:${minutes}`);
}
setTempSelectedTime(null);
};
const handleCancelTime = () => {
setShowTimePicker(false);
setTempSelectedTime(null);
};
const showTimePickerModal = () => {
setShowTimePicker(true);
};
// 获取当前时间对应的Date对象
const getCurrentTimeDate = () => {
const [hours, minutes] = reminderTime.split(':').map(Number);
const date = new Date();
date.setHours(hours, minutes, 0, 0);
return date;
};
// 日期选择器处理
const handleDateChange = (event: any, selectedDate?: Date) => {
if (Platform.OS === 'android') {
if (event.type === 'set' && selectedDate) {
const isoDate = selectedDate.toISOString().split('T')[0];
setEndDate(isoDate);
}
setShowDatePicker(false);
} else {
if (selectedDate) {
setTempSelectedDate(selectedDate);
}
}
};
const handleConfirmDate = () => {
setShowDatePicker(false);
if (tempSelectedDate) {
const isoDate = tempSelectedDate.toISOString().split('T')[0];
setEndDate(isoDate);
}
setTempSelectedDate(null);
};
const handleCancelDate = () => {
setShowDatePicker(false);
setTempSelectedDate(null);
};
const showDatePickerModal = () => {
setShowDatePicker(true);
};
// 获取当前结束日期对应的Date对象
const getCurrentEndDate = () => {
if (endDate) {
return new Date(endDate + 'T00:00:00');
}
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow;
};
// 格式化显示日期
const formatDisplayDate = (dateString: string) => {
const date = new Date(dateString + 'T00:00:00');
return `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`;
};
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={handleClose}
>
<View style={[styles.container, { backgroundColor: colorTokens.background }]}>
{/* 渐变背景 */}
<LinearGradient
colors={['#F0F9FF', '#E0F2FE']}
style={styles.gradientBackground}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
{/* 装饰性圆圈 */}
<View style={styles.decorativeCircle1} />
<View style={styles.decorativeCircle2} />
{/* 头部 */}
<View style={styles.header}>
<TouchableOpacity onPress={handleClose} disabled={loading}>
<Ionicons name="close" size={24} color={colorTokens.text} />
</TouchableOpacity>
<Text style={[styles.title, { color: colorTokens.text }]}>
{editGoalId ? '编辑目标' : '创建新目标'}
</Text>
<View style={{ width: 24 }} />
</View>
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
{/* 目标标题输入 */}
<View style={styles.section}>
<View style={styles.iconTitleContainer}>
{/* <View style={styles.iconPlaceholder}>
<Text style={styles.iconText}>图标</Text>
</View> */}
<TextInput
style={[styles.titleInput, { color: colorTokens.text }]}
placeholder="写点什么..."
// placeholderTextColor={colorTokens.textSecondary}
value={title}
onChangeText={setTitle}
multiline
maxLength={100}
/>
</View>
{/* 装饰图案 */}
<View style={styles.decorationContainer}>
<View style={styles.decoration} />
</View>
</View>
{/* 目标重复周期 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<TouchableOpacity style={styles.optionValue} onPress={() => setShowRepeatTypePicker(true)}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-calender.png')}
style={styles.optionIconImage}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{repeatType === 'weekly' && selectedWeekdays.length > 0
? selectedWeekdays.length <= 3
? `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedWeekdays.map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}`
: `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedWeekdays.slice(0, 2).map(day => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(' ')}${selectedWeekdays.length}`
: repeatType === 'monthly' && selectedMonthDays.length > 0
? selectedMonthDays.length <= 3
? `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedMonthDays.map(day => `${day}`).join(' ')}`
: `${REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label} ${selectedMonthDays.slice(0, 2).map(day => `${day}`).join(' ')}${selectedMonthDays.length}`
: REPEAT_TYPE_OPTIONS.find(opt => opt.value === repeatType)?.label
}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</View>
</TouchableOpacity>
</View>
{/* 重复周期选择器弹窗 */}
<Modal
visible={showRepeatTypePicker}
transparent
animationType="fade"
onRequestClose={() => setShowRepeatTypePicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={() => setShowRepeatTypePicker(false)}
/>
<View style={styles.repeatTypeModalSheet}>
{/* 关闭按钮 */}
<TouchableOpacity
style={styles.modalCloseButton}
onPress={() => setShowRepeatTypePicker(false)}
>
<Text style={styles.modalCloseButtonText}>×</Text>
</TouchableOpacity>
{/* 标题 */}
<Text style={styles.repeatTypeModalTitle}></Text>
{/* 重复类型选择 */}
<View style={styles.repeatTypeOptions}>
{REPEAT_TYPE_OPTIONS.map((option) => (
<TouchableOpacity
key={option.value}
style={[
styles.repeatTypeButton,
repeatType === option.value && styles.repeatTypeButtonSelected
]}
onPress={() => setRepeatType(option.value)}
>
<Text style={[
styles.repeatTypeButtonText,
repeatType === option.value && styles.repeatTypeButtonTextSelected
]}>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
{/* 周几选择 - 仅在选择每周时显示 */}
{repeatType === 'weekly' && (
<View style={styles.weekdaySelection}>
<View style={styles.weekdayOptions}>
{[
{ value: 0, label: '周日' },
{ value: 1, label: '周一' },
{ value: 2, label: '周二' },
{ value: 3, label: '周三' },
{ value: 4, label: '周四' },
{ value: 5, label: '周五' },
{ value: 6, label: '周六' },
].map((weekday) => (
<TouchableOpacity
key={weekday.value}
style={[
styles.weekdayButton,
selectedWeekdays.includes(weekday.value) && styles.weekdayButtonSelected
]}
onPress={() => {
if (selectedWeekdays.includes(weekday.value)) {
setSelectedWeekdays(selectedWeekdays.filter(day => day !== weekday.value));
} else {
setSelectedWeekdays([...selectedWeekdays, weekday.value]);
}
}}
>
<Text style={[
styles.weekdayButtonText,
selectedWeekdays.includes(weekday.value) && styles.weekdayButtonTextSelected
]}>
{weekday.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* 每月日期选择 - 仅在选择每月时显示 */}
{repeatType === 'monthly' && (
<View style={styles.monthDaySelection}>
<View style={styles.monthDayGrid}>
{Array.from({ length: 31 }, (_, i) => i + 1).map((day) => (
<TouchableOpacity
key={day}
style={[
styles.monthDayGridItem,
selectedMonthDays.includes(day) && styles.monthDayGridItemSelected
]}
onPress={() => {
if (selectedMonthDays.includes(day)) {
setSelectedMonthDays(selectedMonthDays.filter(d => d !== day));
} else {
setSelectedMonthDays([...selectedMonthDays, day]);
}
}}
activeOpacity={0.8}
>
<Text style={[
styles.monthDayGridText,
selectedMonthDays.includes(day) && styles.monthDayGridTextSelected
]}>
{day}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
{/* 完成按钮 */}
<TouchableOpacity
style={styles.repeatTypeCompleteButton}
onPress={() => setShowRepeatTypePicker(false)}
>
<Text style={styles.repeatTypeCompleteButtonText}></Text>
</TouchableOpacity>
</View>
</Modal>
{/* 频率设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<TouchableOpacity style={styles.optionValue} onPress={() => setShowFrequencyPicker(true)}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-fire.png')}
style={styles.optionIconImage}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{frequency}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</View>
</TouchableOpacity>
</View>
{/* 频率选择器弹窗 */}
<Modal
visible={showFrequencyPicker}
transparent
animationType="fade"
onRequestClose={() => setShowFrequencyPicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={() => setShowFrequencyPicker(false)}
/>
<View style={styles.modalSheet}>
<View style={{ height: 200 }}>
<WheelPickerExpo
height={200}
width={150}
initialSelectedIndex={frequency - 1}
items={FREQUENCY_OPTIONS.map(num => ({ label: num.toString(), value: num }))}
onChange={({ item }) => setFrequency(item.value)}
backgroundColor={colorTokens.card}
// selectedStyle={{ borderColor: colorTokens.primary, borderWidth: 2 }}
haptics
/>
</View>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn]}
onPress={() => setShowFrequencyPicker(false)}
>
<Text style={styles.modalBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.modalBtnPrimary]}
onPress={() => setShowFrequencyPicker(false)}
>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
</TouchableOpacity>
</View>
)}
</View>
</Modal>
{/* 提醒设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-bell.png')}
style={styles.optionIconImage}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<Switch
value={hasReminder}
onValueChange={setHasReminder}
trackColor={{ false: '#E5E5E5', true: '#6366F1' }}
thumbColor={hasReminder ? '#FFFFFF' : '#FFFFFF'}
/>
</View>
</View>
{/* 时间设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-clock.png')}
style={styles.optionIconImage}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<TouchableOpacity
style={[styles.optionValue]}
onPress={showTimePickerModal}
>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{reminderTime}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</TouchableOpacity>
</View>
</View>
{/* 结束日期设置 */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<View style={styles.optionHeader}>
<View style={styles.optionIcon}>
<Image
source={require('@/assets/images/icons/icon-calender.png')}
style={styles.optionIconImage}
/>
</View>
<Text style={[styles.optionLabel, { color: colorTokens.text }]}>
</Text>
<TouchableOpacity
style={[styles.optionValue]}
onPress={showDatePickerModal}
>
<Text style={[styles.optionValueText, { color: colorTokens.textSecondary }]}>
{endDate ? formatDisplayDate(endDate) : '无限制'}
</Text>
<Text style={[styles.chevron, { color: colorTokens.textSecondary }]}>
</Text>
</TouchableOpacity>
</View>
{endDate && (
<TouchableOpacity
style={styles.clearEndDateButton}
onPress={() => setEndDate(null)}
>
<Text style={styles.clearEndDateText}></Text>
</TouchableOpacity>
)}
</View>
{/* 时间选择器弹窗 */}
<Modal
visible={showTimePicker}
transparent
animationType="fade"
onRequestClose={() => setShowTimePicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={handleCancelTime}
/>
<View style={styles.modalSheet}>
<DateTimePicker
value={tempSelectedTime || getCurrentTimeDate()}
mode="time"
is24Hour={true}
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
onChange={handleTimeChange}
/>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn]}
onPress={handleCancelTime}
>
<Text style={styles.modalBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.modalBtnPrimary]}
onPress={handleConfirmTime}
>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
</TouchableOpacity>
</View>
)}
</View>
</Modal>
{/* 日期选择器弹窗 */}
<Modal
visible={showDatePicker}
transparent
animationType="fade"
onRequestClose={() => setShowDatePicker(false)}
>
<TouchableOpacity
style={styles.modalBackdrop}
activeOpacity={1}
onPress={handleCancelDate}
/>
<View style={styles.modalSheet}>
<DateTimePicker
value={tempSelectedDate || getCurrentEndDate()}
mode="date"
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
onChange={handleDateChange}
minimumDate={new Date()}
locale="zh-CN"
/>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<TouchableOpacity
style={[styles.modalBtn]}
onPress={handleCancelDate}
>
<Text style={styles.modalBtnText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalBtn, styles.modalBtnPrimary]}
onPress={handleConfirmDate}
>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
</TouchableOpacity>
</View>
)}
</View>
</Modal>
{/* 描述输入(可选) */}
<View style={[styles.optionCard, { backgroundColor: colorTokens.card }]}>
<TextInput
style={[styles.descriptionInput, { color: colorTokens.text }]}
placeholder="添加描述(可选)"
placeholderTextColor={colorTokens.textSecondary}
value={description}
onChangeText={setDescription}
multiline
maxLength={500}
/>
</View>
</ScrollView>
{/* 保存按钮 */}
<View style={styles.footer}>
<TouchableOpacity
style={[
styles.saveButton,
{ opacity: loading || !title.trim() ? 0.5 : 1 }
]}
onPress={handleSubmit}
disabled={loading || !title.trim()}
>
<Text style={styles.saveButtonText}>
{loading ? (editGoalId ? '更新中...' : '保存中...') : (editGoalId ? '更新' : '保存')}
</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
gradientBackground: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
opacity: 0.6,
},
decorativeCircle1: {
position: 'absolute',
top: -20,
right: -20,
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#0EA5E9',
opacity: 0.1,
},
decorativeCircle2: {
position: 'absolute',
bottom: -15,
left: -15,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#0EA5E9',
opacity: 0.05,
},
container: {
flex: 1,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingTop: 20,
paddingBottom: 20,
},
cancelButton: {
fontSize: 24,
fontWeight: '600',
},
title: {
fontSize: 18,
fontWeight: '600',
},
content: {
flex: 1,
paddingHorizontal: 20,
},
section: {
},
iconTitleContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 16,
},
iconPlaceholder: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#F3F4F6',
alignItems: 'center',
justifyContent: 'center',
marginRight: 16,
},
iconText: {
fontSize: 12,
color: '#9CA3AF',
fontWeight: '500',
},
titleInput: {
flex: 1,
fontSize: 16,
fontWeight: '500',
minHeight: 60,
textAlignVertical: 'top',
},
decorationContainer: {
alignItems: 'flex-end',
paddingRight: 20,
},
decoration: {
width: 80,
height: 60,
backgroundColor: '#E0E7FF',
borderRadius: 40,
opacity: 0.6,
},
optionCard: {
borderRadius: 16,
marginBottom: 12,
overflow: 'hidden',
},
optionHeader: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
},
optionIcon: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#F3F4F6',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
optionIconText: {
fontSize: 16,
},
optionIconImage: {
width: 20,
height: 20,
resizeMode: 'contain',
},
optionLabel: {
flex: 1,
fontSize: 16,
fontWeight: '500',
},
optionValue: {
flexDirection: 'row',
alignItems: 'center',
},
optionValueText: {
fontSize: 16,
fontWeight: '500',
marginRight: 8,
},
chevron: {
fontSize: 20,
fontWeight: '300',
},
descriptionInput: {
padding: 16,
fontSize: 16,
minHeight: 80,
textAlignVertical: 'top',
},
footer: {
padding: 20,
paddingBottom: 40,
},
saveButton: {
backgroundColor: '#6366F1',
borderRadius: 16,
paddingVertical: 16,
alignItems: 'center',
},
saveButtonText: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: '600',
},
modalBackdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.35)',
},
modalSheet: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
padding: 16,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
},
modalActions: {
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: 8,
gap: 12,
},
modalBtn: {
paddingHorizontal: 14,
paddingVertical: 10,
borderRadius: 10,
backgroundColor: '#F1F5F9',
},
modalBtnPrimary: {
backgroundColor: '#6366F1',
},
modalBtnText: {
color: '#334155',
fontWeight: '700',
},
modalBtnTextPrimary: {
color: '#FFFFFF',
fontWeight: '700',
},
// 重复类型选择器弹窗样式
repeatTypeModalSheet: {
position: 'absolute',
left: 20,
right: 20,
top: '50%',
transform: [{ translateY: -300 }],
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 24,
alignItems: 'center',
maxHeight: 500,
},
modalCloseButton: {
position: 'absolute',
top: 16,
left: 16,
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center',
},
modalCloseButtonText: {
fontSize: 20,
color: '#666666',
fontWeight: '300',
},
repeatTypeModalTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333333',
marginBottom: 20,
textAlign: 'center',
},
repeatTypeOptions: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 8,
marginBottom: 20,
},
repeatTypeButton: {
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 20,
backgroundColor: '#F5F5F5',
borderWidth: 1,
borderColor: '#E5E5E5',
marginHorizontal: 4,
},
repeatTypeButtonSelected: {
backgroundColor: '#E6E6FA',
borderColor: '#8A2BE2',
},
repeatTypeButtonText: {
fontSize: 14,
color: '#666666',
fontWeight: '500',
},
repeatTypeButtonTextSelected: {
color: '#8A2BE2',
fontWeight: '600',
},
weekdaySelection: {
width: '100%',
marginBottom: 20,
},
weekdayOptions: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 6,
},
weekdayButton: {
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 16,
backgroundColor: '#F5F5F5',
borderWidth: 1,
borderColor: '#E5E5E5',
marginHorizontal: 2,
},
weekdayButtonSelected: {
backgroundColor: '#8A2BE2',
borderColor: '#8A2BE2',
},
weekdayButtonText: {
fontSize: 12,
color: '#666666',
fontWeight: '500',
},
weekdayButtonTextSelected: {
color: '#FFFFFF',
fontWeight: '600',
},
// 每月日期选择样式 - 日历网格布局
monthDaySelection: {
width: '100%',
marginBottom: 20,
},
monthDayGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
paddingHorizontal: 8,
},
monthDayGridItem: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#F5F5F5',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 10,
marginRight: 8,
},
monthDayGridItemSelected: {
backgroundColor: '#8A2BE2',
},
monthDayGridText: {
fontSize: 12,
fontWeight: '600',
color: '#666666',
},
monthDayGridTextSelected: {
color: '#FFFFFF',
},
repeatTypeCompleteButton: {
width: '100%',
backgroundColor: '#8A2BE2',
borderRadius: 20,
paddingVertical: 14,
alignItems: 'center',
marginTop: 10,
},
repeatTypeCompleteButtonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
// 清除结束日期按钮样式
clearEndDateButton: {
paddingHorizontal: 16,
paddingVertical: 8,
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: '#F1F5F9',
},
clearEndDateText: {
color: '#EF4444',
fontSize: 14,
fontWeight: '500',
},
});
export default CreateGoalModal;