Files
digital-pilates/components/GoalTemplateModal.tsx
richarjiang 3f89023447 feat: 更新 CLAUDE.md 文件及多个组件以优化用户体验和功能
- 更新 CLAUDE.md 文件,重构架构部分,增加认证和数据层的描述
- 在 GoalsScreen 中新增目标模板选择功能,支持用户选择和创建目标
- 在 CreateGoalModal 中添加初始数据支持,优化目标创建体验
- 新增 GoalTemplateModal 组件,提供目标模板选择界面
- 更新 NotificationHelpers,支持构建深度链接以便于导航
- 在 CoachScreen 中处理路由参数,增强用户交互体验
- 更新多个组件的样式和逻辑,提升整体用户体验
- 删除不再使用的中文回复规则文档
2025-08-26 15:04:04 +08:00

295 lines
7.1 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 { getTemplatesByCategory, goalCategories, GoalTemplate } from '@/constants/goalTemplates';
import { useColorScheme } from '@/hooks/useColorScheme';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import React, { useState } from 'react';
import {
Dimensions,
Image,
Modal,
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
const { width: screenWidth } = Dimensions.get('window');
interface GoalTemplateModalProps {
visible: boolean;
onClose: () => void;
onSelectTemplate: (template: GoalTemplate) => void;
onCreateCustom: () => void;
}
export default function GoalTemplateModal({
visible,
onClose,
onSelectTemplate,
onCreateCustom
}: GoalTemplateModalProps) {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const colorTokens = Colors[theme];
const [selectedCategory, setSelectedCategory] = useState('recommended');
const currentTemplates = getTemplatesByCategory(selectedCategory);
// 渲染分类标签
const renderCategoryTab = (category: any) => {
const isSelected = selectedCategory === category.id;
return (
<TouchableOpacity
key={category.id}
style={[
styles.categoryTab,
isSelected && styles.categoryTabSelected
]}
onPress={() => setSelectedCategory(category.id)}
activeOpacity={0.8}
>
{category.isRecommended && (
<Image
source={require('@/assets/images/icons/icon-recommend.png')}
style={{ width: 24, height: 24 }}
/>
)}
<Text style={[
styles.categoryTabText,
isSelected && styles.categoryTabTextSelected
]}>
{category.title}
</Text>
</TouchableOpacity>
);
};
// 渲染模板项
const renderTemplateItem = (template: GoalTemplate) => (
<TouchableOpacity
key={template.id}
style={[styles.templateItem]}
onPress={() => onSelectTemplate(template)}
activeOpacity={0.8}
>
<View style={[styles.templateIcon]}>
<MaterialIcons
name={template.icon as any}
size={24}
color={template.iconColor}
/>
</View>
<Text style={[styles.templateTitle]}>
{template.title}
</Text>
</TouchableOpacity>
);
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={onClose}
>
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
{/* 头部 */}
<View style={styles.header}>
<TouchableOpacity
onPress={onClose}
style={styles.closeButton}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<MaterialIcons name="keyboard-arrow-down" size={24} color="#374151" />
</TouchableOpacity>
<Text style={styles.title}></Text>
<View style={{ width: 24 }} />
</View>
<ScrollView
style={styles.content}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* 创建自定义目标 */}
<TouchableOpacity
style={styles.customGoalButton}
onPress={onCreateCustom}
activeOpacity={0.8}
>
<View style={styles.customGoalIcon}>
<MaterialIcons name="add" size={24} color="#7C3AED" />
</View>
<Text style={styles.customGoalText}></Text>
</TouchableOpacity>
{/* 分类选择器 */}
<View style={styles.categorySection}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.categoryScrollContent}
style={styles.categoryScrollView}
>
{goalCategories.map(renderCategoryTab)}
</ScrollView>
</View>
{/* 当前分类的模板 */}
<View style={styles.templatesSection}>
<View style={styles.templatesGrid}>
{currentTemplates.map(renderTemplateItem)}
</View>
</View>
</ScrollView>
</SafeAreaView>
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F9FAFB',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingVertical: 16,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: '#E5E7EB',
},
closeButton: {
padding: 4,
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#111827',
textAlign: 'center',
},
content: {
flex: 1,
},
scrollContent: {
paddingBottom: 40,
},
customGoalButton: {
flexDirection: 'row',
alignItems: 'center',
marginHorizontal: 20,
marginTop: 20,
padding: 16,
backgroundColor: '#FFFFFF',
borderRadius: 16,
borderWidth: 1,
borderColor: '#E5E7EB',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 2,
elevation: 1,
},
customGoalIcon: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#F3E8FF',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
customGoalText: {
fontSize: 16,
fontWeight: '600',
color: '#374151',
},
categorySection: {
marginTop: 24,
},
categoryScrollView: {
paddingLeft: 20,
},
categoryScrollContent: {
paddingRight: 20,
},
categoryTab: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 10,
marginRight: 12,
borderRadius: 20,
backgroundColor: '#F3F4F6',
},
categoryTabSelected: {
backgroundColor: '#7C3AED',
},
categoryTabText: {
fontSize: 14,
fontWeight: '600',
color: '#374151',
},
categoryTabTextSelected: {
color: '#FFFFFF',
},
avatarGroup: {
flexDirection: 'row',
marginRight: 8,
},
avatar: {
width: 16,
height: 16,
borderRadius: 8,
borderWidth: 1,
borderColor: '#FFFFFF',
},
templatesSection: {
marginTop: 24,
},
templatesGrid: {
paddingHorizontal: 20,
flexDirection: 'row',
flexWrap: 'wrap',
gap: 12,
},
templateItem: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFFFFF',
minWidth: (screenWidth - 60) / 2, // 2列布局考虑间距和padding
maxWidth: (screenWidth - 60) / 2,
aspectRatio: 2.2,
borderRadius: 16,
padding: 16,
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 3,
elevation: 2,
},
templateIcon: {
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center',
},
templateTitle: {
fontSize: 14,
color: '#374151',
fontWeight: '600',
lineHeight: 20,
marginLeft: 12,
},
});