feat: 实现目标列表左滑删除功能及相关组件

- 在目标列表中添加左滑删除功能,用户可通过左滑手势显示删除按钮并确认删除目标
- 修改 GoalCard 组件,使用 Swipeable 组件包装卡片内容,支持删除操作
- 更新目标列表页面,集成删除目标的逻辑,确保与 Redux 状态管理一致
- 添加开发模式下的模拟数据,方便测试删除功能
- 更新相关文档,详细描述左滑删除功能的实现和使用方法
This commit is contained in:
2025-08-23 17:58:39 +08:00
parent 20a244e375
commit 4f2bd76b8f
14 changed files with 1592 additions and 732 deletions

View File

@@ -4,7 +4,6 @@ import { useAppDispatch } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
import { completeTask, skipTask } from '@/store/tasksSlice';
import { TaskListItem } from '@/types/goals';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { useRouter } from 'expo-router';
import React from 'react';
import { Alert, Animated, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
@@ -27,7 +26,7 @@ export const TaskCard: React.FC<TaskCardProps> = ({
// 当任务进度变化时,启动动画
React.useEffect(() => {
const targetProgress = task.progressPercentage > 0 ? Math.min(task.progressPercentage, 100) : 2;
const targetProgress = task.progressPercentage > 0 ? Math.min(task.progressPercentage, 100) : 6;
Animated.timing(progressAnimation, {
toValue: targetProgress,
@@ -36,7 +35,6 @@ export const TaskCard: React.FC<TaskCardProps> = ({
}).start();
}, [task.progressPercentage, progressAnimation]);
const getStatusText = (status: string) => {
switch (status) {
case 'completed':
@@ -179,51 +177,93 @@ export const TaskCard: React.FC<TaskCardProps> = ({
);
};
return (
<TouchableOpacity
style={[styles.container, { backgroundColor: colorTokens.background }]}
style={[
styles.container,
{
backgroundColor: task.status === 'completed'
? 'rgba(248, 250, 252, 0.8)' // 已完成任务使用稍微透明的背景色
: colorTokens.background
}
]}
onPress={handleTaskPress}
activeOpacity={0.7}
>
{/* 头部区域 */}
<View style={styles.header}>
<View style={styles.titleSection}>
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
<View style={styles.cardContent}>
{/* 左侧图标区域 */}
<View style={styles.iconSection}>
<View style={[
styles.iconCircle,
{
backgroundColor: task.status === 'completed'
? '#EDE9FE' // 完成状态使用更深的紫色背景
: '#F3E8FF',
}
]}>
<Image
source={require('@/assets/images/task/icon-copy.png')}
style={styles.taskIcon}
resizeMode="contain"
/>
</View>
</View>
{/* 右侧信息区域 */}
<View style={styles.infoSection}>
{/* 任务标题 */}
<Text style={[
styles.title,
{
color: task.status === 'completed'
? '#6B7280'
: colorTokens.text,
}
]} numberOfLines={1}>
{task.title}
</Text>
{renderActionIcons()}
{/* 进度条 */}
<View style={styles.progressContainer}>
{/* 背景进度条 */}
<View style={styles.progressBackground} />
{/* 实际进度条 */}
<Animated.View
style={[
styles.progressBar,
{
width: progressAnimation.interpolate({
inputRange: [0, 100],
outputRange: ['6%', '100%'], // 最小显示6%确保可见
}),
backgroundColor: task.status === 'completed'
? '#8B5CF6' // 完成状态也使用紫色
: task.progressPercentage > 0
? '#8B5CF6'
: '#C7D2FE', // 浅紫色,表示待开始
},
]}
/>
{/* 进度文字 */}
<Text style={[
styles.progressText,
{
color: task.progressPercentage > 20 || task.status === 'completed'
? '#FFFFFF'
: '#374151', // 进度较少时使用深色文字
}
]}>
{task.currentCount}/{task.targetCount}
</Text>
</View>
</View>
</View>
{/* 状态和优先级标签 */}
<View style={styles.tagsContainer}>
<View style={styles.statusTag}>
<MaterialIcons name="schedule" size={12} color="#6B7280" />
<Text style={styles.statusTagText}>{getStatusText(task.status)}</Text>
</View>
</View>
{/* 进度条 */}
<View style={styles.progressBar}>
<Animated.View
style={[
styles.progressFill,
{
width: progressAnimation.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%'],
}),
backgroundColor: task.progressPercentage > 0 ? colorTokens.primary : '#E5E7EB',
},
]}
/>
{task.progressPercentage > 0 && task.progressPercentage < 100 && (
<View style={styles.progressGlow} />
)}
{/* 进度百分比文本 */}
<View style={styles.progressTextContainer}>
<Text style={styles.progressText}>{task.currentCount}/{task.targetCount}</Text>
</View>
{/* 操作按钮 */}
{renderActionIcons()}
</View>
</TouchableOpacity>
);
@@ -231,8 +271,8 @@ export const TaskCard: React.FC<TaskCardProps> = ({
const styles = StyleSheet.create({
container: {
padding: 16,
borderRadius: 12,
padding: 14,
borderRadius: 30,
marginBottom: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
@@ -240,6 +280,91 @@ const styles = StyleSheet.create({
shadowRadius: 4,
elevation: 3,
},
cardContent: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
},
iconSection: {
flexShrink: 0,
},
iconCircle: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#F3E8FF', // 浅紫色背景
alignItems: 'center',
justifyContent: 'center',
},
infoSection: {
flex: 1,
gap: 8,
},
title: {
fontSize: 15,
fontWeight: '600',
lineHeight: 20,
color: '#1F2937', // 深蓝紫色文字
marginBottom: 2,
},
progressContainer: {
position: 'relative',
height: 14,
justifyContent: 'center',
marginTop: 4,
},
progressBackground: {
position: 'absolute',
left: 0,
right: 0,
height: 14,
backgroundColor: '#F3F4F6',
borderRadius: 10,
},
progressBar: {
height: 14,
borderRadius: 10,
position: 'absolute',
left: 0,
minWidth: '6%', // 确保最小宽度可见
},
progressText: {
fontSize: 12,
fontWeight: '600',
color: '#FFFFFF',
textAlign: 'center',
position: 'absolute',
left: 0,
right: 0,
zIndex: 1,
},
actionIconsContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
flexShrink: 0,
},
iconContainer: {
width: 28,
height: 28,
borderRadius: 14,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#F3F4F6',
},
skipIconContainer: {
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: '#F3F4F6',
alignItems: 'center',
justifyContent: 'center',
},
taskIcon: {
width: 18,
height: 18,
},
// 保留其他样式以备后用
header: {
marginBottom: 12,
},
@@ -248,38 +373,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
gap: 12,
},
title: {
fontSize: 16,
fontWeight: '600',
lineHeight: 22,
flex: 1,
},
actionIconsContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
flexShrink: 0,
},
iconContainer: {
width: 32,
height: 32,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#F3F4F6',
},
skipIconContainer: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#F3F4F6',
alignItems: 'center',
justifyContent: 'center',
},
taskIcon: {
width: 20,
height: 20,
},
tagsContainer: {
flexDirection: 'row',
gap: 8,
@@ -312,7 +405,7 @@ const styles = StyleSheet.create({
fontWeight: '500',
color: '#FFFFFF',
},
progressBar: {
progressBarOld: {
height: 6,
backgroundColor: '#F3F4F6',
borderRadius: 3,
@@ -360,11 +453,6 @@ const styles = StyleSheet.create({
borderColor: '#E5E7EB',
zIndex: 1,
},
progressText: {
fontSize: 10,
fontWeight: '600',
color: '#374151',
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
@@ -391,10 +479,6 @@ const styles = StyleSheet.create({
width: '100%',
height: '100%',
},
infoSection: {
flexDirection: 'row',
gap: 8,
},
infoTag: {
flexDirection: 'row',
alignItems: 'center',
@@ -409,3 +493,4 @@ const styles = StyleSheet.create({
color: '#374151',
},
});