gitshark

Clone repository

git clone https://git.vilip.de/git/vilip/taskflow.git
git clone git@git.vilip.de:vilip/taskflow.git

← Commits

๐Ÿ’„ Improve ui and usability

c6e0c8099804a1486a8aec4c3b4789a3d9bab962 ยท vvilip ยท 2025-11-04T16:42:39Z

Changes

4 files changed, +153 -59

MODIFY app/(tabs)/index.tsx +79 -3
diff --git "a/app/\050tabs\051/index.tsx" "b/app/\050tabs\051/index.tsx"
index 04884fd..7fa30c7 100644
--- "a/app/\050tabs\051/index.tsx"
+++ "b/app/\050tabs\051/index.tsx"
@@ -1,5 +1,5 @@
1 1 import React, { useEffect, useState } from 'react';
2 -import { StyleSheet, SafeAreaView, Alert } from 'react-native';
2 +import { StyleSheet, SafeAreaView, Alert, TouchableOpacity, ScrollView } from 'react-native';
3 3 import { router, useFocusEffect } from 'expo-router';
4 4 import { ThemedView } from '@/components/themed-view';
5 5 import { ThemedText } from '@/components/themed-text';
@@ -7,21 +7,27 @@
7 7 import { FabButton } from '@/components/fab-button';
8 8 import { Task } from '@/types/gtd';
9 9 import { taskService } from '@/services';
10 +import { Colors } from '@/constants/theme';
11 +import { useColorScheme } from '@/hooks/use-color-scheme';
10 12
11 13 export default function InboxScreen() {
12 14 const [tasks, setTasks] = useState<Task[]>([]);
13 15 const [loading, setLoading] = useState(true);
16 + const [statusFilter, setStatusFilter] = useState<'all' | 'waiting' | 'someday'>('all');
14 17
15 18 useFocusEffect(
16 19 React.useCallback(() => {
17 20 loadTasks();
18 - }, [])
21 + }, [statusFilter])
19 22 );
20 23
21 24 const loadTasks = async () => {
22 25 try {
23 26 const inboxTasks = await taskService.getInboxTasks();
24 - setTasks(inboxTasks);
27 + const filtered = statusFilter === 'all'
28 + ? inboxTasks
29 + : inboxTasks.filter(t => t.status === statusFilter);
30 + setTasks(filtered);
25 31 } catch (error) {
26 32 Alert.alert('Error', 'Failed to load tasks');
27 33 } finally {
@@ -54,12 +60,59 @@
54 60 router.push('/task/new');
55 61 };
56 62
63 + const colorScheme = useColorScheme();
64 + const colors = Colors[colorScheme];
65 +
57 66 return (
58 67 <SafeAreaView style={styles.container}>
59 68 <ThemedView style={styles.header}>
60 69 <ThemedText type="title">Inbox</ThemedText>
61 70 <ThemedText style={styles.count}>{tasks.length} tasks</ThemedText>
62 71 </ThemedView>
72 +
73 + <ThemedView style={styles.filterContainer}>
74 + <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.filterScroll}>
75 + <TouchableOpacity
76 + style={[
77 + styles.filterButton,
78 + { borderColor: colors.border },
79 + statusFilter === 'all' && [styles.filterButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
80 + ]}
81 + onPress={() => setStatusFilter('all')}
82 + >
83 + <ThemedText style={[
84 + styles.filterText,
85 + statusFilter === 'all' && styles.filterTextActive,
86 + ]}>All</ThemedText>
87 + </TouchableOpacity>
88 + <TouchableOpacity
89 + style={[
90 + styles.filterButton,
91 + { borderColor: colors.border },
92 + statusFilter === 'waiting' && [styles.filterButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
93 + ]}
94 + onPress={() => setStatusFilter('waiting')}
95 + >
96 + <ThemedText style={[
97 + styles.filterText,
98 + statusFilter === 'waiting' && styles.filterTextActive,
99 + ]}>โณ Waiting</ThemedText>
100 + </TouchableOpacity>
101 + <TouchableOpacity
102 + style={[
103 + styles.filterButton,
104 + { borderColor: colors.border },
105 + statusFilter === 'someday' && [styles.filterButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
106 + ]}
107 + onPress={() => setStatusFilter('someday')}
108 + >
109 + <ThemedText style={[
110 + styles.filterText,
111 + statusFilter === 'someday' && styles.filterTextActive,
112 + ]}>๐Ÿ’ญ Someday</ThemedText>
113 + </TouchableOpacity>
114 + </ScrollView>
115 + </ThemedView>
63 116
64 117 <TaskList
65 118 tasks={tasks}
@@ -87,4 +140,27 @@
87 140 opacity: 0.6,
88 141 marginTop: 4,
89 142 },
143 + filterContainer: {
144 + paddingVertical: 8,
145 + },
146 + filterScroll: {
147 + paddingHorizontal: 16,
148 + gap: 8,
149 + },
150 + filterButton: {
151 + paddingVertical: 6,
152 + paddingHorizontal: 16,
153 + borderRadius: 20,
154 + borderWidth: 1,
155 + marginRight: 8,
156 + },
157 + filterButtonActive: {
158 + },
159 + filterText: {
160 + fontSize: 14,
161 + },
162 + filterTextActive: {
163 + color: '#fff',
164 + fontWeight: '600',
165 + },
90 166 });
MODIFY app/task/[id].tsx +70 -44
diff --git "a/app/task/\133id\135.tsx" "b/app/task/\133id\135.tsx"
index 56c6425..a9d7040 100644
--- "a/app/task/\133id\135.tsx"
+++ "b/app/task/\133id\135.tsx"
@@ -21,7 +21,7 @@
21 21 const [task, setTask] = useState<Partial<Task>>({
22 22 title: '',
23 23 description: '',
24 - status: 'inbox',
24 + status: undefined,
25 25 priority: undefined,
26 26 dueDate: dueDateParam ? parseInt(dueDateParam) : undefined,
27 27 projectId: projectIdParam,
@@ -117,15 +117,36 @@
117 117 });
118 118 };
119 119
120 - const setDueDate = (days: number | null) => {
121 - if (days === null) {
122 - setTask({ ...task, dueDate: undefined });
123 - } else {
124 - const date = new Date();
125 - date.setDate(date.getDate() + days);
126 - date.setHours(23, 59, 59, 999);
127 - setTask({ ...task, dueDate: date.getTime() });
120 + const isDueDateToday = (timestamp: number, daysOffset: number) => {
121 + const taskDate = new Date(timestamp);
122 + taskDate.setHours(0, 0, 0, 0);
123 + const compareDate = new Date();
124 + compareDate.setDate(compareDate.getDate() + daysOffset);
125 + compareDate.setHours(0, 0, 0, 0);
126 + return taskDate.getTime() === compareDate.getTime();
127 + };
128 +
129 + const setDueDate = (days: number) => {
130 + const date = new Date();
131 + date.setDate(date.getDate() + days);
132 + date.setHours(23, 59, 59, 999);
133 + const newDate = date.getTime();
134 +
135 + // Toggle: if the same date is already set, remove it
136 + if (task.dueDate) {
137 + const existingDate = new Date(task.dueDate);
138 + existingDate.setHours(0, 0, 0, 0);
139 + const compareDate = new Date();
140 + compareDate.setDate(compareDate.getDate() + days);
141 + compareDate.setHours(0, 0, 0, 0);
142 +
143 + if (existingDate.getTime() === compareDate.getTime()) {
144 + setTask({ ...task, dueDate: undefined });
145 + return;
146 + }
128 147 }
148 +
149 + setTask({ ...task, dueDate: newDate });
129 150 };
130 151
131 152 return (
@@ -186,7 +207,7 @@
186 207 { borderColor: colors.border },
187 208 task.priority === priority && [styles.priorityButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
188 209 ]}
189 - onPress={() => setTask({ ...task, priority })}
210 + onPress={() => setTask({ ...task, priority: task.priority === priority ? undefined : priority })}
190 211 >
191 212 <ThemedText
192 213 style={[
@@ -194,16 +215,10 @@
194 215 task.priority === priority && styles.priorityTextActive,
195 216 ]}
196 217 >
197 - {priority.charAt(0).toUpperCase() + priority.slice(1)}
218 + {priority === 'high' ? '๐Ÿ”ด High' : priority === 'medium' ? '๐ŸŸก Medium' : '๐ŸŸข Low'}
198 219 </ThemedText>
199 220 </TouchableOpacity>
200 221 ))}
201 - <TouchableOpacity
202 - style={[styles.priorityButton, { borderColor: colors.border }]}
203 - onPress={() => setTask({ ...task, priority: undefined })}
204 - >
205 - <ThemedText style={styles.priorityText}>None</ThemedText>
206 - </TouchableOpacity>
207 222 </ThemedView>
208 223 </ThemedView>
209 224
@@ -211,33 +226,48 @@
211 226 <ThemedText style={styles.label}>Due Date</ThemedText>
212 227 <ThemedView style={styles.dateContainer}>
213 228 <TouchableOpacity
214 - style={[styles.dateButton, { borderColor: colors.border }]}
229 + style={[
230 + styles.dateButton,
231 + { borderColor: colors.border },
232 + task.dueDate && isDueDateToday(task.dueDate, 0) && [styles.dateButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }]
233 + ]}
215 234 onPress={() => setDueDate(0)}
216 235 >
217 - <ThemedText style={styles.dateButtonText}>Today</ThemedText>
236 + <ThemedText style={[
237 + styles.dateButtonText,
238 + task.dueDate && isDueDateToday(task.dueDate, 0) && styles.dateButtonTextActive
239 + ]}>Today</ThemedText>
218 240 </TouchableOpacity>
219 241 <TouchableOpacity
220 - style={[styles.dateButton, { borderColor: colors.border }]}
242 + style={[
243 + styles.dateButton,
244 + { borderColor: colors.border },
245 + task.dueDate && isDueDateToday(task.dueDate, 1) && [styles.dateButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }]
246 + ]}
221 247 onPress={() => setDueDate(1)}
222 248 >
223 - <ThemedText style={styles.dateButtonText}>Tomorrow</ThemedText>
249 + <ThemedText style={[
250 + styles.dateButtonText,
251 + task.dueDate && isDueDateToday(task.dueDate, 1) && styles.dateButtonTextActive
252 + ]}>Tomorrow</ThemedText>
224 253 </TouchableOpacity>
225 254 <TouchableOpacity
226 - style={[styles.dateButton, { borderColor: colors.border }]}
255 + style={[
256 + styles.dateButton,
257 + { borderColor: colors.border },
258 + task.dueDate && isDueDateToday(task.dueDate, 7) && [styles.dateButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }]
259 + ]}
227 260 onPress={() => setDueDate(7)}
228 261 >
229 - <ThemedText style={styles.dateButtonText}>Next Week</ThemedText>
230 - </TouchableOpacity>
231 - <TouchableOpacity
232 - style={[styles.dateButton, { borderColor: colors.border }]}
233 - onPress={() => setDueDate(null)}
234 - >
235 - <ThemedText style={styles.dateButtonText}>Clear</ThemedText>
262 + <ThemedText style={[
263 + styles.dateButtonText,
264 + task.dueDate && isDueDateToday(task.dueDate, 7) && styles.dateButtonTextActive
265 + ]}>Next Week</ThemedText>
236 266 </TouchableOpacity>
237 267 </ThemedView>
238 268 {task.dueDate && (
239 269 <ThemedText style={styles.selectedDate}>
240 - {formatDate(task.dueDate)}
270 + ๐Ÿ“… {formatDate(task.dueDate)}
241 271 </ThemedText>
242 272 )}
243 273 </ThemedView>
@@ -245,7 +275,7 @@
245 275 <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
246 276 <ThemedText style={styles.label}>Status</ThemedText>
247 277 <ThemedView style={styles.statusContainer}>
248 - {(['inbox', 'next', 'waiting', 'someday'] as TaskStatus[]).map((status) => (
278 + {(['waiting', 'someday'] as TaskStatus[]).map((status) => (
249 279 <TouchableOpacity
250 280 key={status}
251 281 style={[
@@ -253,7 +283,7 @@
253 283 { borderColor: colors.border },
254 284 task.status === status && [styles.statusButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
255 285 ]}
256 - onPress={() => setTask({ ...task, status })}
286 + onPress={() => setTask({ ...task, status: task.status === status ? undefined : status })}
257 287 >
258 288 <ThemedText
259 289 style={[
@@ -261,7 +291,7 @@
261 291 task.status === status && styles.statusTextActive,
262 292 ]}
263 293 >
264 - {status.charAt(0).toUpperCase() + status.slice(1)}
294 + {status === 'waiting' ? 'โณ Waiting' : '๐Ÿ’ญ Someday'}
265 295 </ThemedText>
266 296 </TouchableOpacity>
267 297 ))}
@@ -271,16 +301,6 @@
271 301 <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
272 302 <ThemedText style={styles.label}>Project</ThemedText>
273 303 <ThemedView style={styles.projectContainer}>
274 - <TouchableOpacity
275 - style={[
276 - styles.projectButton,
277 - { borderColor: colors.border },
278 - !task.projectId && [styles.projectButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
279 - ]}
280 - onPress={() => setTask({ ...task, projectId: undefined })}
281 - >
282 - <ThemedText style={[styles.projectText, !task.projectId && styles.projectTextActive]}>None</ThemedText>
283 - </TouchableOpacity>
284 304 {projects.map((project) => (
285 305 <TouchableOpacity
286 306 key={project.id}
@@ -289,7 +309,7 @@
289 309 { borderColor: colors.border },
290 310 task.projectId === project.id && [styles.projectButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
291 311 ]}
292 - onPress={() => setTask({ ...task, projectId: project.id })}
312 + onPress={() => setTask({ ...task, projectId: task.projectId === project.id ? undefined : project.id })}
293 313 >
294 314 <ThemedText style={[styles.projectText, task.projectId === project.id && styles.projectTextActive]}>{project.name}</ThemedText>
295 315 </TouchableOpacity>
@@ -386,9 +406,15 @@
386 406 borderRadius: 8,
387 407 borderWidth: 1,
388 408 },
409 + dateButtonActive: {
410 + },
389 411 dateButtonText: {
390 412 fontSize: 14,
391 413 },
414 + dateButtonTextActive: {
415 + color: '#fff',
416 + fontWeight: '600',
417 + },
392 418 selectedDate: {
393 419 marginTop: 8,
394 420 fontSize: 14,
MODIFY services/task.service.ts +2 -10
diff --git a/services/task.service.ts b/services/task.service.ts
index 84d2f17..0c45b87 100644
--- a/services/task.service.ts
+++ b/services/task.service.ts
@@ -25,12 +25,11 @@
25 25 }
26 26
27 27 /**
28 - * Get inbox tasks (only tasks without due date and in inbox status)
28 + * Get inbox tasks (tasks without due date - unscheduled)
29 29 */
30 30 async getInboxTasks(): Promise<Task[]> {
31 31 const tasks = await this.getAllTasks();
32 32 return tasks.filter(task =>
33 - task.status === 'inbox' &&
34 33 !task.completed &&
35 34 !task.dueDate
36 35 );
@@ -125,7 +124,7 @@
125 124 id: generateId(),
126 125 title: taskData.title || '',
127 126 description: taskData.description,
128 - status: taskData.status || 'inbox',
127 + status: taskData.status,
129 128 priority: taskData.priority,
130 129 dueDate: taskData.dueDate,
131 130 projectId: taskData.projectId,
@@ -160,13 +159,6 @@
160 159 updatedAt: Date.now(),
161 160 };
162 161
163 - // Auto-move out of inbox only when task gets a due date or status change
164 - if (currentTask.status === 'inbox') {
165 - if (updates.dueDate || (updates.status && updates.status !== 'inbox')) {
166 - updatedTask.status = updates.status || 'next';
167 - }
168 - }
169 -
170 162 data.tasks[taskIndex] = updatedTask;
171 163 await storageService.saveData(data);
172 164
MODIFY types/gtd.ts +2 -2
diff --git a/types/gtd.ts b/types/gtd.ts
index 8511af6..9d88a78 100644
--- a/types/gtd.ts
+++ b/types/gtd.ts
@@ -5,7 +5,7 @@
5 5
6 6 export type Priority = 'low' | 'medium' | 'high';
7 7
8 -export type TaskStatus = 'inbox' | 'next' | 'waiting' | 'someday' | 'done';
8 +export type TaskStatus = 'waiting' | 'someday';
9 9
10 10 export interface Tag {
11 11 id: string;
@@ -29,7 +29,7 @@
29 29 id: string;
30 30 title: string;
31 31 description?: string;
32 - status: TaskStatus;
32 + status?: TaskStatus;
33 33 priority?: Priority;
34 34 dueDate?: number; // timestamp
35 35 projectId?: string;

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog