- 新增目标通知功能,支持根据用户创建目标时选择的频率和开始时间自动创建本地定时推送通知 - 实现每日、每周和每月的重复类型,用户可自定义选择提醒时间和重复规则 - 集成目标通知测试组件,方便开发者测试不同类型的通知 - 更新相关文档,详细描述目标通知功能的实现和使用方法 - 优化目标页面,确保用户体验和界面一致性
198 lines
4.7 KiB
TypeScript
198 lines
4.7 KiB
TypeScript
import { Colors } from '@/constants/Colors';
|
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import dayjs from 'dayjs';
|
|
import React from 'react';
|
|
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
|
|
export interface GoalItem {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
time: string;
|
|
category: 'workout' | 'finance' | 'personal' | 'work' | 'health';
|
|
priority?: 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
interface GoalCardProps {
|
|
item: GoalItem;
|
|
onPress?: (item: GoalItem) => void;
|
|
}
|
|
|
|
const { width: screenWidth } = Dimensions.get('window');
|
|
const CARD_WIDTH = (screenWidth - 60) * 0.65; // 显示1.5张卡片
|
|
|
|
const getCategoryIcon = (category: GoalItem['category']) => {
|
|
switch (category) {
|
|
case 'workout':
|
|
return 'fitness-outline';
|
|
case 'finance':
|
|
return 'card-outline';
|
|
case 'personal':
|
|
return 'person-outline';
|
|
case 'work':
|
|
return 'briefcase-outline';
|
|
case 'health':
|
|
return 'heart-outline';
|
|
default:
|
|
return 'checkmark-circle-outline';
|
|
}
|
|
};
|
|
|
|
const getCategoryColor = (category: GoalItem['category']) => {
|
|
switch (category) {
|
|
case 'workout':
|
|
return '#FF6B6B';
|
|
case 'finance':
|
|
return '#4ECDC4';
|
|
case 'personal':
|
|
return '#45B7D1';
|
|
case 'work':
|
|
return '#96CEB4';
|
|
case 'health':
|
|
return '#FFEAA7';
|
|
default:
|
|
return '#DDA0DD';
|
|
}
|
|
};
|
|
|
|
const getPriorityColor = (priority: GoalItem['priority']) => {
|
|
switch (priority) {
|
|
case 'high':
|
|
return '#FF4757';
|
|
case 'medium':
|
|
return '#FFA502';
|
|
case 'low':
|
|
return '#2ED573';
|
|
default:
|
|
return '#747D8C';
|
|
}
|
|
};
|
|
|
|
export function GoalCard({ item, onPress }: GoalCardProps) {
|
|
const theme = useColorScheme() ?? 'light';
|
|
const colorTokens = Colors[theme];
|
|
|
|
const categoryColor = getCategoryColor(item.category);
|
|
const categoryIcon = getCategoryIcon(item.category);
|
|
const priorityColor = getPriorityColor(item.priority);
|
|
|
|
const timeFormatted = dayjs(item.time).format('HH:mm');
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.container, { backgroundColor: colorTokens.card }]}
|
|
onPress={() => onPress?.(item)}
|
|
activeOpacity={0.8}
|
|
>
|
|
{/* 顶部标签和优先级 */}
|
|
<View style={styles.header}>
|
|
<View style={[styles.categoryBadge, { backgroundColor: categoryColor }]}>
|
|
<Ionicons name={categoryIcon as any} size={12} color="#fff" />
|
|
<Text style={styles.categoryText}>{item.category}</Text>
|
|
</View>
|
|
|
|
{item.priority && (
|
|
<View style={[styles.priorityDot, { backgroundColor: priorityColor }]} />
|
|
)}
|
|
</View>
|
|
|
|
{/* 主要内容 */}
|
|
<View style={styles.content}>
|
|
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
|
|
{item.title}
|
|
</Text>
|
|
|
|
{item.description && (
|
|
<Text style={[styles.description, { color: colorTokens.textSecondary }]} numberOfLines={2}>
|
|
{item.description}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* 底部时间 */}
|
|
<View style={styles.footer}>
|
|
<View style={styles.timeContainer}>
|
|
<Ionicons
|
|
name='time-outline'
|
|
size={14}
|
|
color={colorTokens.textMuted}
|
|
/>
|
|
<Text style={[styles.timeText, { color: colorTokens.textMuted }]}>
|
|
{timeFormatted}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
width: CARD_WIDTH,
|
|
height: 140,
|
|
marginHorizontal: 8,
|
|
borderRadius: 20,
|
|
padding: 16,
|
|
elevation: 6,
|
|
position: 'relative',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
categoryBadge: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 12,
|
|
backgroundColor: '#4ECDC4',
|
|
},
|
|
categoryText: {
|
|
fontSize: 10,
|
|
fontWeight: '600',
|
|
color: '#fff',
|
|
marginLeft: 4,
|
|
textTransform: 'capitalize',
|
|
},
|
|
priorityDot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: '#FF4757',
|
|
},
|
|
content: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
},
|
|
title: {
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
lineHeight: 20,
|
|
marginBottom: 4,
|
|
},
|
|
description: {
|
|
fontSize: 12,
|
|
lineHeight: 16,
|
|
opacity: 0.7,
|
|
},
|
|
footer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginTop: 12,
|
|
},
|
|
timeContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
timeText: {
|
|
fontSize: 12,
|
|
fontWeight: '500',
|
|
marginLeft: 4,
|
|
},
|
|
});
|