gitshark

Clone repository

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

← Commits

๐Ÿ’„ Add a lightmode

90d6cb08c4e38ed1cb2a40705afb22eaf4d870ff ยท vvilip ยท 2025-11-04T16:15:15Z

Changes

8 files changed, +248 -68

MODIFY app/(tabs)/settings.tsx +85 -11
diff --git "a/app/\050tabs\051/settings.tsx" "b/app/\050tabs\051/settings.tsx"
index e96e549..d60dd9d 100644
--- "a/app/\050tabs\051/settings.tsx"
+++ "b/app/\050tabs\051/settings.tsx"
@@ -3,10 +3,16 @@
3 3 import { ThemedView } from '@/components/themed-view';
4 4 import { ThemedText } from '@/components/themed-text';
5 5 import { storageService, taskService } from '@/services';
6 +import { useTheme } from '@/contexts/theme-context';
7 +import { Colors } from '@/constants/theme';
8 +import { useColorScheme } from '@/hooks/use-color-scheme';
6 9
7 10 export default function SettingsScreen() {
8 11 const [syncing, setSyncing] = useState(false);
9 12 const [autoArchive, setAutoArchive] = useState(false);
13 + const { themeMode, setThemeMode } = useTheme();
14 + const colorScheme = useColorScheme();
15 + const colors = Colors[colorScheme];
10 16
11 17 const handleExport = async () => {
12 18 try {
@@ -86,16 +92,65 @@
86 92
87 93 <ScrollView style={styles.scrollView}>
88 94 <ThemedView style={styles.section}>
95 + <ThemedText type="subtitle" style={styles.sectionTitle}>Appearance</ThemedText>
96 +
97 + <ThemedView style={[styles.option, { borderBottomColor: colors.border }]}>
98 + <ThemedText style={styles.optionText}>Theme</ThemedText>
99 + <ThemedView style={styles.themeOptions}>
100 + <TouchableOpacity
101 + style={[
102 + styles.themeButton,
103 + { borderColor: colors.border },
104 + themeMode === 'light' && [styles.themeButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
105 + ]}
106 + onPress={() => setThemeMode('light')}
107 + >
108 + <ThemedText style={[
109 + styles.themeButtonText,
110 + themeMode === 'light' && styles.themeButtonTextActive,
111 + ]}>โ˜€๏ธ Light</ThemedText>
112 + </TouchableOpacity>
113 + <TouchableOpacity
114 + style={[
115 + styles.themeButton,
116 + { borderColor: colors.border },
117 + themeMode === 'dark' && [styles.themeButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
118 + ]}
119 + onPress={() => setThemeMode('dark')}
120 + >
121 + <ThemedText style={[
122 + styles.themeButtonText,
123 + themeMode === 'dark' && styles.themeButtonTextActive,
124 + ]}>๐ŸŒ™ Dark</ThemedText>
125 + </TouchableOpacity>
126 + <TouchableOpacity
127 + style={[
128 + styles.themeButton,
129 + { borderColor: colors.border },
130 + themeMode === 'system' && [styles.themeButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
131 + ]}
132 + onPress={() => setThemeMode('system')}
133 + >
134 + <ThemedText style={[
135 + styles.themeButtonText,
136 + themeMode === 'system' && styles.themeButtonTextActive,
137 + ]}>โš™๏ธ System</ThemedText>
138 + </TouchableOpacity>
139 + </ThemedView>
140 + </ThemedView>
141 + </ThemedView>
142 +
143 + <ThemedView style={styles.section}>
89 144 <ThemedText type="subtitle" style={styles.sectionTitle}>Data</ThemedText>
90 145
91 - <TouchableOpacity style={styles.option} onPress={handleExport}>
146 + <TouchableOpacity style={[styles.option, { borderBottomColor: colors.border }]} onPress={handleExport}>
92 147 <ThemedText style={styles.optionText}>Export Data</ThemedText>
93 148 <ThemedText style={styles.optionDescription}>
94 149 Export all data as JSON file
95 150 </ThemedText>
96 151 </TouchableOpacity>
97 152
98 - <TouchableOpacity style={styles.option} onPress={handleClearCompleted}>
153 + <TouchableOpacity style={[styles.option, { borderBottomColor: colors.border }]} onPress={handleClearCompleted}>
99 154 <ThemedText style={styles.optionText}>Archive Completed Tasks</ThemedText>
100 155 <ThemedText style={styles.optionDescription}>
101 156 Remove old completed tasks (30+ days)
@@ -107,7 +162,7 @@
107 162 <ThemedText type="subtitle" style={styles.sectionTitle}>Sync</ThemedText>
108 163
109 164 <TouchableOpacity
110 - style={styles.option}
165 + style={[styles.option, { borderBottomColor: colors.border }]}
111 166 onPress={handleSync}
112 167 disabled={syncing}
113 168 >
@@ -119,7 +174,7 @@
119 174 </ThemedText>
120 175 </TouchableOpacity>
121 176
122 - <ThemedView style={styles.option}>
177 + <ThemedView style={[styles.option, { borderBottomColor: colors.border }]}>
123 178 <ThemedView style={styles.optionWithSwitch}>
124 179 <ThemedView style={styles.optionTextContainer}>
125 180 <ThemedText style={styles.optionText}>Auto Archive</ThemedText>
@@ -138,20 +193,20 @@
138 193 <ThemedView style={styles.section}>
139 194 <ThemedText type="subtitle" style={styles.sectionTitle}>About</ThemedText>
140 195
141 - <ThemedView style={styles.option}>
196 + <ThemedView style={[styles.option, { borderBottomColor: colors.border }]}>
142 197 <ThemedText style={styles.optionText}>Version</ThemedText>
143 198 <ThemedText style={styles.optionDescription}>1.0.0</ThemedText>
144 199 </ThemedView>
145 200
146 - <ThemedView style={styles.option}>
201 + <ThemedView style={[styles.option, { borderBottomColor: colors.border }]}>
147 202 <ThemedText style={styles.optionText}>Storage Format</ThemedText>
148 203 <ThemedText style={styles.optionDescription}>JSON (Human-readable)</ThemedText>
149 204 </ThemedView>
150 205 </ThemedView>
151 206
152 207 <ThemedView style={styles.section}>
153 - <TouchableOpacity style={styles.dangerOption} onPress={handleClearAll}>
154 - <ThemedText style={styles.dangerText}>Clear All Data</ThemedText>
208 + <TouchableOpacity style={[styles.dangerOption, { backgroundColor: colors.dangerBackground }]} onPress={handleClearAll}>
209 + <ThemedText style={[styles.dangerText, { color: colors.danger }]}>Clear All Data</ThemedText>
155 210 </TouchableOpacity>
156 211 </ThemedView>
157 212 </ScrollView>
@@ -181,7 +236,28 @@
181 236 option: {
182 237 paddingVertical: 12,
183 238 borderBottomWidth: StyleSheet.hairlineWidth,
184 - borderBottomColor: '#e2e8f0',
239 + },
240 + themeOptions: {
241 + flexDirection: 'row',
242 + gap: 8,
243 + marginTop: 8,
244 + },
245 + themeButton: {
246 + flex: 1,
247 + paddingVertical: 8,
248 + paddingHorizontal: 12,
249 + borderRadius: 8,
250 + borderWidth: 1,
251 + alignItems: 'center',
252 + },
253 + themeButtonActive: {
254 + },
255 + themeButtonText: {
256 + fontSize: 14,
257 + },
258 + themeButtonTextActive: {
259 + color: '#fff',
260 + fontWeight: '600',
185 261 },
186 262 optionWithSwitch: {
187 263 flexDirection: 'row',
@@ -203,10 +279,8 @@
203 279 paddingVertical: 16,
204 280 alignItems: 'center',
205 281 borderRadius: 8,
206 - backgroundColor: '#fef2f2',
207 282 },
208 283 dangerText: {
209 - color: '#ef4444',
210 284 fontSize: 16,
211 285 fontWeight: '600',
212 286 },
MODIFY app/_layout.tsx +12 -3
diff --git a/app/_layout.tsx b/app/_layout.tsx
index f518c9b..5df177b 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,24 +1,33 @@
1 -import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
1 +import { DarkTheme, DefaultTheme, ThemeProvider as NavigationThemeProvider } from '@react-navigation/native';
2 2 import { Stack } from 'expo-router';
3 3 import { StatusBar } from 'expo-status-bar';
4 4 import 'react-native-reanimated';
5 5
6 6 import { useColorScheme } from '@/hooks/use-color-scheme';
7 +import { ThemeProvider } from '@/contexts/theme-context';
7 8
8 9 export const unstable_settings = {
9 10 anchor: '(tabs)',
10 11 };
11 12
12 -export default function RootLayout() {
13 +function RootLayoutNav() {
13 14 const colorScheme = useColorScheme();
14 15
15 16 return (
16 - <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
17 + <NavigationThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
17 18 <Stack>
18 19 <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
19 20 <Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
20 21 </Stack>
21 22 <StatusBar style="auto" />
23 + </NavigationThemeProvider>
24 + );
25 +}
26 +
27 +export default function RootLayout() {
28 + return (
29 + <ThemeProvider>
30 + <RootLayoutNav />
22 31 </ThemeProvider>
23 32 );
24 33 }
MODIFY app/task/[id].tsx +48 -44
diff --git "a/app/task/\133id\135.tsx" "b/app/task/\133id\135.tsx"
index 718f9fb..9f3d7f4 100644
--- "a/app/task/\133id\135.tsx"
+++ "b/app/task/\133id\135.tsx"
@@ -5,10 +5,14 @@
5 5 import { ThemedText } from '@/components/themed-text';
6 6 import { Task, Priority, TaskStatus, Project, Tag } from '@/types/gtd';
7 7 import { taskService, projectService, tagService } from '@/services';
8 +import { Colors } from '@/constants/theme';
9 +import { useColorScheme } from '@/hooks/use-color-scheme';
8 10
9 11 export default function TaskDetailScreen() {
10 12 const { id } = useLocalSearchParams<{ id: string }>();
11 13 const isNew = id === 'new';
14 + const colorScheme = useColorScheme() ?? 'light';
15 + const colors = Colors[colorScheme];
12 16
13 17 const [task, setTask] = useState<Partial<Task>>({
14 18 title: '',
@@ -122,44 +126,52 @@
122 126
123 127 return (
124 128 <SafeAreaView style={styles.container}>
125 - <ThemedView style={styles.header}>
129 + <ThemedView style={[styles.header, { borderBottomColor: colors.border }]}>
126 130 <TouchableOpacity onPress={() => router.back()}>
127 - <ThemedText style={styles.cancelButton}>Cancel</ThemedText>
131 + <ThemedText style={[styles.cancelButton, { color: colors.subtitle }]}>Cancel</ThemedText>
128 132 </TouchableOpacity>
129 133 <ThemedText type="defaultSemiBold">{isNew ? 'New Task' : 'Edit Task'}</ThemedText>
130 134 <TouchableOpacity onPress={handleSave}>
131 - <ThemedText style={styles.saveButton}>Save</ThemedText>
135 + <ThemedText style={[styles.saveButton, { color: colors.tint }]}>Save</ThemedText>
132 136 </TouchableOpacity>
133 137 </ThemedView>
134 138
135 139 <ScrollView style={styles.scrollView}>
136 - <ThemedView style={styles.section}>
140 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
137 141 <ThemedText style={styles.label}>Title *</ThemedText>
138 142 <TextInput
139 - style={styles.input}
143 + style={[styles.input, {
144 + backgroundColor: colors.inputBackground,
145 + borderColor: colors.inputBorder,
146 + color: colors.text
147 + }]}
140 148 value={task.title}
141 149 onChangeText={(text) => setTask({ ...task, title: text })}
142 150 placeholder="Enter task title"
143 - placeholderTextColor="#94a3b8"
151 + placeholderTextColor={colors.placeholder}
144 152 autoFocus={isNew}
145 153 />
146 154 </ThemedView>
147 155
148 - <ThemedView style={styles.section}>
156 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
149 157 <ThemedText style={styles.label}>Description</ThemedText>
150 158 <TextInput
151 - style={[styles.input, styles.textArea]}
159 + style={[styles.input, styles.textArea, {
160 + backgroundColor: colors.inputBackground,
161 + borderColor: colors.inputBorder,
162 + color: colors.text
163 + }]}
152 164 value={task.description}
153 165 onChangeText={(text) => setTask({ ...task, description: text })}
154 166 placeholder="Add description"
155 - placeholderTextColor="#94a3b8"
167 + placeholderTextColor={colors.placeholder}
156 168 multiline
157 169 numberOfLines={4}
158 170 textAlignVertical="top"
159 171 />
160 172 </ThemedView>
161 173
162 - <ThemedView style={styles.section}>
174 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
163 175 <ThemedText style={styles.label}>Priority</ThemedText>
164 176 <ThemedView style={styles.priorityContainer}>
165 177 {(['high', 'medium', 'low'] as Priority[]).map((priority) => (
@@ -167,7 +179,8 @@
167 179 key={priority}
168 180 style={[
169 181 styles.priorityButton,
170 - task.priority === priority && styles.priorityButtonActive,
182 + { borderColor: colors.border },
183 + task.priority === priority && [styles.priorityButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
171 184 ]}
172 185 onPress={() => setTask({ ...task, priority })}
173 186 >
@@ -182,7 +195,7 @@
182 195 </TouchableOpacity>
183 196 ))}
184 197 <TouchableOpacity
185 - style={styles.priorityButton}
198 + style={[styles.priorityButton, { borderColor: colors.border }]}
186 199 onPress={() => setTask({ ...task, priority: undefined })}
187 200 >
188 201 <ThemedText style={styles.priorityText}>None</ThemedText>
@@ -190,29 +203,29 @@
190 203 </ThemedView>
191 204 </ThemedView>
192 205
193 - <ThemedView style={styles.section}>
206 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
194 207 <ThemedText style={styles.label}>Due Date</ThemedText>
195 208 <ThemedView style={styles.dateContainer}>
196 209 <TouchableOpacity
197 - style={styles.dateButton}
210 + style={[styles.dateButton, { borderColor: colors.border }]}
198 211 onPress={() => setDueDate(0)}
199 212 >
200 213 <ThemedText style={styles.dateButtonText}>Today</ThemedText>
201 214 </TouchableOpacity>
202 215 <TouchableOpacity
203 - style={styles.dateButton}
216 + style={[styles.dateButton, { borderColor: colors.border }]}
204 217 onPress={() => setDueDate(1)}
205 218 >
206 219 <ThemedText style={styles.dateButtonText}>Tomorrow</ThemedText>
207 220 </TouchableOpacity>
208 221 <TouchableOpacity
209 - style={styles.dateButton}
222 + style={[styles.dateButton, { borderColor: colors.border }]}
210 223 onPress={() => setDueDate(7)}
211 224 >
212 225 <ThemedText style={styles.dateButtonText}>Next Week</ThemedText>
213 226 </TouchableOpacity>
214 227 <TouchableOpacity
215 - style={styles.dateButton}
228 + style={[styles.dateButton, { borderColor: colors.border }]}
216 229 onPress={() => setDueDate(null)}
217 230 >
218 231 <ThemedText style={styles.dateButtonText}>Clear</ThemedText>
@@ -225,7 +238,7 @@
225 238 )}
226 239 </ThemedView>
227 240
228 - <ThemedView style={styles.section}>
241 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
229 242 <ThemedText style={styles.label}>Status</ThemedText>
230 243 <ThemedView style={styles.statusContainer}>
231 244 {(['inbox', 'next', 'waiting', 'someday'] as TaskStatus[]).map((status) => (
@@ -233,7 +246,8 @@
233 246 key={status}
234 247 style={[
235 248 styles.statusButton,
236 - task.status === status && styles.statusButtonActive,
249 + { borderColor: colors.border },
250 + task.status === status && [styles.statusButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
237 251 ]}
238 252 onPress={() => setTask({ ...task, status })}
239 253 >
@@ -250,37 +264,39 @@
250 264 </ThemedView>
251 265 </ThemedView>
252 266
253 - <ThemedView style={styles.section}>
267 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
254 268 <ThemedText style={styles.label}>Project</ThemedText>
255 269 <ThemedView style={styles.projectContainer}>
256 270 <TouchableOpacity
257 271 style={[
258 272 styles.projectButton,
259 - !task.projectId && styles.projectButtonActive,
273 + { borderColor: colors.border },
274 + !task.projectId && [styles.projectButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
260 275 ]}
261 276 onPress={() => setTask({ ...task, projectId: undefined })}
262 277 >
263 - <ThemedText style={styles.projectText}>None</ThemedText>
278 + <ThemedText style={[styles.projectText, !task.projectId && styles.projectTextActive]}>None</ThemedText>
264 279 </TouchableOpacity>
265 280 {projects.map((project) => (
266 281 <TouchableOpacity
267 282 key={project.id}
268 283 style={[
269 284 styles.projectButton,
270 - task.projectId === project.id && styles.projectButtonActive,
285 + { borderColor: colors.border },
286 + task.projectId === project.id && [styles.projectButtonActive, { backgroundColor: colors.tint, borderColor: colors.tint }],
271 287 ]}
272 288 onPress={() => setTask({ ...task, projectId: project.id })}
273 289 >
274 - <ThemedText style={styles.projectText}>{project.name}</ThemedText>
290 + <ThemedText style={[styles.projectText, task.projectId === project.id && styles.projectTextActive]}>{project.name}</ThemedText>
275 291 </TouchableOpacity>
276 292 ))}
277 293 </ThemedView>
278 294 </ThemedView>
279 295
280 296 {!isNew && (
281 - <ThemedView style={styles.section}>
282 - <TouchableOpacity style={styles.deleteButton} onPress={handleDelete}>
283 - <ThemedText style={styles.deleteButtonText}>Delete Task</ThemedText>
297 + <ThemedView style={[styles.section, { borderBottomColor: colors.border }]}>
298 + <TouchableOpacity style={[styles.deleteButton, { backgroundColor: colors.dangerBackground }]} onPress={handleDelete}>
299 + <ThemedText style={[styles.deleteButtonText, { color: colors.danger }]}>Delete Task</ThemedText>
284 300 </TouchableOpacity>
285 301 </ThemedView>
286 302 )}
@@ -302,13 +318,11 @@
302 318 paddingHorizontal: 16,
303 319 paddingVertical: 12,
304 320 borderBottomWidth: StyleSheet.hairlineWidth,
305 - borderBottomColor: '#e2e8f0',
306 321 },
307 322 cancelButton: {
308 - color: '#64748b',
323 + fontWeight: '400',
309 324 },
310 325 saveButton: {
311 - color: '#0a7ea4',
312 326 fontWeight: '600',
313 327 },
314 328 scrollView: {
@@ -318,7 +332,6 @@
318 332 paddingHorizontal: 16,
319 333 paddingVertical: 16,
320 334 borderBottomWidth: StyleSheet.hairlineWidth,
321 - borderBottomColor: '#e2e8f0',
322 335 },
323 336 label: {
324 337 fontSize: 14,
@@ -331,7 +344,6 @@
331 344 paddingVertical: 8,
332 345 paddingHorizontal: 12,
333 346 borderWidth: 1,
334 - borderColor: '#e2e8f0',
335 347 borderRadius: 8,
336 348 minHeight: 44,
337 349 },
@@ -349,11 +361,8 @@
349 361 paddingHorizontal: 16,
350 362 borderRadius: 8,
351 363 borderWidth: 1,
352 - borderColor: '#e2e8f0',
353 364 },
354 365 priorityButtonActive: {
355 - backgroundColor: '#0a7ea4',
356 - borderColor: '#0a7ea4',
357 366 },
358 367 priorityText: {
359 368 fontSize: 14,
@@ -372,7 +381,6 @@
372 381 paddingHorizontal: 16,
373 382 borderRadius: 8,
374 383 borderWidth: 1,
375 - borderColor: '#e2e8f0',
376 384 },
377 385 dateButtonText: {
378 386 fontSize: 14,
@@ -392,11 +400,8 @@
392 400 paddingHorizontal: 16,
393 401 borderRadius: 8,
394 402 borderWidth: 1,
395 - borderColor: '#e2e8f0',
396 403 },
397 404 statusButtonActive: {
398 - backgroundColor: '#0a7ea4',
399 - borderColor: '#0a7ea4',
400 405 },
401 406 statusText: {
402 407 fontSize: 14,
@@ -415,23 +420,22 @@
415 420 paddingHorizontal: 16,
416 421 borderRadius: 8,
417 422 borderWidth: 1,
418 - borderColor: '#e2e8f0',
419 423 },
420 424 projectButtonActive: {
421 - backgroundColor: '#0a7ea4',
422 - borderColor: '#0a7ea4',
423 425 },
424 426 projectText: {
425 427 fontSize: 14,
426 428 },
429 + projectTextActive: {
430 + color: '#fff',
431 + fontWeight: '600',
432 + },
427 433 deleteButton: {
428 434 paddingVertical: 16,
429 435 borderRadius: 8,
430 - backgroundColor: '#fef2f2',
431 436 alignItems: 'center',
432 437 },
433 438 deleteButtonText: {
434 - color: '#ef4444',
435 439 fontSize: 16,
436 440 fontWeight: '600',
437 441 },
MODIFY components/task-item.tsx +7 -5
diff --git a/components/task-item.tsx b/components/task-item.tsx
index 0a8a175..bbe0770 100644
--- a/components/task-item.tsx
+++ b/components/task-item.tsx
@@ -18,7 +18,7 @@
18 18
19 19 const priorityColor = task.priority === 'high' ? '#ef4444' :
20 20 task.priority === 'medium' ? '#f59e0b' :
21 - '#6b7280';
21 + colors.subtitle;
22 22
23 23 const formatDate = (timestamp?: number) => {
24 24 if (!timestamp) return null;
@@ -38,7 +38,11 @@
38 38 return (
39 39 <ThemedView style={styles.container}>
40 40 <TouchableOpacity
41 - style={[styles.checkbox, task.completed && styles.checkboxCompleted]}
41 + style={[
42 + styles.checkbox,
43 + { borderColor: colors.placeholder },
44 + task.completed && [styles.checkboxCompleted, { backgroundColor: colors.tint }]
45 + ]}
42 46 onPress={onToggleComplete}
43 47 >
44 48 {task.completed && <ThemedText style={styles.checkmark}>โœ“</ThemedText>}
@@ -92,14 +96,12 @@
92 96 height: 24,
93 97 borderRadius: 12,
94 98 borderWidth: 2,
95 - borderColor: '#94a3b8',
96 99 justifyContent: 'center',
97 100 alignItems: 'center',
98 101 marginTop: 2,
99 102 },
100 103 checkboxCompleted: {
101 - backgroundColor: '#0a7ea4',
102 - borderColor: '#0a7ea4',
104 + borderWidth: 0,
103 105 },
104 106 checkmark: {
105 107 color: '#fff',
MODIFY components/task-list.tsx +7 -3
diff --git a/components/task-list.tsx b/components/task-list.tsx
index f55c18e..5b1a35b 100644
--- a/components/task-list.tsx
+++ b/components/task-list.tsx
@@ -4,6 +4,8 @@
4 4 import { TaskItem } from './task-item';
5 5 import { ThemedText } from './themed-text';
6 6 import { ThemedView } from './themed-view';
7 +import { Colors } from '@/constants/theme';
8 +import { useColorScheme } from '@/hooks/use-color-scheme';
7 9
8 10 interface TaskListProps {
9 11 tasks: Task[];
@@ -14,6 +16,9 @@
14 16 }
15 17
16 18 export function TaskList({ tasks, onTaskPress, onToggleComplete, emptyMessage, scrollable = true }: TaskListProps) {
19 + const colorScheme = useColorScheme() ?? 'light';
20 + const colors = Colors[colorScheme];
21 +
17 22 if (tasks.length === 0) {
18 23 return (
19 24 <ThemedView style={styles.emptyContainer}>
@@ -34,7 +39,7 @@
34 39 onPress={() => onTaskPress(item)}
35 40 onToggleComplete={() => onToggleComplete(item.id)}
36 41 />
37 - {index < tasks.length - 1 && <View style={styles.separator} />}
42 + {index < tasks.length - 1 && <View style={[styles.separator, { backgroundColor: colors.border }]} />}
38 43 </React.Fragment>
39 44 ))}
40 45 </ThemedView>
@@ -52,7 +57,7 @@
52 57 onToggleComplete={() => onToggleComplete(item.id)}
53 58 />
54 59 )}
55 - ItemSeparatorComponent={() => <View style={styles.separator} />}
60 + ItemSeparatorComponent={() => <View style={[styles.separator, { backgroundColor: colors.border }]} />}
56 61 contentContainerStyle={styles.list}
57 62 />
58 63 );
@@ -64,7 +69,6 @@
64 69 },
65 70 separator: {
66 71 height: StyleSheet.hairlineWidth,
67 - backgroundColor: '#e2e8f0',
68 72 marginHorizontal: 16,
69 73 },
70 74 emptyContainer: {
MODIFY constants/theme.ts +19 -1
diff --git a/constants/theme.ts b/constants/theme.ts
index f06facd..a39c7ca 100644
--- a/constants/theme.ts
+++ b/constants/theme.ts
@@ -6,7 +6,7 @@
6 6 import { Platform } from 'react-native';
7 7
8 8 const tintColorLight = '#0a7ea4';
9 -const tintColorDark = '#fff';
9 +const tintColorDark = '#60a5fa';
10 10
11 11 export const Colors = {
12 12 light: {
@@ -16,6 +16,15 @@
16 16 icon: '#687076',
17 17 tabIconDefault: '#687076',
18 18 tabIconSelected: tintColorLight,
19 + border: '#e2e8f0',
20 + cardBackground: '#f8fafc',
21 + inputBackground: '#fff',
22 + inputBorder: '#e2e8f0',
23 + placeholder: '#94a3b8',
24 + subtitle: '#64748b',
25 + danger: '#ef4444',
26 + dangerBackground: '#fef2f2',
27 + archiveBackground: '#f1f5f9',
19 28 },
20 29 dark: {
21 30 text: '#ECEDEE',
@@ -24,6 +33,15 @@
24 33 icon: '#9BA1A6',
25 34 tabIconDefault: '#9BA1A6',
26 35 tabIconSelected: tintColorDark,
36 + border: '#2d3748',
37 + cardBackground: '#1e293b',
38 + inputBackground: '#1e293b',
39 + inputBorder: '#334155',
40 + placeholder: '#64748b',
41 + subtitle: '#94a3b8',
42 + danger: '#ef4444',
43 + dangerBackground: '#450a0a',
44 + archiveBackground: '#1e293b',
27 45 },
28 46 };
29 47
ADD contexts/theme-context.tsx +64 -0
diff --git a/contexts/theme-context.tsx b/contexts/theme-context.tsx
new file mode 100644
index 0000000..c7b8241
--- /dev/null
+++ b/contexts/theme-context.tsx
@@ -0,0 +1,64 @@
1 +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
2 +import { useColorScheme as useSystemColorScheme } from 'react-native';
3 +import AsyncStorage from '@react-native-async-storage/async-storage';
4 +
5 +type ThemeMode = 'light' | 'dark' | 'system';
6 +type ColorScheme = 'light' | 'dark';
7 +
8 +interface ThemeContextType {
9 + themeMode: ThemeMode;
10 + colorScheme: ColorScheme;
11 + setThemeMode: (mode: ThemeMode) => void;
12 +}
13 +
14 +const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
15 +
16 +const THEME_STORAGE_KEY = '@taskflow:theme';
17 +
18 +export function ThemeProvider({ children }: { children: ReactNode }) {
19 + const systemColorScheme = useSystemColorScheme();
20 + const [themeMode, setThemeModeState] = useState<ThemeMode>('system');
21 +
22 + useEffect(() => {
23 + loadTheme();
24 + }, []);
25 +
26 + const loadTheme = async () => {
27 + try {
28 + const saved = await AsyncStorage.getItem(THEME_STORAGE_KEY);
29 + if (saved) {
30 + setThemeModeState(saved as ThemeMode);
31 + }
32 + } catch (error) {
33 + console.error('Failed to load theme:', error);
34 + }
35 + };
36 +
37 + const setThemeMode = async (mode: ThemeMode) => {
38 + try {
39 + await AsyncStorage.setItem(THEME_STORAGE_KEY, mode);
40 + setThemeModeState(mode);
41 + } catch (error) {
42 + console.error('Failed to save theme:', error);
43 + }
44 + };
45 +
46 + const colorScheme: ColorScheme =
47 + themeMode === 'system'
48 + ? (systemColorScheme ?? 'light')
49 + : themeMode;
50 +
51 + return (
52 + <ThemeContext.Provider value={{ themeMode, colorScheme, setThemeMode }}>
53 + {children}
54 + </ThemeContext.Provider>
55 + );
56 +}
57 +
58 +export function useTheme() {
59 + const context = useContext(ThemeContext);
60 + if (!context) {
61 + throw new Error('useTheme must be used within ThemeProvider');
62 + }
63 + return context;
64 +}
MODIFY hooks/use-color-scheme.ts +6 -1
diff --git a/hooks/use-color-scheme.ts b/hooks/use-color-scheme.ts
index 17e3c63..1d3bde3 100644
--- a/hooks/use-color-scheme.ts
+++ b/hooks/use-color-scheme.ts
@@ -1 +1,6 @@
1 -export { useColorScheme } from 'react-native';
1 +import { useTheme } from '@/contexts/theme-context';
2 +
3 +export function useColorScheme() {
4 + const { colorScheme } = useTheme();
5 + return colorScheme;
6 +}

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog