๐ Improve ui and usability
Changes
4 files changed, +153 -59
MODIFY
app/(tabs)/index.tsx
+79 -3
@@ -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
@@ -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
@@ -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
@@ -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;