gitshark

Clone repository

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

← Commits

✨ Make the reminder time configurable per weekday

e68a7c716e986a207dcea169eea6eaa8eb73bd68 · Phillip Souza Furtner · 2026-09-02T07:35:25Z

Changes

5 files changed, +290 -57

MODIFY README.md +1 -1
diff --git a/README.md b/README.md
index b42c859..1a76aef 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
13 13 - **Local persistence** via AsyncStorage (JSON)
14 14 - **Export / import** as a JSON file using the native share sheet
15 15 - **WebDAV / Nextcloud sync** with last-write-wins conflict resolution; supports force-push and force-pull
16 -- **Daily task reminder** notification at a configurable time (native platforms only); the reminder shows how many tasks are scheduled for today, counted per day and re-counted shortly before delivery via a background task
16 +- **Daily task reminder** notification at a time configurable per weekday (native platforms only); the reminder shows how many tasks are scheduled for that day, counted per day and re-counted shortly before delivery via a background task
17 17 - **Light / dark / system** theme, persisted across sessions
18 18
19 19 ## Tech Stack
MODIFY app/(tabs)/settings.tsx +48 -30
diff --git "a/app/\050tabs\051/settings.tsx" "b/app/\050tabs\051/settings.tsx"
index 3e2a51c..31ddf71 100644
--- "a/app/\050tabs\051/settings.tsx"
+++ "b/app/\050tabs\051/settings.tsx"
@@ -10,14 +10,22 @@
10 10 import { useTheme } from '@/contexts/theme-context';
11 11 import { Colors } from '@/constants/theme';
12 12 import { useColorScheme } from '@/hooks/use-color-scheme';
13 -import { DEFAULT_REMINDER_TIME, ReminderTime, formatReminderTime } from '@/utils/reminder';
13 +import {
14 + DEFAULT_REMINDER_SCHEDULE,
15 + ReminderSchedule,
16 + ReminderTime,
17 + WEEKDAY_DISPLAY_ORDER,
18 + WEEKDAY_LABELS,
19 + describeReminderSchedule,
20 + formatReminderTime,
21 +} from '@/utils/reminder';
14 22
15 23 export default function SettingsScreen() {
16 24 const [syncing, setSyncing] = useState(false);
17 25 const [autoArchive, setAutoArchive] = useState(false);
18 26 const [dailyNotifications, setDailyNotifications] = useState(false);
19 - const [reminderTime, setReminderTime] = useState<ReminderTime>(DEFAULT_REMINDER_TIME);
20 - const [showTimePicker, setShowTimePicker] = useState(false);
27 + const [reminderSchedule, setReminderSchedule] = useState<ReminderSchedule>(DEFAULT_REMINDER_SCHEDULE);
28 + const [pickerWeekday, setPickerWeekday] = useState<number | null>(null);
21 29 const [webdavConfigured, setWebdavConfigured] = useState(false);
22 30 const [webdavInfo, setWebdavInfo] = useState<{ url: string; username: string } | null>(null);
23 31 const { themeMode, setThemeMode } = useTheme();
@@ -42,7 +50,7 @@
42 50
43 51 const checkNotificationStatus = async () => {
44 52 setDailyNotifications(await notificationService.isDailyReminderEnabled());
45 - setReminderTime(await notificationService.getReminderTime());
53 + setReminderSchedule(await notificationService.getReminderSchedule());
46 54 };
47 55
48 56 const handleExport = async () => {
@@ -215,7 +223,7 @@
215 223 setDailyNotifications(true);
216 224 Alert.alert(
217 225 'Notifications Enabled',
218 - `You will receive a daily reminder at ${formatReminderTime(reminderTime)}`
226 + `You will receive a daily reminder ${describeReminderSchedule(reminderSchedule)}`
219 227 );
220 228 }
221 229 } else {
@@ -226,10 +234,11 @@
226 234 };
227 235
228 236 const handleReminderTimeChange = async (
237 + weekday: number,
229 238 event: { type: string },
230 239 selectedDate?: Date
231 240 ) => {
232 - setShowTimePicker(Platform.OS === 'ios');
241 + setPickerWeekday(Platform.OS === 'ios' ? weekday : null);
233 242 if (event.type === 'dismissed' || !selectedDate) {
234 243 return;
235 244 }
@@ -237,17 +246,17 @@
237 246 hour: selectedDate.getHours(),
238 247 minute: selectedDate.getMinutes(),
239 248 };
240 - setReminderTime(newTime);
241 249 try {
242 - await notificationService.setReminderTime(newTime);
250 + setReminderSchedule(await notificationService.setReminderTimeForWeekday(weekday, newTime));
243 251 } catch {
244 252 Alert.alert('Error', 'Failed to update reminder time');
245 253 }
246 254 };
247 255
248 - const reminderDate = (() => {
256 + const pickerDate = (() => {
257 + const { hour, minute } = reminderSchedule[pickerWeekday ?? 0];
249 258 const date = new Date();
250 - date.setHours(reminderTime.hour, reminderTime.minute, 0, 0);
259 + date.setHours(hour, minute, 0, 0);
251 260 return date;
252 261 })();
253 262
@@ -330,7 +339,7 @@
330 339 <ThemedView style={styles.optionTextContainer}>
331 340 <ThemedText style={styles.optionText}>Daily Reminder</ThemedText>
332 341 <ThemedText style={styles.optionDescription}>
333 - Get notified daily at {formatReminderTime(reminderTime)} with your task count
342 + Get notified with your task count, {describeReminderSchedule(reminderSchedule)}
334 343 </ThemedText>
335 344 </ThemedView>
336 345 <Switch
@@ -343,31 +352,40 @@
343 352 {dailyNotifications && (
344 353 <>
345 354 <ThemedView style={[styles.option, { borderBottomColor: colors.border }]}>
346 - <ThemedView style={styles.optionWithSwitch}>
347 - <ThemedView style={styles.optionTextContainer}>
348 - <ThemedText style={styles.optionText}>Reminder Time</ThemedText>
349 - <ThemedText style={styles.optionDescription}>
350 - When the daily reminder is sent
351 - </ThemedText>
352 - </ThemedView>
353 - <TouchableOpacity
354 - style={[styles.timeButton, { borderColor: colors.border }]}
355 - onPress={() => setShowTimePicker(true)}
356 - >
357 - <ThemedText style={[styles.timeButtonText, { color: colors.tint }]}>
358 - {formatReminderTime(reminderTime)}
359 - </ThemedText>
360 - </TouchableOpacity>
361 - </ThemedView>
355 + <ThemedText style={styles.optionText}>Reminder Time</ThemedText>
356 + <ThemedText style={styles.optionDescription}>
357 + When the reminder is sent on each weekday
358 + </ThemedText>
362 359 </ThemedView>
363 360
364 - {showTimePicker && (
361 + {WEEKDAY_DISPLAY_ORDER.map(weekday => (
362 + <ThemedView
363 + key={weekday}
364 + style={[styles.option, { borderBottomColor: colors.border }]}
365 + >
366 + <ThemedView style={styles.optionWithSwitch}>
367 + <ThemedView style={styles.optionTextContainer}>
368 + <ThemedText style={styles.optionText}>{WEEKDAY_LABELS[weekday]}</ThemedText>
369 + </ThemedView>
370 + <TouchableOpacity
371 + style={[styles.timeButton, { borderColor: colors.border }]}
372 + onPress={() => setPickerWeekday(weekday)}
373 + >
374 + <ThemedText style={[styles.timeButtonText, { color: colors.tint }]}>
375 + {formatReminderTime(reminderSchedule[weekday])}
376 + </ThemedText>
377 + </TouchableOpacity>
378 + </ThemedView>
379 + </ThemedView>
380 + ))}
381 +
382 + {pickerWeekday !== null && (
365 383 <DateTimePicker
366 - value={reminderDate}
384 + value={pickerDate}
367 385 mode="time"
368 386 is24Hour
369 387 display={Platform.OS === 'ios' ? 'spinner' : 'default'}
370 - onChange={handleReminderTimeChange}
388 + onChange={(event, date) => handleReminderTimeChange(pickerWeekday, event, date)}
371 389 />
372 390 )}
373 391
MODIFY services/notification.service.ts +43 -17
diff --git a/services/notification.service.ts b/services/notification.service.ts
index ad744a1..b9b0b40 100644
--- a/services/notification.service.ts
+++ b/services/notification.service.ts
@@ -8,15 +8,22 @@
8 8 import type { NotificationRequest } from 'expo-notifications';
9 9 import taskService from './task.service';
10 10 import {
11 - DEFAULT_REMINDER_TIME,
11 + DEFAULT_REMINDER_SCHEDULE,
12 12 REMINDER_WINDOW_DAYS,
13 + ReminderSchedule,
13 14 ReminderTime,
14 15 buildReminderBody,
15 16 buildReminderOccurrences,
17 + createUniformSchedule,
18 + isValidReminderSchedule,
16 19 isValidReminderTime,
20 + withReminderTimeForWeekday,
17 21 } from '../utils/reminder';
18 22
19 -const REMINDER_TIME_KEY = '@taskflow_reminder_time';
23 +const REMINDER_SCHEDULE_KEY = '@taskflow_reminder_schedule';
24 +
25 +/** Pre-per-weekday key, holding a single time for all days. */
26 +const LEGACY_REMINDER_TIME_KEY = '@taskflow_reminder_time';
20 27 const REMINDER_ENABLED_KEY = '@taskflow_reminder_enabled';
21 28
22 29 /** Background task that recounts today's tasks and rewrites pending reminders. */
@@ -86,36 +93,55 @@
86 93 }
87 94
88 95 /**
89 - * Get the configured daily reminder time (falls back to the default).
96 + * Get the reminder time of every weekday. A schedule stored before reminder
97 + * times were per-weekday is carried over to all seven days.
90 98 */
91 - async getReminderTime(): Promise<ReminderTime> {
99 + async getReminderSchedule(): Promise<ReminderSchedule> {
92 100 try {
93 - const json = await AsyncStorage.getItem(REMINDER_TIME_KEY);
101 + const json = await AsyncStorage.getItem(REMINDER_SCHEDULE_KEY);
94 102 if (json) {
95 - const parsed = JSON.parse(json) as ReminderTime;
96 - if (isValidReminderTime(parsed)) {
103 + const parsed = JSON.parse(json) as ReminderSchedule;
104 + if (isValidReminderSchedule(parsed)) {
97 105 return parsed;
98 106 }
99 107 }
108 +
109 + const legacyJson = await AsyncStorage.getItem(LEGACY_REMINDER_TIME_KEY);
110 + if (legacyJson) {
111 + const legacy = JSON.parse(legacyJson) as ReminderTime;
112 + if (isValidReminderTime(legacy)) {
113 + return createUniformSchedule(legacy);
114 + }
115 + }
100 116 } catch (error) {
101 - console.error('Failed to load reminder time:', error);
117 + console.error('Failed to load reminder schedule:', error);
102 118 }
103 - return DEFAULT_REMINDER_TIME;
119 + return DEFAULT_REMINDER_SCHEDULE;
104 120 }
105 121
106 122 /**
107 - * Persist a new reminder time. If the daily reminder is enabled, the pending
108 - * reminders are rewritten to the new time.
123 + * Persist a full reminder schedule. If the daily reminder is enabled, the
124 + * pending reminders are rewritten to the new times.
109 125 */
110 - async setReminderTime(time: ReminderTime): Promise<void> {
111 - if (!isValidReminderTime(time)) {
112 - throw new Error('Invalid reminder time');
126 + async setReminderSchedule(schedule: ReminderSchedule): Promise<void> {
127 + if (!isValidReminderSchedule(schedule)) {
128 + throw new Error('Invalid reminder schedule');
113 129 }
114 - await AsyncStorage.setItem(REMINDER_TIME_KEY, JSON.stringify(time));
130 + await AsyncStorage.setItem(REMINDER_SCHEDULE_KEY, JSON.stringify(schedule));
115 131 await this.refreshDailyNotificationIfEnabled();
116 132 }
117 133
118 134 /**
135 + * Persist the reminder time of a single weekday (0 = Sunday), leaving the
136 + * other days as they are.
137 + */
138 + async setReminderTimeForWeekday(weekday: number, time: ReminderTime): Promise<ReminderSchedule> {
139 + const schedule = withReminderTimeForWeekday(await this.getReminderSchedule(), weekday, time);
140 + await this.setReminderSchedule(schedule);
141 + return schedule;
142 + }
143 +
144 + /**
119 145 * Whether the daily reminder is switched on. Installs from before the flag
120 146 * existed are recognised by their already-scheduled notifications.
121 147 */
@@ -215,8 +241,8 @@
215 241 await Notifications.cancelAllScheduledNotificationsAsync();
216 242
217 243 const tasks = await taskService.getAllTasks();
218 - const time = await this.getReminderTime();
219 - const occurrences = buildReminderOccurrences(tasks, time, {
244 + const schedule = await this.getReminderSchedule();
245 + const occurrences = buildReminderOccurrences(tasks, schedule, {
220 246 now: new Date(),
221 247 days: REMINDER_WINDOW_DAYS,
222 248 });
MODIFY utils/__tests__/reminder.test.ts +126 -4
diff --git a/utils/__tests__/reminder.test.ts b/utils/__tests__/reminder.test.ts
index 11e0670..cd5c09e 100644
--- a/utils/__tests__/reminder.test.ts
+++ b/utils/__tests__/reminder.test.ts
@@ -1,10 +1,17 @@
1 1 import {
2 2 DEFAULT_REMINDER_TIME,
3 + WEEKDAY_LABELS,
4 + WEEKDAY_DISPLAY_ORDER,
3 5 buildReminderBody,
4 6 buildReminderOccurrences,
5 7 countTasksDueOn,
8 + createUniformSchedule,
9 + describeReminderSchedule,
6 10 formatReminderTime,
11 + getReminderTimeForDate,
12 + isValidReminderSchedule,
7 13 isValidReminderTime,
14 + withReminderTimeForWeekday,
8 15 } from '../reminder';
9 16
10 17 describe('buildReminderBody', () => {
@@ -62,12 +69,100 @@
62 69 });
63 70 });
64 71
72 +describe('createUniformSchedule', () => {
73 + it('gives every weekday the same time', () => {
74 + const schedule = createUniformSchedule({ hour: 7, minute: 30 });
75 +
76 + expect(schedule).toHaveLength(7);
77 + expect(schedule.every(time => formatReminderTime(time) === '07:30')).toBe(true);
78 + });
79 +
80 + it('does not share one time object between weekdays', () => {
81 + const schedule = createUniformSchedule({ hour: 7, minute: 30 });
82 + expect(schedule[0]).not.toBe(schedule[1]);
83 + });
84 +});
85 +
86 +describe('withReminderTimeForWeekday', () => {
87 + it('changes only the given weekday', () => {
88 + const schedule = createUniformSchedule({ hour: 12, minute: 0 });
89 + const updated = withReminderTimeForWeekday(schedule, 1, { hour: 6, minute: 45 });
90 +
91 + expect(formatReminderTime(updated[1])).toBe('06:45');
92 + expect(updated.filter(time => formatReminderTime(time) === '12:00')).toHaveLength(6);
93 + });
94 +
95 + it('leaves the original schedule untouched', () => {
96 + const schedule = createUniformSchedule({ hour: 12, minute: 0 });
97 + withReminderTimeForWeekday(schedule, 1, { hour: 6, minute: 45 });
98 +
99 + expect(formatReminderTime(schedule[1])).toBe('12:00');
100 + });
101 +});
102 +
103 +describe('getReminderTimeForDate', () => {
104 + it('picks the time configured for that weekday', () => {
105 + const schedule = createUniformSchedule({ hour: 12, minute: 0 });
106 + schedule[1] = { hour: 6, minute: 45 };
107 +
108 + // 2026-08-17 is a Monday, 2026-08-18 a Tuesday.
109 + expect(formatReminderTime(getReminderTimeForDate(schedule, new Date(2026, 7, 17)))).toBe('06:45');
110 + expect(formatReminderTime(getReminderTimeForDate(schedule, new Date(2026, 7, 18)))).toBe('12:00');
111 + });
112 +});
113 +
114 +describe('isValidReminderSchedule', () => {
115 + it('accepts a schedule of seven valid times', () => {
116 + expect(isValidReminderSchedule(createUniformSchedule(DEFAULT_REMINDER_TIME))).toBe(true);
117 + });
118 +
119 + it('rejects a schedule that does not cover every weekday', () => {
120 + expect(isValidReminderSchedule([{ hour: 8, minute: 0 }])).toBe(false);
121 + });
122 +
123 + it('rejects a schedule holding an invalid time', () => {
124 + const schedule = createUniformSchedule(DEFAULT_REMINDER_TIME);
125 + schedule[3] = { hour: 24, minute: 0 };
126 + expect(isValidReminderSchedule(schedule)).toBe(false);
127 + });
128 +
129 + it('rejects non-arrays', () => {
130 + expect(isValidReminderSchedule({ hour: 8, minute: 0 } as never)).toBe(false);
131 + });
132 +});
133 +
134 +describe('describeReminderSchedule', () => {
135 + it('names the time when every day shares it', () => {
136 + expect(describeReminderSchedule(createUniformSchedule({ hour: 9, minute: 0 }))).toBe('09:00 every day');
137 + });
138 +
139 + it('says so when the days differ', () => {
140 + const schedule = createUniformSchedule({ hour: 9, minute: 0 });
141 + schedule[6] = { hour: 11, minute: 0 };
142 + expect(describeReminderSchedule(schedule)).toBe('at a time set per weekday');
143 + });
144 +});
145 +
146 +describe('weekday display order', () => {
147 + it('starts the week on Monday and ends on Sunday', () => {
148 + expect(WEEKDAY_DISPLAY_ORDER.map(day => WEEKDAY_LABELS[day])).toEqual([
149 + 'Monday',
150 + 'Tuesday',
151 + 'Wednesday',
152 + 'Thursday',
153 + 'Friday',
154 + 'Saturday',
155 + 'Sunday',
156 + ]);
157 + });
158 +});
159 +
65 160 describe('buildReminderOccurrences', () => {
66 - const time = { hour: 12, minute: 0 };
161 + const schedule = createUniformSchedule({ hour: 12, minute: 0 });
67 162
68 163 it('starts today when the reminder time is still ahead', () => {
69 164 const now = new Date(2026, 7, 17, 9, 30);
70 - const occurrences = buildReminderOccurrences([], time, { now, days: 2 });
165 + const occurrences = buildReminderOccurrences([], schedule, { now, days: 2 });
71 166
72 167 expect(occurrences.map(o => o.triggerAt)).toEqual([
73 168 new Date(2026, 7, 17, 12, 0),
@@ -77,11 +172,38 @@
77 172
78 173 it('skips today when the reminder time has already passed', () => {
79 174 const now = new Date(2026, 7, 17, 13, 0);
80 - const occurrences = buildReminderOccurrences([], time, { now, days: 2 });
175 + const occurrences = buildReminderOccurrences([], schedule, { now, days: 2 });
81 176
82 177 expect(occurrences.map(o => o.triggerAt)).toEqual([new Date(2026, 7, 18, 12, 0)]);
83 178 });
84 179
180 + it('fires each day at the time configured for its weekday', () => {
181 + const perDay = createUniformSchedule({ hour: 12, minute: 0 });
182 + perDay[1] = { hour: 6, minute: 30 }; // Monday
183 + perDay[2] = { hour: 20, minute: 15 }; // Tuesday
184 + perDay[3] = { hour: 9, minute: 0 }; // Wednesday
185 +
186 + const now = new Date(2026, 7, 17, 5, 0);
187 + const occurrences = buildReminderOccurrences([], perDay, { now, days: 3 });
188 +
189 + expect(occurrences.map(o => o.triggerAt)).toEqual([
190 + new Date(2026, 7, 17, 6, 30),
191 + new Date(2026, 7, 18, 20, 15),
192 + new Date(2026, 7, 19, 9, 0),
193 + ]);
194 + });
195 +
196 + it('skips only today when today’s own time has passed', () => {
197 + const perDay = createUniformSchedule({ hour: 12, minute: 0 });
198 + perDay[1] = { hour: 6, minute: 30 }; // Monday, already over
199 + perDay[2] = { hour: 20, minute: 15 }; // Tuesday
200 +
201 + const now = new Date(2026, 7, 17, 7, 0);
202 + const occurrences = buildReminderOccurrences([], perDay, { now, days: 2 });
203 +
204 + expect(occurrences.map(o => o.triggerAt)).toEqual([new Date(2026, 7, 18, 20, 15)]);
205 + });
206 +
85 207 it('counts the tasks of each day separately', () => {
86 208 const now = new Date(2026, 7, 17, 9, 0);
87 209 const tasks = [
@@ -91,7 +213,7 @@
91 213 { dueDate: new Date(2026, 7, 18, 20, 0).getTime(), completed: true },
92 214 ];
93 215
94 - const occurrences = buildReminderOccurrences(tasks, time, { now, days: 3 });
216 + const occurrences = buildReminderOccurrences(tasks, schedule, { now, days: 3 });
95 217
96 218 expect(occurrences.map(o => o.taskCount)).toEqual([1, 2, 0]);
97 219 expect(occurrences.map(o => o.body)).toEqual([
MODIFY utils/reminder.ts +72 -5
diff --git a/utils/reminder.ts b/utils/reminder.ts
index 1126271..71746fd 100644
--- a/utils/reminder.ts
+++ b/utils/reminder.ts
@@ -25,9 +25,55 @@
25 25 body: string;
26 26 }
27 27
28 +/**
29 + * A reminder time for every weekday, indexed like `Date.getDay()`:
30 + * 0 = Sunday ... 6 = Saturday.
31 + */
32 +export type ReminderSchedule = ReminderTime[];
33 +
28 34 /** Default reminder time: 12:00 (noon). */
29 35 export const DEFAULT_REMINDER_TIME: ReminderTime = { hour: 12, minute: 0 };
30 36
37 +/** Weekday names, indexed like `Date.getDay()`. */
38 +export const WEEKDAY_LABELS = [
39 + 'Sunday',
40 + 'Monday',
41 + 'Tuesday',
42 + 'Wednesday',
43 + 'Thursday',
44 + 'Friday',
45 + 'Saturday',
46 +];
47 +
48 +/** Weekday indices in the order they are shown, Monday first. */
49 +export const WEEKDAY_DISPLAY_ORDER = [1, 2, 3, 4, 5, 6, 0];
50 +
51 +/** Build a schedule that reminds at the same time on every weekday. */
52 +export function createUniformSchedule(time: ReminderTime): ReminderSchedule {
53 + return WEEKDAY_LABELS.map(() => ({ ...time }));
54 +}
55 +
56 +/** Default schedule: the default time on every weekday. */
57 +export const DEFAULT_REMINDER_SCHEDULE: ReminderSchedule =
58 + createUniformSchedule(DEFAULT_REMINDER_TIME);
59 +
60 +/** Copy of `schedule` with `weekday` set to `time`. */
61 +export function withReminderTimeForWeekday(
62 + schedule: ReminderSchedule,
63 + weekday: number,
64 + time: ReminderTime
65 +): ReminderSchedule {
66 + return schedule.map((existing, day) => (day === weekday ? { ...time } : { ...existing }));
67 +}
68 +
69 +/** The reminder time configured for the weekday `date` falls on. */
70 +export function getReminderTimeForDate(
71 + schedule: ReminderSchedule,
72 + date: Date
73 +): ReminderTime {
74 + return schedule[date.getDay()];
75 +}
76 +
31 77 /** How many days of reminders are kept scheduled ahead. */
32 78 export const REMINDER_WINDOW_DAYS = 7;
33 79
@@ -58,13 +104,14 @@
58 104 }
59 105
60 106 /**
61 - * Build the upcoming reminders for the next `days` days, each carrying the
62 - * task count of its own day. Occurrences whose time has already passed are
63 - * left out, so the first entry is always in the future.
107 + * Build the upcoming reminders for the next `days` days, each at the time its
108 + * own weekday is configured for and carrying the task count of that day.
109 + * Occurrences whose time has already passed are left out, so the first entry
110 + * is always in the future.
64 111 */
65 112 export function buildReminderOccurrences(
66 113 tasks: ReminderTask[],
67 - time: ReminderTime,
114 + schedule: ReminderSchedule,
68 115 { now, days }: { now: Date; days: number }
69 116 ): ReminderOccurrence[] {
70 117 const occurrences: ReminderOccurrence[] = [];
@@ -72,7 +119,8 @@
72 119 for (let offset = 0; offset < days; offset++) {
73 120 const triggerAt = new Date(now);
74 121 triggerAt.setDate(triggerAt.getDate() + offset);
75 - triggerAt.setHours(time.hour, time.minute, 0, 0);
122 + const { hour, minute } = getReminderTimeForDate(schedule, triggerAt);
123 + triggerAt.setHours(hour, minute, 0, 0);
76 124
77 125 if (triggerAt.getTime() <= now.getTime()) {
78 126 continue;
@@ -92,6 +140,16 @@
92 140 return `${hh}:${mm}`;
93 141 }
94 142
143 +/**
144 + * Describe a schedule in one phrase, naming the time when every weekday
145 + * shares it.
146 + */
147 +export function describeReminderSchedule(schedule: ReminderSchedule): string {
148 + const first = formatReminderTime(schedule[0]);
149 + const uniform = schedule.every(time => formatReminderTime(time) === first);
150 + return uniform ? `${first} every day` : 'at a time set per weekday';
151 +}
152 +
95 153 /** Validate that a reminder time has in-range, integer hour and minute. */
96 154 export function isValidReminderTime(time: ReminderTime): boolean {
97 155 const { hour, minute } = time;
@@ -104,3 +162,12 @@
104 162 minute <= 59
105 163 );
106 164 }
165 +
166 +/** Validate that a schedule covers every weekday with a valid time. */
167 +export function isValidReminderSchedule(schedule: ReminderSchedule): boolean {
168 + return (
169 + Array.isArray(schedule) &&
170 + schedule.length === WEEKDAY_LABELS.length &&
171 + schedule.every(isValidReminderTime)
172 + );
173 +}

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog