From a1097a3eea26442cbcbc1bf2ce20a46bec7bb746 Mon Sep 17 00:00:00 2001 From: lauzonhomeschool <85599144+lauzonhomeschool@users.noreply.github.com> Date: Sun, 8 Jan 2023 23:04:13 -0500 Subject: [PATCH] [sched, alarm] allow dated Event repeat --- apps/alarm/ChangeLog | 1 + apps/alarm/README.md | 1 + apps/alarm/app.js | 101 +++++++++++++++++++++++++-------------- apps/alarm/metadata.json | 2 +- apps/sched/ChangeLog | 1 + apps/sched/README.md | 4 +- apps/sched/metadata.json | 2 +- apps/sched/sched.js | 28 ++++++++++- 8 files changed, 100 insertions(+), 40 deletions(-) diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index bb8a292a0..89fb08a6f 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -38,3 +38,4 @@ 0.35: Add automatic translation of more strings 0.36: alarm widget moved out of app 0.37: add message input and dated Events +0.38: Dated event repeat option diff --git a/apps/alarm/README.md b/apps/alarm/README.md index 0298e0836..9da142dab 100644 --- a/apps/alarm/README.md +++ b/apps/alarm/README.md @@ -13,6 +13,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master - `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely) - `New Timer` → Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds) - `New Event` → Configure a new event (triggered based on time and date) + - `Repeat` → Alarm can be be fired only once or repeated (every X number of _days_, _weeks_, _months_ or _years_) - `Advanced` - `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details - `Enable All` → Enable _all_ disabled alarms & timers diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 74007d04b..972080872 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -6,6 +6,8 @@ const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}) const WORKDAYS = 62 const WEEKEND = firstDayOfWeek ? 192 : 65; const EVERY_DAY = firstDayOfWeek ? 254 : 127; +const INTERVALS = ["day", "week", "month", "year"]; +const INTERVAL_LABELS = [/*LANG*/"Day", /*LANG*/"Week", /*LANG*/"Month", /*LANG*/"Year"]; const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA=="); const iconAlarmOff = "\0" + (g.theme.dark @@ -50,8 +52,9 @@ function showMainMenu() { alarms.forEach((e, index) => { var label = (e.timer ? require("time_utils").formatDuration(e.timer) - : (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "")) - ) + (e.msg ? " " + e.msg : ""); + : (e.date ? `${e.date.substring(5,10)}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : "")) + ) + (e.msg ? ` ${e.msg}` : ""); + menu[label] = { value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff), onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index) @@ -148,8 +151,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) { onchange: v => alarm.on = v }, /*LANG*/"Repeat": { - value: decodeDOW(alarm), - onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => { + value: decodeRepeat(alarm), + onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, date || alarm.dow, (repeat, dow) => { alarm.rp = repeat; alarm.dow = dow; prepareAlarmForSave(alarm, alarmIndex, time, date, true); @@ -174,9 +177,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) { }; if (!keyboard) delete menu[/*LANG*/"Message"]; - if (alarm.date || withDate) { - delete menu[/*LANG*/"Repeat"]; - } else { + if (!alarm.date) { delete menu[/*LANG*/"Day"]; delete menu[/*LANG*/"Month"]; delete menu[/*LANG*/"Year"]; @@ -225,49 +226,77 @@ function saveAndReload() { alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); } -function decodeDOW(alarm) { +function decodeRepeat(alarm) { return alarm.rp - ? require("date_utils") - .dows(firstDayOfWeek, 2) - .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_") - .join("") - .toLowerCase() + ? (alarm.date + ? `${alarm.rp.num}*${INTERVAL_LABELS[INTERVALS.indexOf(alarm.rp.interval)]}` + : require("date_utils") + .dows(firstDayOfWeek, 2) + .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_") + .join("") + .toLowerCase()) : /*LANG*/"Once" } -function showEditRepeatMenu(repeat, dow, dowChangeCallback) { +function showEditRepeatMenu(repeat, day, dowChangeCallback) { var originalRepeat = repeat; - var originalDow = dow; - var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY; + var dow; const menu = { "": { "title": /*LANG*/"Repeat Alarm" }, "< Back": () => dowChangeCallback(repeat, dow), - /*LANG*/"Once": { + /*LANG*/"Only Once": () => dowChangeCallback(false, EVERY_DAY) // The alarm will fire once. Internally it will be saved // as "fire every days" BUT the repeat flag is false so // we avoid messing up with the scheduler. - value: !repeat, - onchange: () => dowChangeCallback(false, EVERY_DAY) - }, - /*LANG*/"Workdays": { - value: repeat && dow == WORKDAYS, - onchange: () => dowChangeCallback(true, WORKDAYS) - }, - /*LANG*/"Weekends": { - value: repeat && dow == WEEKEND, - onchange: () => dowChangeCallback(true, WEEKEND) - }, - /*LANG*/"Every Day": { - value: repeat && dow == EVERY_DAY, - onchange: () => dowChangeCallback(true, EVERY_DAY) - }, - /*LANG*/"Custom": { - value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false, - onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow) - } }; + let restOfMenu; + if (typeof day === "number") { + dow = day; + var originalDow = dow; + var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY; + + restOfMenu = { + /*LANG*/"Workdays": { + value: repeat && dow == WORKDAYS, + onchange: () => dowChangeCallback(true, WORKDAYS) + }, + /*LANG*/"Weekends": { + value: repeat && dow == WEEKEND, + onchange: () => dowChangeCallback(true, WEEKEND) + }, + /*LANG*/"Every Day": { + value: repeat && dow == EVERY_DAY, + onchange: () => dowChangeCallback(true, EVERY_DAY) + }, + /*LANG*/"Custom": { + value: isCustom ? decodeRepeat({ rp: true, dow: dow }) : false, + onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow) + } + }; + } else { + var date = day; // eventually: detect day of date and configure a repeat e.g. 3rd Monday of Month + dow = EVERY_DAY; + repeat = repeat || {interval: "month", num: 1}; + + restOfMenu = { + /*LANG*/"Every": { + value: repeat.num, + min: 1, + onchange: v => repeat.num = v + }, + /*LANG*/"Interval": { + value: INTERVALS.indexOf(repeat.interval), + format: v => INTERVAL_LABELS[v], + min: 0, + max: INTERVALS.length - 1, + onchange: v => repeat.interval = INTERVALS[v] + } + }; + } + + Object.assign(menu, restOfMenu); E.showMenu(menu); } diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 29e71b3d9..28d48daab 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.37", + "version": "0.38", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm", diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index 634250d48..92b04fb32 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -22,3 +22,4 @@ 0.19: Update clock_info to refresh periodically on active alarms/timers 0.20: Alarm dismiss and snooze events 0.21: Fix crash in clock_info +0.22: Dated event repeat option diff --git a/apps/sched/README.md b/apps/sched/README.md index c874b5577..2fb201cee 100644 --- a/apps/sched/README.md +++ b/apps/sched/README.md @@ -45,7 +45,9 @@ Alarms are stored in an array in `sched.json`, and take the form: // eg (new Date()).toISOString().substr(0,10) msg : "Eat food", // message to display. last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! (No change from 0 on timers) - rp : true, // repeat the alarm every day? + rp : true, // repeat the alarm every day? If date is given, pass an object instead of a boolean, + // e.g. repeat every 2 months: { interval: "month", num: 2 }. + // Supported intervals: day, week, month, year vibrate : "...", // OPTIONAL pattern of '.', '-' and ' ' to use for when buzzing out this alarm (defaults to '..' if not set) hidden : false, // OPTIONAL if false, the widget should not show an icon for this alarm as : false, // auto snooze diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 2a1b0f8ca..98602318d 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.21", + "version": "0.22", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", diff --git a/apps/sched/sched.js b/apps/sched/sched.js index f2f2644f9..57876ff69 100644 --- a/apps/sched/sched.js +++ b/apps/sched/sched.js @@ -42,7 +42,9 @@ function showAlarm(alarm) { if (del) { alarms.splice(alarmIndex, 1); } else { - if (!alarm.timer) { + if (alarm.date && alarm.rp) { + setNextRepeatDate(alarm); + } else if (!alarm.timer) { alarm.last = new Date().getDate(); } if (alarm.ot !== undefined) { @@ -78,6 +80,30 @@ function showAlarm(alarm) { }); } + function setNextRepeatDate(alarm) { + let date = new Date(alarm.date); + let rp = alarm.rp; + switch(rp.interval) { + case "day": + date.setDate(date.getDate() + rp.num); + break; + case "week": + date.setDate(date.getDate() + (rp.num * 7)); + break; + case "month": + if (!alarm.od) alarm.od = date.getDate(); + date = new Date(date.getFullYear(), date.getMonth() + rp.num, alarm.od); + if (date.getDate() != alarm.od) date.setDate(0); + break; + case "year": + if (!alarm.od) alarm.od = date.getDate(); + date = new Date(date.getFullYear() + rp.num, date.getMonth(), alarm.od); + if (date.getDate() != alarm.od) date.setDate(0); + break; + } + alarm.date = date.toLocalISOString().slice(0,10); + } + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) return;