From ce06879fe46844476d4b7cce1c3926fa931fed15 Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Tue, 3 May 2022 21:15:40 +0200 Subject: [PATCH 1/7] [date_utils] Refactor functions, documentation - Use the same names used in locale module (so dow instead of getDOW, month instead of getMonth, etc.) - Add documentation --- modules/date_utils.js | 94 +++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/modules/date_utils.js b/modules/date_utils.js index da0ed24d9..7239d4f1f 100644 --- a/modules/date_utils.js +++ b/modules/date_utils.js @@ -1,39 +1,65 @@ -/* Utility functions that use the 'locale' module so can produce text -in the currently selected language. */ +// module "date_utils" +// +// Utility functions that use the "locale" module so can produce +// date-related text in the currently selected language. +// +// Some functions have a "firstDayOfWeek" parameter. +// Most used values are: +// - 0/undefined --> Sunday +// - 1 --> Monday +// but you can start the week from any day if you need it. +// +// Some functions have an "abbreviated" parameter. +// It supports the following 3 values: +// - 0/undefined --> get the full value, without abbreviation (eg.: "Monday", "January", etc.) +// - 1 --> get the short value (eg.: "Mon", "Jan", etc.) +// - 2 --> get only the first char (eg.: "M", "J", etc.) +// -/** Return the day of the week (0=Sunday) - short==0/undefined -> "Sunday" - short==1 -> "Sun" -*/ -exports.getDOW = (dow, short) => require("locale").dow({getDay:()=>dow},short); - -/** Return the month (1=January) - short==0/undefined -> "January" - short==1 -> "Jan" -*/ -exports.getMonth = (month, short) => require("locale").month({getMonth:()=>month-1},short); - -/** Return all 7 days of the week as an array ["Sunday","Monday",...]. - short==0/undefined -> ["Sunday",... - short==1 -> ["Sun",... - short==2 -> ["S",... -*/ -exports.getDOWs = (short) => { - var locale = require("locale"); - var days = []; - for (var i=0;i<7;i++) - days.push(locale.dow({getDay:()=>i},short).slice(0,(short==2)?1:100)); - return days; +/** + * @param {int} i The index of the day of the week (0 = Sunday) + * @param {int} abbreviated + * @returns The localized name of the i-th day of the week + */ +exports.dow = (i, abbreviated) => { + var dow = require("locale").dow(new Date(((i || 0) + 3.5) * 86400000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100); + return abbreviated == 2 ? dow.toUpperCase() : dow; } -/** Return all 12 months as an array ["January","February",...] - short==0/undefined -> ["January",... - short==1 -> ["Jan",... -*/ -exports.getMonths = (short) => { +/** + * @param {int} firstDayOfWeek 0/undefined -> Sunday, + * 1 -> Monday + * @param {int} abbreviated + * @returns All 7 days of the week (localized) as an array + */ +exports.dows = (firstDayOfWeek, abbreviated) => { + var dows = []; var locale = require("locale"); + for (var i = 0; i < 7; i++) { + dows.push(exports.dow(i + (firstDayOfWeek || 0), abbreviated)) + } + return abbreviated == 2 ? dows.map(dow => dow.toUpperCase()) : dows; +}; + +/** + * @param {int} i The index of the month (1 = January) + * @param {int} abbreviated + * @returns The localized name of the i-th month + */ +exports.month = (i, abbreviated) => { + var month = require("locale").month(new Date((i - 0.5) * 2628000000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100); + return abbreviated == 2 ? month.toUpperCase() : month; +} + +/** + * @param {int} abbreviated + * @returns All 12 months (localized) as an array + */ +exports.months = (abbreviated) => { var months = []; - for (var i=0;i<12;i++) - months.push(locale.month({getMonth:()=>i},short)); - return months; -} + var locale = require("locale"); + for (var i = 1; i <= 12; i++) { + months.push(locale.month(new Date((i - 0.5) * 2628000000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100)); + } + return abbreviated == 2 ? months.map(month => month.toUpperCase()) : months; +}; From 74db2585d714f9328aa9e4b3f20a996a9bdc82eb Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Sun, 1 May 2022 22:42:28 +0200 Subject: [PATCH 2/7] [Settings] Add new option for selecting the first day of the week --- apps/setting/README.md | 2 +- apps/setting/settings.js | 35 +++++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/setting/README.md b/apps/setting/README.md index 42e3939fb..77bf70845 100644 --- a/apps/setting/README.md +++ b/apps/setting/README.md @@ -7,7 +7,7 @@ This is Bangle.js's settings menu * **Beep** most Bangle.js do not have a speaker inside, but they can use the vibration motor to beep in different pitches. You can change the behaviour here to use a Piezo speaker if one is connected * **Vibration** enable/disable the vibration motor * **Quiet Mode** prevent notifications/alarms from vibrating/beeping/turning the screen on - see below -* **Locale** set time zone/whether the clock is 12/24 hour (for supported clocks) +* **Locale** set time zone, the time format (12/24h, for supported clocks) and the first day of the week * **Select Clock** if you have more than one clock face, select the default one * **Set Time** Configure the current time - Note that this can be done much more easily by choosing 'Set Time' from the App Loader * **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below. diff --git a/apps/setting/settings.js b/apps/setting/settings.js index afc7e23c8..a2a3dfbf0 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -37,18 +37,19 @@ function internalToG(u) { function resetSettings() { settings = { - ble: true, // Bluetooth enabled by default - blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used? - log: false, // Do log messages appear on screen? - quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence - timeout: 10, // Default LCD timeout in seconds - vibrate: true, // Vibration enabled by default. App must support - beep: BANGLEJS2?true:"vib", // Beep enabled by default. App must support - timezone: 0, // Set the timezone for the device - HID: false, // BLE HID mode, off by default - clock: null, // a string for the default clock's name - "12hour" : false, // 12 or 24 hour clock? - brightness: 1, // LCD brightness from 0 to 1 + ble: true, // Bluetooth enabled by default + blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used? + log: false, // Do log messages appear on screen? + quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence + timeout: 10, // Default LCD timeout in seconds + vibrate: true, // Vibration enabled by default. App must support + beep: BANGLEJS2 ? true : "vib", // Beep enabled by default. App must support + timezone: 0, // Set the timezone for the device + HID: false, // BLE HID mode, off by default + clock: null, // a string for the default clock's name + "12hour" : false, // 12 or 24 hour clock? + firstDayOfWeek: 0, // 0 -> Sunday (default), 1 -> Monday + brightness: 1, // LCD brightness from 0 to 1 // welcomed : undefined/true (whether welcome app should show) options: { wakeOnBTN1: true, @@ -493,6 +494,16 @@ function showLocaleMenu() { settings["12hour"] = v; updateSettings(); } + }, + /*LANG*/'Start Week On': { + value: settings["firstDayOfWeek"] || 0, + min: 0, // Sunday + max: 1, // Monday + format: v => require("date_utils").dow(v, 1), + onchange: v => { + settings["firstDayOfWeek"] = v; + updateSettings(); + }, } }; return E.showMenu(localemenu); From ec4ba0d0a71dd0b1469ec127047dcce7e35617c3 Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Mon, 2 May 2022 11:55:05 +0200 Subject: [PATCH 3/7] [Settings] Fix the time zone format A positive value is shown as '+X' instead of 'X' --- apps/setting/settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a2a3dfbf0..981a7b864 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -479,6 +479,7 @@ function showLocaleMenu() { '< Back': ()=>showSystemMenu(), /*LANG*/'Time Zone': { value: settings.timezone, + format: v => (v > 0 ? "+" : "") + v, min: -11, max: 13, step: 0.5, From fdcb965b5e93eb9d3cc3db31fb39e9be0dd05fba Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Mon, 2 May 2022 12:09:06 +0200 Subject: [PATCH 4/7] [Settings] Improve the "Date & Time" menu - Use month name instead of month number - Move the "time" options after the "date" options --- apps/setting/settings.js | 53 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 981a7b864..9b5bdae68 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -95,7 +95,7 @@ function showSystemMenu() { /*LANG*/'LCD': ()=>showLCDMenu(), /*LANG*/'Locale': ()=>showLocaleMenu(), /*LANG*/'Select Clock': ()=>showClockMenu(), - /*LANG*/'Set Time': ()=>showSetTimeMenu() + /*LANG*/'Date & Time': ()=>showSetTimeMenu() }; return E.showMenu(mainmenu); @@ -488,9 +488,9 @@ function showLocaleMenu() { updateSettings(); } }, - /*LANG*/'Clock Style': { + /*LANG*/'Time Format': { value: !!settings["12hour"], - format: v => v ? "12hr" : "24hr", + format: v => v ? "12h" : "24h", onchange: v => { settings["12hour"] = v; updateSettings(); @@ -618,11 +618,34 @@ function showClockMenu() { function showSetTimeMenu() { d = new Date(); const timemenu = { - '': { 'title': /*LANG*/'Set Time' }, + '': { 'title': /*LANG*/'Date & Time' }, '< Back': function () { setTime(d.getTime() / 1000); showSystemMenu(); }, + /*LANG*/'Day': { + value: d.getDate(), + onchange: function (v) { + this.value = ((v+30)%31)+1; + d.setDate(this.value); + } + }, + /*LANG*/'Month': { + value: d.getMonth() + 1, + format: v => require("date_utils").month(v), + onchange: function (v) { + this.value = ((v+11)%12)+1; + d.setMonth(this.value - 1); + } + }, + /*LANG*/'Year': { + value: d.getFullYear(), + min: 2019, + max: 2100, + onchange: function (v) { + d.setFullYear(v); + } + }, /*LANG*/'Hour': { value: d.getHours(), onchange: function (v) { @@ -643,28 +666,6 @@ function showSetTimeMenu() { this.value = (v+60)%60; d.setSeconds(this.value); } - }, - /*LANG*/'Date': { - value: d.getDate(), - onchange: function (v) { - this.value = ((v+30)%31)+1; - d.setDate(this.value); - } - }, - /*LANG*/'Month': { - value: d.getMonth() + 1, - onchange: function (v) { - this.value = ((v+11)%12)+1; - d.setMonth(this.value - 1); - } - }, - /*LANG*/'Year': { - value: d.getFullYear(), - min: 2019, - max: 2100, - onchange: function (v) { - d.setFullYear(v); - } } }; return E.showMenu(timemenu); From 7abb4c780c28f5b05db4ea78c85d706414fda702 Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Mon, 2 May 2022 12:12:19 +0200 Subject: [PATCH 5/7] [Settings] Update metadata and changelog --- apps/setting/ChangeLog | 2 ++ apps/setting/metadata.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index fe259827c..bfd32a130 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -46,3 +46,5 @@ 0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272) 0.42: Fix theme customizer on new Bangle 2 firmware 0.43: Add some Bangle 1 colours to theme customizer +0.44: Add "Start Week On X" option (#1780) + UI improvements to Locale and Date & Time menu diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index 750752bd7..85dddf9db 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.43", + "version": "0.44", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", From ea70f011857389c683e0005ec6fac88574fdff4e Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Tue, 3 May 2022 21:34:03 +0200 Subject: [PATCH 6/7] [Alarms & Timers] Add support for Monday as first day of the week --- apps/alarm/app.js | 119 +++++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 90a62afc5..25c03c3c5 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -2,10 +2,14 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); // An array of alarm objects (see sched/README.md) -let alarms = require("sched").getAlarms(); +var alarms = require("sched").getAlarms(); + +// 0 = Sunday +// 1 = Monday +var firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0; function getCurrentTime() { - let time = new Date(); + var time = new Date(); return ( time.getHours() * 3600000 + time.getMinutes() * 60000 + @@ -14,6 +18,9 @@ function getCurrentTime() { } function saveAndReload() { + // Before saving revert the dow to the standard format + alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek)); + require("sched").setAlarms(alarms); require("sched").reload(); } @@ -23,30 +30,32 @@ function showMainMenu() { // Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA") const menu = { '': { 'title': /*LANG*/'Alarms&Timers' }, - /*LANG*/'< Back' : ()=>{load();}, - /*LANG*/'New Alarm': ()=>editAlarm(-1), - /*LANG*/'New Timer': ()=>editTimer(-1) + /*LANG*/'< Back': () => { load(); }, + /*LANG*/'New Alarm': () => editAlarm(-1), + /*LANG*/'New Timer': () => editTimer(-1) }; - alarms.forEach((alarm,idx)=>{ - var type,txt; // a leading space is currently required (JS error in Espruino 2v12) + alarms.forEach((alarm, idx) => { + alarm.dow = handleFirstDayOfWeek(alarm.dow, firstDayOfWeek); + + var type, txt; // a leading space is currently required (JS error in Espruino 2v12) if (alarm.timer) { type = /*LANG*/"Timer"; - txt = " "+require("sched").formatTime(alarm.timer); + txt = " " + require("sched").formatTime(alarm.timer); } else { type = /*LANG*/"Alarm"; - txt = " "+require("sched").formatTime(alarm.t); + txt = " " + require("sched").formatTime(alarm.t); } - if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA="); + if (alarm.rp) txt += "\0" + atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA="); // rename duplicate alarms - if (menu[type+txt]) { + if (menu[type + txt]) { var n = 2; - while (menu[type+" "+n+txt]) n++; - txt = type+" "+n+txt; - } else txt = type+txt; + while (menu[type + " " + n + txt]) n++; + txt = type + " " + n + txt; + } else txt = type + txt; // add to menu menu[txt] = { - value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"), - onchange : function() { + value: "\0" + atob(alarm.on ? "EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g" : "EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"), + onchange: function () { setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm); } }; @@ -69,25 +78,27 @@ function showMainMenu() { function editDOW(dow, onchange) { const menu = { '': { 'title': /*LANG*/'Days of Week' }, - /*LANG*/'< Back' : () => onchange(dow) + /*LANG*/'< Back': () => onchange(dow) }; - for (let i = 0; i < 7; i++) (i => { - let dayOfWeek = require("locale").dow({ getDay: () => i }); - menu[dayOfWeek] = { - value: !!(dow&(1< { + menu[dows[i]] = { + value: !!(dow & (1 << (i + firstDayOfWeek))), format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", - onchange: v => v ? dow |= 1< v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek))) }; })(i); E.showMenu(menu); } function editAlarm(alarmIndex, alarm) { - let newAlarm = alarmIndex < 0; - let a = require("sched").newDefaultAlarm(); + var newAlarm = alarmIndex < 0; + var a = require("sched").newDefaultAlarm(); + a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek); + if (!newAlarm) Object.assign(a, alarms[alarmIndex]); - if (alarm) Object.assign(a,alarm); - let t = require("sched").decodeTime(a.t); + if (alarm) Object.assign(a, alarm); + var t = require("sched").decodeTime(a.t); const menu = { '': { 'title': /*LANG*/'Alarm' }, @@ -96,17 +107,17 @@ function editAlarm(alarmIndex, alarm) { showMainMenu(); }, /*LANG*/'Hours': { - value: t.hrs, min : 0, max : 23, wrap : true, - onchange: v => t.hrs=v + value: t.hrs, min: 0, max: 23, wrap: true, + onchange: v => t.hrs = v }, /*LANG*/'Minutes': { - value: t.mins, min : 0, max : 59, wrap : true, - onchange: v => t.mins=v + value: t.mins, min: 0, max: 59, wrap: true, + onchange: v => t.mins = v }, /*LANG*/'Enabled': { value: a.on, format: v => v ? /*LANG*/"On" : /*LANG*/"Off", - onchange: v=>a.on=v + onchange: v => a.on = v }, /*LANG*/'Repeat': { value: a.rp, @@ -114,14 +125,14 @@ function editAlarm(alarmIndex, alarm) { onchange: v => a.rp = v }, /*LANG*/'Days': { - value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< setTimeout(editDOW, 100, a.dow, d => { a.dow = d; a.t = require("sched").encodeTime(t); editAlarm(alarmIndex, a); }) }, - /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), + /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), /*LANG*/'Auto Snooze': { value: a.as, format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", @@ -156,11 +167,11 @@ function saveAlarm(newAlarm, alarmIndex, a, t) { } function editTimer(alarmIndex, alarm) { - let newAlarm = alarmIndex < 0; - let a = require("sched").newDefaultTimer(); + var newAlarm = alarmIndex < 0; + var a = require("sched").newDefaultTimer(); if (!newAlarm) Object.assign(a, alarms[alarmIndex]); - if (alarm) Object.assign(a,alarm); - let t = require("sched").decodeTime(a.timer); + if (alarm) Object.assign(a, alarm); + var t = require("sched").decodeTime(a.timer); const menu = { '': { 'title': /*LANG*/'Timer' }, @@ -169,26 +180,26 @@ function editTimer(alarmIndex, alarm) { showMainMenu(); }, /*LANG*/'Hours': { - value: t.hrs, min : 0, max : 23, wrap : true, - onchange: v => t.hrs=v + value: t.hrs, min: 0, max: 23, wrap: true, + onchange: v => t.hrs = v }, /*LANG*/'Minutes': { - value: t.mins, min : 0, max : 59, wrap : true, - onchange: v => t.mins=v + value: t.mins, min: 0, max: 59, wrap: true, + onchange: v => t.mins = v }, /*LANG*/'Enabled': { value: a.on, format: v => v ? /*LANG*/"On" : /*LANG*/"Off", onchange: v => a.on = v }, - /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), + /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), }; menu[/*LANG*/"Cancel"] = () => showMainMenu(); if (!newAlarm) { - menu[/*LANG*/"Delete"] = function() { - alarms.splice(alarmIndex,1); + menu[/*LANG*/"Delete"] = function () { + alarms.splice(alarmIndex, 1); saveAndReload(); showMainMenu(); }; @@ -210,6 +221,26 @@ function saveTimer(newAlarm, alarmIndex, a, t) { saveAndReload(); } +function handleFirstDayOfWeek(dow, firstDayOfWeek) { + if (firstDayOfWeek == 1) { + if ((dow & 1) == 1) { + // By default 1 = Sunday. + // Here the week starts on Monday and Sunday is ON so move Sunday to 128. + dow += 127; + } else if ((dow & 128) == 128) { + dow -= 127; + } + } + return dow; +} + +function decodeDOW(dow) { + return require("date_utils") + .dows(firstDayOfWeek, 2) + .map((day, index) => dow & (1 << (index + firstDayOfWeek)) ? day : "_") + .join(""); +} + function enableAll(on) { E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" From 4d4eccb821c4daf5e2e363fdb585f2c36f2c8752 Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Tue, 3 May 2022 22:01:04 +0200 Subject: [PATCH 7/7] [Alarms & Timers] Update metadata and changelog --- apps/alarm/ChangeLog | 1 + apps/alarm/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 9daf7bcbf..ca1417b5b 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -25,3 +25,4 @@ 0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow Add "Enable All", "Disable All" and "Remove All" actions 0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu +0.26: Add support for Monday as first day of the week (#1780) diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 33312beb6..c062b030d 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.25", + "version": "0.26", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget",