首先我們先調整我們的任務細節頁面
// src\pages\task-detail.page.tsx
import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Switch, TextInput, ScrollView } from 'react-native';
import { Task } from '../types';
type TaskDetailsPageProps = {
isDarkMode: boolean;
task: Task;
onBack: () => void;
onTaskUpdate: (updatedTask: Task) => void;
onTaskDelete: (taskId: string) => void;
};
const TaskDetailsPage: React.FC<TaskDetailsPageProps> = ({
isDarkMode,
task,
onBack,
onTaskUpdate,
onTaskDelete,
}) => {
const [isEditing, setIsEditing] = useState(false);
const [editedTask, setEditedTask] = useState(task);
const handleEdit = useCallback(() => {
setIsEditing(true);
}, []);
const handleSave = useCallback(() => {
onTaskUpdate(editedTask);
setIsEditing(false);
}, [editedTask, onTaskUpdate]);
const handleDelete = useCallback(() => {
onTaskDelete(task.id);
}, [onTaskDelete, task.id]);
const toggleCompletion = useCallback(() => {
const updatedTask = { ...editedTask, isDone: !editedTask.isDone };
setEditedTask(updatedTask);
onTaskUpdate(updatedTask);
}, [editedTask, onTaskUpdate]);
const handleInputChange = useCallback((field: keyof Task, value: string) => {
setEditedTask(prev => ({ ...prev, [field]: value }));
}, []);
const addTag = useCallback(() => {
setEditedTask(prev => ({
...prev,
tags: [...prev.tags, { title: '新標籤' }],
}));
}, []);
const removeTag = useCallback((index: number) => {
setEditedTask(prev => ({
...prev,
tags: prev.tags.filter((_, i) => i !== index),
}));
}, []);
const updateTag = useCallback((index: number, newTitle: string) => {
setEditedTask(prev => ({
...prev,
tags: prev.tags.map((tag, i) => i === index ? { ...tag, title: newTitle } : tag),
}));
}, []);
return (
<ScrollView style={[styles.container, { backgroundColor: isDarkMode ? '#1a1a1a' : '#ffffff' }]}>
<TouchableOpacity style={styles.backButton} onPress={onBack}>
<Text style={[styles.backButtonText, { color: isDarkMode ? '#ffffff' : '#007AFF' }]}>返回</Text>
</TouchableOpacity>
{isEditing ? (
<>
<TextInput
style={[styles.input, { color: isDarkMode ? '#ffffff' : '#000000' }]}
value={editedTask.title}
onChangeText={(value) => handleInputChange('title', value)}
placeholder="任務標題"
placeholderTextColor={isDarkMode ? '#888888' : '#cccccc'}
/>
<TextInput
style={[styles.input, styles.descriptionInput, { color: isDarkMode ? '#ffffff' : '#000000' }]}
value={editedTask.description}
onChangeText={(value) => handleInputChange('description', value)}
placeholder="任務描述"
placeholderTextColor={isDarkMode ? '#888888' : '#cccccc'}
multiline
/>
<TextInput
style={[styles.input, { color: isDarkMode ? '#ffffff' : '#000000' }]}
value={editedTask?.startDate ?? ''}
onChangeText={(value) => handleInputChange('startDate', value)}
placeholder="開始日期 (YYYY-MM-DD)"
placeholderTextColor={isDarkMode ? '#888888' : '#cccccc'}
/>
<TextInput
style={[styles.input, { color: isDarkMode ? '#ffffff' : '#000000' }]}
value={editedTask?.endDate ?? ''}
onChangeText={(value) => handleInputChange('endDate', value)}
placeholder="結束日期 (YYYY-MM-DD)"
placeholderTextColor={isDarkMode ? '#888888' : '#cccccc'}
/>
</>
) : (
<>
<Text style={[styles.title, { color: isDarkMode ? '#ffffff' : '#000000' }]}>{task.title}</Text>
<Text style={[styles.description, { color: isDarkMode ? '#dddddd' : '#333333' }]}>{task.description}</Text>
<Text style={[styles.info, { color: isDarkMode ? '#bbbbbb' : '#666666' }]}>開始日期: {task.startDate}</Text>
<Text style={[styles.info, { color: isDarkMode ? '#bbbbbb' : '#666666' }]}>結束日期: {task.endDate}</Text>
</>
)}
<View style={styles.tagsContainer}>
{editedTask.tags.map((tag, index) => (
<View key={index} style={[styles.tag, { backgroundColor: isDarkMode ? '#333333' : '#E0E0E0' }]}>
{isEditing ? (
<>
<TextInput
style={[styles.tagInput, { color: isDarkMode ? '#ffffff' : '#000000' }]}
value={tag.title}
onChangeText={(value) => updateTag(index, value)}
/>
<TouchableOpacity onPress={() => removeTag(index)}>
<Text style={[styles.removeTagText, { color: isDarkMode ? '#ff6b6b' : '#FF3B30' }]}>X</Text>
</TouchableOpacity>
</>
) : (
<Text style={[styles.tagText, { color: isDarkMode ? '#ffffff' : '#000000' }]}>{tag.title}</Text>
)}
</View>
))}
{isEditing && (
<TouchableOpacity style={[styles.addTagButton, { backgroundColor: isDarkMode ? '#4a4a4a' : '#E0E0E0' }]} onPress={addTag}>
<Text style={[styles.addTagButtonText, { color: isDarkMode ? '#ffffff' : '#007AFF' }]}>+ 添加標籤</Text>
</TouchableOpacity>
)}
</View>
<View style={styles.actionContainer}>
{isEditing ? (
<TouchableOpacity style={[styles.actionButton, { backgroundColor: isDarkMode ? '#4a4a4a' : '#007AFF' }]} onPress={handleSave}>
<Text style={styles.actionButtonText}>保存</Text>
</TouchableOpacity>
) : (
<TouchableOpacity style={[styles.actionButton, { backgroundColor: isDarkMode ? '#4a4a4a' : '#007AFF' }]} onPress={handleEdit}>
<Text style={styles.actionButtonText}>編輯</Text>
</TouchableOpacity>
)}
<TouchableOpacity style={[styles.actionButton, styles.deleteButton, { backgroundColor: isDarkMode ? '#8b0000' : '#FF3B30' }]} onPress={handleDelete}>
<Text style={styles.actionButtonText}>刪除</Text>
</TouchableOpacity>
</View>
<View style={styles.completionContainer}>
<Text style={[styles.completionText, { color: isDarkMode ? '#ffffff' : '#000000' }]}>任務完成</Text>
<Switch
value={editedTask.isDone}
onValueChange={toggleCompletion}
trackColor={{ false: isDarkMode ? '#767577' : '#E0E0E0', true: isDarkMode ? '#81b0ff' : '#007AFF' }}
thumbColor={editedTask.isDone ? (isDarkMode ? '#f5dd4b' : '#f4f3f4') : '#f4f3f4'}
/>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
backButton: {
marginBottom: 20,
},
backButtonText: {
fontSize: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
description: {
fontSize: 16,
marginBottom: 20,
},
info: {
fontSize: 14,
marginBottom: 5,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 5,
padding: 10,
marginBottom: 10,
fontSize: 16,
},
descriptionInput: {
height: 100,
textAlignVertical: 'top',
},
tagsContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 10,
marginBottom: 20,
},
tag: {
borderRadius: 20,
paddingVertical: 5,
paddingHorizontal: 10,
marginRight: 5,
marginBottom: 5,
flexDirection: 'row',
alignItems: 'center',
},
tagText: {
fontSize: 12,
},
tagInput: {
fontSize: 12,
padding: 0,
height: 20,
width: 80,
},
removeTagText: {
fontSize: 12,
marginLeft: 5,
},
addTagButton: {
borderRadius: 20,
paddingVertical: 5,
paddingHorizontal: 10,
},
addTagButtonText: {
fontSize: 12,
},
actionContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 20,
},
actionButton: {
padding: 10,
borderRadius: 5,
flex: 1,
marginRight: 10,
},
deleteButton: {
marginRight: 0,
},
actionButtonText: {
color: 'white',
textAlign: 'center',
fontWeight: 'bold',
},
completionContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 20,
},
completionText: {
fontSize: 16,
},
});
export default React.memo(TaskDetailsPage);
// src\pages\statistics-page.tsx
import React, { useRef, useEffect, useMemo } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions } from 'react-native';
import * as echarts from 'echarts/core';
import { SkiaChart, SVGRenderer } from '@wuba/react-native-echarts';
import { GaugeChart, PieChart, BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components';
import { Task } from '../types';
echarts.use([SVGRenderer, GaugeChart, PieChart, BarChart, GridComponent, TooltipComponent, LegendComponent, TitleComponent]);
type StatisticsPageProps = {
isDarkMode: boolean;
tasks: Task[];
};
const { width } = Dimensions.get('window');
const chartWidth = width - 40; // Assuming 20px padding on each side
const StatisticsPage: React.FC<StatisticsPageProps> = ({ isDarkMode, tasks }) => {
const completionRateChartRef = useRef<any>(null);
const taskDistributionChartRef = useRef<any>(null);
const taskTrendChartRef = useRef<any>(null);
const taskStats = useMemo(() => {
const totalTasks = tasks.length;
const completedTasks = tasks.filter(task => task.isDone).length;
const completionRate = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
const tagCounts = tasks.reduce((acc, task) => {
task.tags.forEach(tag => {
acc[tag.title] = (acc[tag.title] || 0) + 1;
});
return acc;
}, {} as Record<string, number>);
const tasksByMonth = tasks.reduce((acc, task) => {
const month = task?.startDate ? new Date(task.startDate).getMonth() : new Date().getMonth();
acc[month] = (acc[month] || 0) + 1;
return acc;
}, {} as Record<number, number>);
return { totalTasks, completedTasks, completionRate, tagCounts, tasksByMonth };
}, [tasks]);
useEffect(() => {
const theme = isDarkMode ? 'dark' : 'light';
const textColor = isDarkMode ? '#ffffff' : '#000000';
const completionRateOption = {
series: [
{
type: 'gauge',
startAngle: 90,
endAngle: -270,
pointer: { show: false },
progress: {
show: true,
overlap: false,
roundCap: true,
clip: false,
itemStyle: {
borderWidth: 1,
borderColor: isDarkMode ? '#ffffff' : '#464646',
},
},
axisLine: {
lineStyle: {
width: 30,
},
},
splitLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
data: [{
value: taskStats.completionRate,
name: '完成率',
title: { offsetCenter: ['0%', '-20%'] },
detail: { offsetCenter: ['0%', '0%'] },
}],
title: {
fontSize: 14,
color: textColor,
},
detail: {
width: 50,
height: 14,
fontSize: 24,
color: textColor,
borderColor: textColor,
borderRadius: 20,
borderWidth: 1,
formatter: '{value.toFixed(2)}%',
},
},
],
};
const taskDistributionOption = {
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
textStyle: {
color: textColor,
},
},
series: [
{
name: '任務分佈',
type: 'pie',
radius: '50%',
data: Object.entries(taskStats.tagCounts).map(([name, value]) => ({ name, value })),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
label: {
color: textColor,
},
},
],
};
const taskTrendOption = {
title: {
text: '月度任務趨勢',
left: 'center',
textStyle: {
color: textColor,
},
},
xAxis: {
type: 'category',
data: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
axisLabel: {
color: textColor,
},
},
yAxis: {
type: 'value',
axisLabel: {
color: textColor,
},
},
series: [{
data: Array.from({ length: 12 }, (_, i) => taskStats.tasksByMonth[i] || 0),
type: 'bar',
itemStyle: {
color: isDarkMode ? '#8e9aaf' : '#4a4e69',
},
}],
};
if (completionRateChartRef.current) {
const chart = echarts.init(completionRateChartRef.current, theme, {
renderer: 'svg',
width: chartWidth,
height: 250,
});
chart.setOption(completionRateOption);
}
if (taskDistributionChartRef.current) {
const chart = echarts.init(taskDistributionChartRef.current, theme, {
renderer: 'svg',
width: chartWidth,
height: 300,
});
chart.setOption(taskDistributionOption);
}
if (taskTrendChartRef.current) {
const chart = echarts.init(taskTrendChartRef.current, theme, {
renderer: 'svg',
width: chartWidth,
height: 250,
});
chart.setOption(taskTrendOption);
}
return () => {
completionRateChartRef.current?.dispose();
taskDistributionChartRef.current?.dispose();
taskTrendChartRef.current?.dispose();
};
}, [isDarkMode, taskStats]);
return (
<ScrollView style={[styles.container, { backgroundColor: isDarkMode ? '#1a1a1a' : '#ffffff' }]}>
<Text style={[styles.title, { color: isDarkMode ? '#ffffff' : '#000000' }]}>統計資訊</Text>
<View style={styles.statsContainer}>
<Text style={[styles.statsText, { color: isDarkMode ? '#dddddd' : '#333333' }]}>總任務數: {taskStats.totalTasks}</Text>
<Text style={[styles.statsText, { color: isDarkMode ? '#dddddd' : '#333333' }]}>已完成任務: {taskStats.completedTasks}</Text>
<Text style={[styles.statsText, { color: isDarkMode ? '#dddddd' : '#333333' }]}>完成率: {taskStats.completionRate.toFixed(2)}%</Text>
</View>
<View style={styles.chartContainer}>
<Text style={[styles.chartTitle, { color: isDarkMode ? '#ffffff' : '#000000' }]}>任務完成率</Text>
<SkiaChart ref={completionRateChartRef} />
</View>
<View style={styles.chartContainer}>
<Text style={[styles.chartTitle, { color: isDarkMode ? '#ffffff' : '#000000' }]}>任務分佈</Text>
<SkiaChart ref={taskDistributionChartRef} />
</View>
<View style={styles.chartContainer}>
<Text style={[styles.chartTitle, { color: isDarkMode ? '#ffffff' : '#000000' }]}>任務趨勢</Text>
<SkiaChart ref={taskTrendChartRef} />
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
statsContainer: {
marginBottom: 20,
backgroundColor: 'rgba(200, 200, 200, 0.1)',
padding: 15,
borderRadius: 10,
},
statsText: {
fontSize: 16,
marginBottom: 5,
},
chartContainer: {
marginBottom: 30,
alignItems: 'center',
backgroundColor: 'rgba(200, 200, 200, 0.1)',
padding: 15,
borderRadius: 10,
},
chartTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
});
export default React.memo(StatisticsPage);
現在我們把生成資料的部分移到App上,並調整Home頁面
// App.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, StatusBar, useColorScheme } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import HomePage from './src/pages/home.page';
import AddTaskPage from './src/pages/add-task.page';
import StatisticsPage from './src/pages/statistics-page';
import SettingsPage from './src/pages/settings.page';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import TaskDetailsPage from './src/pages/task-detail.page';
import { Tags, Task } from './src/types';
function MainContent() {
const [isDarkMode, setIsDarkMode] = useState(useColorScheme() === 'dark');
const [currentPage, setCurrentPage] = useState('home');
const [tasks, setTasks] = useState<Task[]>([]);
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
useEffect(() => {
// Generate random tasks when the app starts
setTasks(generateRandomTasks(10));
}, []);
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
flex: 1,
};
const toggleDarkMode = () => {
setIsDarkMode(!isDarkMode);
};
const generateRandomTasks = (count: number): Task[] => {
const generateRandomDate = (start: Date, end: Date) => {
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString().split('T')[0];
};
const possibleTags: Tags[] = [
{ title: '工作' },
{ title: '個人' },
{ title: '學習' },
{ title: '娛樂' },
];
for (let i = 0; i < count; i++) {
const startDate = generateRandomDate(new Date(), new Date(Date.now() + 30 * 24 * 60 * 60 * 1000));
const endDate = generateRandomDate(new Date(startDate), new Date(Date.now() + 60 * 24 * 60 * 60 * 1000));
tasks.push({
id: `task-${i + 1}`,
title: `任務 ${i + 1}`,
startDate,
endDate,
description: `隨機生成的第 ${i + 1} 筆內容。`,
isDone: Math.random() < 0.3,
tags: possibleTags.filter(() => Math.random() < 0.3),
subTasks: [],
});
}
return tasks;
};
const renderPage = () => {
switch (currentPage) {
case 'home':
return (
<HomePage
isDarkMode={isDarkMode}
tasks={tasks}
setTasks={setTasks}
onTaskSelect={(task) => {
setSelectedTask(task);
setCurrentPage('taskDetails');
}}
/>
);
case 'add':
return <AddTaskPage isDarkMode={isDarkMode} />;
case 'stats':
return <StatisticsPage isDarkMode={isDarkMode} tasks={tasks} />;
case 'settings':
return <SettingsPage isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} />;
case 'taskDetails':
return selectedTask ? (
<TaskDetailsPage
isDarkMode={isDarkMode}
task={selectedTask}
onBack={() => setCurrentPage('home')}
onTaskUpdate={(updatedTask) => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === updatedTask.id ? updatedTask : task
)
);
setCurrentPage('home');
}}
onTaskDelete={(taskId) => {
setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId));
setCurrentPage('home');
}}
/>
) : null;
default:
return <HomePage isDarkMode={isDarkMode} tasks={tasks} setTasks={setTasks} />;
}
};
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<View style={styles.topArea}>
<Text style={styles.topText}>我的應用</Text>
</View>
<View style={styles.container}>
{renderPage()}
</View>
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('home')}>
<Text style={styles.navText}>主頁</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('add')}>
<Text style={styles.navText}>新增</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('stats')}>
<Text style={styles.navText}>統計</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem} onPress={() => setCurrentPage('settings')}>
<Text style={styles.navText}>設置</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</GestureHandlerRootView>
);
}
function App(): React.JSX.Element {
return (
<SafeAreaProvider>
<MainContent />
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
topArea: {
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: '#ccc',
backgroundColor: '#f8f8f8',
},
topText: {
fontSize: 18,
fontWeight: 'bold',
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
height: 50,
borderTopWidth: 1,
borderTopColor: '#ccc',
backgroundColor: '#f8f8f8',
},
navItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
height: '100%',
},
navText: {
fontSize: 12,
},
});
export default App;
在調整我們的HomePage。
// src\pages\home.page.tsx
import React, { useCallback } from 'react';
import { View, Text, TextInput, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import { Task, Tags } from '../types';
import { Swipeable } from 'react-native-gesture-handler';
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
searchBar: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 10,
},
taskList: {
flex: 1,
},
taskItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
checkbox: {
width: 24,
height: 24,
borderWidth: 2,
borderColor: '#007AFF',
borderRadius: 12,
marginRight: 10,
justifyContent: 'center',
alignItems: 'center',
},
checkboxInner: {
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: '#007AFF',
},
taskContent: {
flex: 1,
},
taskTitle: {
fontSize: 16,
fontWeight: 'bold',
},
taskDates: {
fontSize: 12,
color: '#666',
},
taskDescription: {
fontSize: 14,
marginTop: 5,
},
taskTags: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 5,
},
tag: {
backgroundColor: '#E0E0E0',
borderRadius: 10,
padding: 5,
marginRight: 5,
marginBottom: 5,
},
tagText: {
fontSize: 12,
},
deleteButton: {
backgroundColor: 'red',
justifyContent: 'center',
alignItems: 'center',
width: 80,
height: '100%',
},
deleteButtonText: {
color: 'white',
fontWeight: 'bold',
},
});
type HomePageProps = {
isDarkMode: boolean;
tasks: Task[];
setTasks: React.Dispatch<React.SetStateAction<Task[]>>;
onTaskSelect?: (task: Task) => void;
};
const HomePage: React.FC<HomePageProps> = ({ isDarkMode, tasks, setTasks, onTaskSelect }) => {
const toggleTaskStatus = useCallback((id: string) => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === id ? { ...task, isDone: !task.isDone } : task
)
);
}, [setTasks]);
const deleteTask = useCallback((id: string) => {
setTasks(prevTasks => prevTasks.filter(task => task.id !== id));
}, [setTasks]);
const renderRightActions = useCallback((id: string) => {
return (
<TouchableOpacity
style={styles.deleteButton}
onPress={() => deleteTask(id)}
>
<Text style={styles.deleteButtonText}>Delete</Text>
</TouchableOpacity>
);
}, [deleteTask]);
const renderTask = useCallback(({ item }: { item: Task }) => (
<Swipeable renderRightActions={() => renderRightActions(item.id)}>
<TouchableOpacity onPress={() => onTaskSelect(item)}>
<View
style={[
styles.taskItem,
{ backgroundColor: item.isDone ? '#E8F5E9' : 'transparent' },
]}
>
<TouchableOpacity onPress={() => toggleTaskStatus(item.id)}>
<View style={styles.checkbox}>
{item.isDone && <View style={styles.checkboxInner} />}
</View>
</TouchableOpacity>
<View style={styles.taskContent}>
<Text style={styles.taskTitle}>{item.title}</Text>
<Text style={styles.taskDates}>
{item.startDate} - {item.endDate}
</Text>
<Text style={styles.taskDescription}>{item.description}</Text>
<View style={styles.taskTags}>
{item.tags.map((tag, index) => (
<View key={index} style={styles.tag}>
<Text style={styles.tagText}>{tag.title}</Text>
</View>
))}
</View>
</View>
</View>
</TouchableOpacity>
</Swipeable>
), [toggleTaskStatus, renderRightActions, onTaskSelect]);
return (
<View style={styles.container}>
<Text style={styles.title}>任務列表</Text>
<TextInput
style={styles.searchBar}
placeholder="搜尋欄"
placeholderTextColor={isDarkMode ? Colors.light : Colors.dark}
/>
<FlatList
style={styles.taskList}
data={tasks}
renderItem={renderTask}
keyExtractor={item => item.id}
/>
</View>
);
};
export default HomePage;
現在我們已經調整了目前的統計和任務詳細頁面。明天接著完成其他部分。