๐ Add a lightmode
Changes
8 files changed, +248 -68
MODIFY
app/(tabs)/settings.tsx
+85 -11
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+}