diff --git a/README.md b/README.md index a6c449cc7..78dd1b492 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ try and keep filenames short to avoid overflowing the buffer. ### Screenshots -In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { url:"screenshot.png" } ],` +In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { "url":"screenshot.png" } ],` To get a screenshot you can: diff --git a/apps/accellog/ChangeLog b/apps/accellog/ChangeLog index c0097db80..80981fe27 100644 --- a/apps/accellog/ChangeLog +++ b/apps/accellog/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Use the new multiplatform 'Layout' library 0.03: Exit as first menu option, dont show decimal places for seconds +0.04: Localisation, change Exit->Back to allow back-arrow to appear on 2v13 firmware diff --git a/apps/accellog/app.js b/apps/accellog/app.js index 4bead361e..f4c1b3c5a 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -7,21 +7,21 @@ function getFileName(n) { function showMenu() { var menu = { - "" : { title : "Accel Logger" }, - "Exit" : function() { + "" : { title : /*LANG*/"Accel Logger" }, + "< Back" : function() { load(); }, - "File No" : { + /*LANG*/"File No" : { value : fileNumber, min : 0, max : MAXLOGS, onchange : v => { fileNumber=v; } }, - "Start" : function() { + /*LANG*/"Start" : function() { E.showMenu(); startRecord(); }, - "View Logs" : function() { + /*LANG*/"View Logs" : function() { viewLogs(); }, }; @@ -37,29 +37,29 @@ function viewLog(n) { if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 ); var menu = { - "" : { title : "Log "+n } + "" : { title : "Log "+n }, + "< Back" : () => { viewLogs(); } }; - menu[records+" Records"] = ""; - menu[length+" Seconds"] = ""; - menu["DELETE"] = function() { - E.showPrompt("Delete Log "+n).then(ok=>{ + menu[records+/*LANG*/" Records"] = ""; + menu[length+/*LANG*/" Seconds"] = ""; + menu[/*LANG*/"DELETE"] = function() { + E.showPrompt(/*LANG*/"Delete Log "+n).then(ok=>{ if (ok) { - E.showMessage("Erasing..."); + E.showMessage(/*LANG*/"Erasing..."); f.erase(); viewLogs(); } else viewLog(n); }); }; - menu["< Back"] = function() { - viewLogs(); - }; + E.showMenu(menu); } function viewLogs() { var menu = { - "" : { title : "Logs" } + "" : { title : /*LANG*/"Logs" }, + "< Back" : () => { showMenu(); } }; var hadLogs = false; @@ -67,14 +67,13 @@ function viewLogs() { var f = require("Storage").open(getFileName(i), "r"); if (f.readLine()!==undefined) { (function(i) { - menu["Log "+i] = () => viewLog(i); + menu[/*LANG*/"Log "+i] = () => viewLog(i); })(i); hadLogs = true; } } if (!hadLogs) - menu["No Logs Found"] = function(){}; - menu["< Back"] = function() { showMenu(); }; + menu[/*LANG*/"No Logs Found"] = function(){}; E.showMenu(menu); } @@ -83,7 +82,7 @@ function startRecord(force) { // check for existing file var f = require("Storage").open(getFileName(fileNumber), "r"); if (f.readLine()!==undefined) - return E.showPrompt("Overwrite Log "+fileNumber+"?").then(ok=>{ + return E.showPrompt(/*LANG*/"Overwrite Log "+fileNumber+"?").then(ok=>{ if (ok) startRecord(true); else showMenu(); }); } @@ -93,14 +92,14 @@ function startRecord(force) { var Layout = require("Layout"); var layout = new Layout({ type: "v", c: [ - {type:"txt", font:"6x8", label:"Samples", pad:2}, + {type:"txt", font:"6x8", label:/*LANG*/"Samples", pad:2}, {type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, - {type:"txt", font:"6x8", label:"Time", pad:2}, + {type:"txt", font:"6x8", label:/*LANG*/"Time", pad:2}, {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, - {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1}, + {type:"txt", font:"6x8:2", label:/*LANG*/"RECORDING", bgCol:"#f00", pad:5, fillx:1}, ] },{btns:[ // Buttons... - {label:"STOP", cb:()=>{ + {label:/*LANG*/"STOP", cb:()=>{ Bangle.removeListener('accel', accelHandler); showMenu(); }} diff --git a/apps/accellog/metadata.json b/apps/accellog/metadata.json index a30c9a6fc..fdf6cf320 100644 --- a/apps/accellog/metadata.json +++ b/apps/accellog/metadata.json @@ -2,7 +2,7 @@ "id": "accellog", "name": "Acceleration Logger", "shortName": "Accel Log", - "version": "0.03", + "version": "0.04", "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", "icon": "app.png", "tags": "outdoor", diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog index 5560f00bc..53e29a66d 100644 --- a/apps/activityreminder/ChangeLog +++ b/apps/activityreminder/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Fix the settings bug and some tweaking diff --git a/apps/activityreminder/README.md b/apps/activityreminder/README.md index 03466bbbf..1e643fb54 100644 --- a/apps/activityreminder/README.md +++ b/apps/activityreminder/README.md @@ -3,11 +3,11 @@ A reminder to take short walks for the ones with a sedentary lifestyle. The alert will popup only if you didn't take your short walk yet -Differents settings can be personnalized: +Different settings can be personalized: - Enable : Enable/Disable the app - Start hour: Hour to start the reminder - End hour: Hour to end the reminder -- Max innactivity: Maximum innactivity time to allow before the alert. From 15 min to 60 min +- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 min - Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 15 min - Min steps: Minimal amount of steps to count as an activity diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js index 9fb52e9ac..310dc10b0 100644 --- a/apps/activityreminder/app.js +++ b/apps/activityreminder/app.js @@ -15,7 +15,7 @@ function drawAlert(){ }); Bangle.buzz(400); - setTimeout(load, 10000); + setTimeout(load, 20000); } function run(){ diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js index fee30e4c3..712842fba 100644 --- a/apps/activityreminder/lib.js +++ b/apps/activityreminder/lib.js @@ -6,17 +6,17 @@ exports.loadSettings = function() { maxInnactivityMin: 30, dismissDelayMin: 15, minSteps: 50 - }, require("Storage").readJSON("ar.settings.json", true) || {}); + }, require("Storage").readJSON("activityreminder.s.json", true) || {}); }; exports.writeSettings = function(settings){ - require("Storage").writeJSON("ar.settings.json", settings); + require("Storage").writeJSON("activityreminder.s.json", settings); }; exports.saveStepsArray = function(stepsArray) { - require("Storage").writeJSON("ar.stepsarray.json", stepsArray); + require("Storage").writeJSON("activityreminder.sa.json", stepsArray); }; exports.loadStepsArray = function(){ - return require("Storage").readJSON("ar.stepsarray.json") || []; + return require("Storage").readJSON("activityreminder.sa.json") || []; }; \ No newline at end of file diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json index bc31776d6..eba5de105 100644 --- a/apps/activityreminder/metadata.json +++ b/apps/activityreminder/metadata.json @@ -3,7 +3,7 @@ "name": "Activity Reminder", "shortName":"Activity Reminder", "description": "A reminder to take short walks for the ones with a sedentary lifestyle", - "version":"0.01", + "version":"0.02", "icon": "app.png", "type": "app", "tags": "tool,activity", @@ -17,6 +17,7 @@ {"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true} ], "data": [ - {"name": "ar.settings.json", "name": "ar.stepsarray.json"} + {"name": "activityreminder.s.json"}, + {"name": "activityreminder.sa.json"} ] } diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js index 65a19feb2..9b9a0ecd8 100644 --- a/apps/activityreminder/settings.js +++ b/apps/activityreminder/settings.js @@ -7,7 +7,7 @@ "" : { "title" : "Activity Reminder" }, "< Back" : () => back(), 'Enable': { - value: !!settings.enabled, + value: settings.enabled, format: v => v?"Yes":"No", onchange: v => { settings.enabled = v; @@ -15,7 +15,7 @@ } }, 'Start hour': { - value: 9|settings.startHour, + value: settings.startHour, min: 0, max: 24, onchange: v => { settings.startHour = v; @@ -23,31 +23,37 @@ } }, 'End hour': { - value: 20|settings.endHour, + value: settings.endHour, min: 0, max: 24, onchange: v => { settings.endHour = v; require("activityreminder").writeSettings(settings); } }, - 'Max innactivity': { - value: 30|settings.maxInnactivityMin, - min: 15, max: 60, + 'Max inactivity': { + value: settings.maxInnactivityMin, + min: 15, max: 120, onchange: v => { settings.maxInnactivityMin = v; require("activityreminder").writeSettings(settings); + }, + format: x => { + return x + " min"; } }, 'Dismiss delay': { - value: 10|settings.dismissDelayMin, + value: settings.dismissDelayMin, min: 5, max: 15, onchange: v => { settings.dismissDelayMin = v; require("activityreminder").writeSettings(settings); + }, + format: x => { + return x + " min"; } }, 'Min steps': { - value: 50|settings.minSteps, + value: settings.minSteps, min: 10, max: 500, onchange: v => { settings.minSteps = v; diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index e3b69e76e..41dd93081 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -20,4 +20,7 @@ 0.19: Ensure rescheduled alarms that already fired have 'last' reset 0.20: Use the new 'sched' factories to initialize new alarms/timers 0.21: Fix time reset after a day of week change (#1676) -0.22: Refactor some methods to scheduling library \ No newline at end of file +0.22: Refactor some methods to scheduling library +0.23: Fix regression with Days of Week (#1735) +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 diff --git a/apps/alarm/README.md b/apps/alarm/README.md index 42131a5a6..e979dbaf1 100644 --- a/apps/alarm/README.md +++ b/apps/alarm/README.md @@ -1,6 +1,7 @@ -Default Alarm & Timer -====================== +Alarms & Timers +=============== -This allows you to add/modify any running timers. +This app allows you to add/modify any alarms and timers. -It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps. +It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) +to handle the alarm scheduling in an efficient way that can work alongside other apps. diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 15a4c3774..3b3421115 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -52,6 +52,17 @@ function showMainMenu() { } }; }); + + if (alarms.some(e => !e.on)) { + menu[/*LANG*/"Enable All"] = () => enableAll(true); + } + if (alarms.some(e => e.on)) { + menu[/*LANG*/"Disable All"] = () => enableAll(false); + } + if (alarms.length > 0) { + menu[/*LANG*/"Delete All"] = () => deleteAll(); + } + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); return E.showMenu(menu); } @@ -81,7 +92,10 @@ function editAlarm(alarmIndex, alarm) { const menu = { '': { 'title': /*LANG*/'Alarm' }, - /*LANG*/'< Back' : () => showMainMenu(), + /*LANG*/'< Back': () => { + saveAlarm(newAlarm, alarmIndex, a, t); + showMainMenu(); + }, /*LANG*/'Hours': { value: t.hrs, min : 0, max : 23, wrap : true, onchange: v => t.hrs=v @@ -104,7 +118,7 @@ function editAlarm(alarmIndex, alarm) { value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d => { a.dow = d; - a.t = encodeTime(t); + a.t = require("sched").encodeTime(t); editAlarm(alarmIndex, a); }) }, @@ -115,24 +129,33 @@ function editAlarm(alarmIndex, alarm) { onchange: v => a.as = v } }; - menu[/*LANG*/"Save"] = function() { - a.t = require("sched").encodeTime(t); - a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0; - if (newAlarm) alarms.push(a); - else alarms[alarmIndex] = a; - saveAndReload(); - showMainMenu(); - }; + + menu[/*LANG*/"Cancel"] = () => showMainMenu(); + if (!newAlarm) { - menu[/*LANG*/"Delete"] = function() { - alarms.splice(alarmIndex,1); + menu[/*LANG*/"Delete"] = function () { + alarms.splice(alarmIndex, 1); saveAndReload(); showMainMenu(); }; } + return E.showMenu(menu); } +function saveAlarm(newAlarm, alarmIndex, a, t) { + a.t = require("sched").encodeTime(t); + a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0; + + if (newAlarm) { + alarms.push(a); + } else { + alarms[alarmIndex] = a; + } + + saveAndReload(); +} + function editTimer(alarmIndex, alarm) { let newAlarm = alarmIndex < 0; let a = require("sched").newDefaultTimer(); @@ -142,7 +165,10 @@ function editTimer(alarmIndex, alarm) { const menu = { '': { 'title': /*LANG*/'Timer' }, - /*LANG*/'< Back' : () => showMainMenu(), + /*LANG*/'< Back': () => { + saveTimer(newAlarm, alarmIndex, a, t); + showMainMenu(); + }, /*LANG*/'Hours': { value: t.hrs, min : 0, max : 23, wrap : true, onchange: v => t.hrs=v @@ -158,15 +184,9 @@ function editTimer(alarmIndex, alarm) { }, /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), }; - menu[/*LANG*/"Save"] = function() { - a.timer = require("sched").encodeTime(t); - a.t = getCurrentTime() + a.timer; - a.last = 0; - if (newAlarm) alarms.push(a); - else alarms[alarmIndex] = a; - saveAndReload(); - showMainMenu(); - }; + + menu[/*LANG*/"Cancel"] = () => showMainMenu(); + if (!newAlarm) { menu[/*LANG*/"Delete"] = function() { alarms.splice(alarmIndex,1); @@ -177,4 +197,44 @@ function editTimer(alarmIndex, alarm) { return E.showMenu(menu); } +function saveTimer(newAlarm, alarmIndex, a, t) { + a.timer = require("sched").encodeTime(t); + a.t = getCurrentTime() + a.timer; + a.last = 0; + + if (newAlarm) { + alarms.push(a); + } else { + alarms[alarmIndex] = a; + } + + saveAndReload(); +} + +function enableAll(on) { + E.showPrompt(/*LANG*/"Are you sure?", { + title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" + }).then((confirm) => { + if (confirm) { + alarms.forEach(alarm => alarm.on = on); + saveAndReload(); + } + + showMainMenu(); + }); +} + +function deleteAll() { + E.showPrompt(/*LANG*/"Are you sure?", { + title: /*LANG*/"Delete All" + }).then((confirm) => { + if (confirm) { + alarms = []; + saveAndReload(); + } + + showMainMenu(); + }); +} + showMainMenu(); diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 906df810f..2084c2a30 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.22", + "version": "0.24", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 59cb23a46..96b50c3a0 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -6,3 +6,4 @@ 0.05: Fix handling of message actions 0.06: Option to keep messages after a disconnect (default false) (fix #1186) 0.07: Include charging state in battery updates to phone +0.08: Handling of alarms diff --git a/apps/android/README.md b/apps/android/README.md index c10718aac..580eeec9a 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -21,6 +21,7 @@ of Gadgetbridge - making your phone make noise so you can find it. * `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js keep any messages it has received, or should it delete them? * `Messages` - launches the messages app, showing a list of messages +* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze ## How it works diff --git a/apps/android/boot.js b/apps/android/boot.js index eb3d26c6e..9e24c9893 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -5,6 +5,11 @@ } var settings = require("Storage").readJSON("android.settings.json",1)||{}; + //default alarm settings + if (settings.rp == undefined) settings.rp = true; + if (settings.as == undefined) settings.as = true; + if (settings.vibrate == undefined) settings.vibrate = ".."; + require('Storage').writeJSON("android.settings.json", settings); var _GB = global.GB; global.GB = (event) => { // feed a copy to other handlers if there were any @@ -44,6 +49,40 @@ title:event.name||"Call", body:"Incoming call\n"+event.number}); require("messages").pushMessage(event); }, + "alarm" : function() { + //wipe existing GB alarms + var sched; + try { sched = require("sched"); } catch (e) {} + if (!sched) return; // alarms may not be installed + var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms"); + for (var i = 0; i < gbalarms.length; i++) + sched.setAlarm(gbalarms[i].id, undefined); + var alarms = sched.getAlarms(); + var time = new Date(); + var currentTime = time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000; + for (var j = 0; j < event.d.length; j++) { + // prevents all alarms from going off at once?? + var dow = event.d[j].rep; + if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW + var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0; + var a = { + id : "gb"+j, + appid : "gbalarms", + on : true, + t : event.d[j].h * 3600000 + event.d[j].m * 60000, + dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format + last : last, + rp : settings.rp, + as : settings.as, + vibrate : settings.vibrate + }; + alarms.push(a); + } + sched.setAlarms(alarms); + sched.reload(); + }, }; var h = HANDLERS[event.t]; if (h) h(); else console.log("GB Unknown",event); diff --git a/apps/android/metadata.json b/apps/android/metadata.json index d126b869a..203cd18b1 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.07", + "version": "0.08", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/android/settings.js b/apps/android/settings.js index 7c46a1fc0..9f72947ab 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -10,8 +10,8 @@ "" : { "title" : "Android" }, "< Back" : back, /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, - "Find Phone" : () => E.showMenu({ - "" : { "title" : "Find Phone" }, + /*LANG*/"Find Phone" : () => E.showMenu({ + "" : { "title" : /*LANG*/"Find Phone" }, "< Back" : ()=>E.showMenu(mainmenu), /*LANG*/"On" : _=>gb({t:"findPhone",n:true}), /*LANG*/"Off" : _=>gb({t:"findPhone",n:false}), @@ -24,7 +24,28 @@ updateSettings(); } }, - /*LANG*/"Messages" : ()=>load("messages.app.js") + /*LANG*/"Messages" : ()=>load("messages.app.js"), + /*LANG*/"Alarms" : () => E.showMenu({ + "" : { "title" : /*LANG*/"Alarms" }, + "< Back" : ()=>E.showMenu(mainmenu), + /*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}), + /*LANG*/"Repeat": { + value: settings.rp, + format : v=>v?/*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + settings.rp = v; + updateSettings(); + } + }, + /*LANG*/"Auto snooze": { + value: settings.as, + format : v=>v?/*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + settings.as = v; + updateSettings(); + } + }, + }) }; E.showMenu(mainmenu); }) diff --git a/apps/btmultimeter/ChangeLog b/apps/btmultimeter/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/btmultimeter/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/btmultimeter/README.md b/apps/btmultimeter/README.md new file mode 100644 index 000000000..80fcdcf50 --- /dev/null +++ b/apps/btmultimeter/README.md @@ -0,0 +1,32 @@ +# Bluetooth Multimeter + +Connect to compatible a Bluetooth Multimeters and display the result on your wrist! + +## Compatible Bluetooth meters + +Only the OWON is supported right now - feel free to add support for more! + +### OWON OW18E + +Available [on Amazon](https://www.amazon.co.uk/Bluetooth-Multimeter-Multimeters-Voltmeter-Resistance/dp/B08NJT38SF/ref=sr_1_1) + +Turn the meter on, and long-press the Hz/Duty/Delta/Bluetooth button on the right hand side. Now run the app. + +## Usage + +The app currently only displays the current reading from the volt meter. + +If the app fails to connect you'll need to reload it to reconnect. + +To exit the app, long-press the button. + + +## Future functionality... + +* Logging +* Graphs +* More than one meter + +## Creator + +Gordon Williams (please file issues via GitHub) diff --git a/apps/btmultimeter/app-icon.js b/apps/btmultimeter/app-icon.js new file mode 100644 index 000000000..815929fd1 --- /dev/null +++ b/apps/btmultimeter/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4kA///z3vy067fWlP7/t1r3f33vrU07tdxHKn1mnUEv92DgO75xUwmcziIAIiAWJgYXLiIuLC5dwC6xIJCwP/swAHsIXM5GZAA/HC4kCC42I7oAG3OXC4sgC4/e9v+939AQP7C40iC429C4Pu/wCC9YXHGAYXCzoXC3wCCC5AwDC4WZC4M+909AQM7C5AwCC4ef/tfn/dAQQXIDAQXDAA4XHJIYXBzNVAA9XC4K8CR4QwCC4Mzi93AA1xC4JBCC4QwCC4URABIXBCgaqDC5MWC4YRCI4hfC1VEmMX93u8MR/Pd6J3CCQJ3DR4YXF84XBR4ilDbAYXFagM3iP1R4YQBCwbXER4KHBRYaWBR4TWFYoQXCsKPKCIQuEC452BC6MxiPju8xiYZEI5gXBiLDHO5k+FgKSBCYM+C4inJAAMWmlEolDi8TC4jXJAAQWBAANBUoIXCCQQCBDgQXCn0z+gXDokRjwXCCIQXCDoQuHAAJlBC4ZBBC4kiC4ekC4lBI4ioDYQYXD1QXJXIR3DL4fjn4XBGIdBbQQXFYAYvE0gXEF5DXGC4IYBC4WhC5IFCC4dKC4QDB+KPDC4guCX4n6GAIXCmK/CuAXEAgQvEPAIXC14JDmAXDFwYXEeAKpCBAgXECwYXFeQVDC5AAFC400AwoXQAAwXJgYXWGBoWJAF4A==")) diff --git a/apps/btmultimeter/app.js b/apps/btmultimeter/app.js new file mode 100644 index 000000000..11bcca9fb --- /dev/null +++ b/apps/btmultimeter/app.js @@ -0,0 +1,105 @@ +var decoded; +var gatt; + + +function decode(d) { + var value = d.getUint16(4,1); + if (value&32768) + value = -(value&32767); + var flags = d.getUint8(0); + var flags2 = d.getUint8(1); + // mv dc 27,240 "11xxx" + // mv ac 95,240 "1011xxx" + // v dc 36,240 "100xxx" 36(2dp) 35(20dp) + // v ac 100,240 "1100xxx" 100(2dp) 99(20dp) 97(2000dp) + // ohms 55,241 "110xxx" + // beep 231,242 "11100xxx" + // diode 167,242 "10100xxx" + // capac 76,241 "1001xxx" + // hz 162,241 "10100xxx" + // temp 33,242 "100xxx" + // ncv 96,243 "1100xxx" + // uA 146,240 "10010xxx" + // ma 155,240 "10011xxx" + // A 163,240 "10100xxx" + var dp = flags&7; + var range = (flags>>3)&7; + value *= Math.pow(10, -dp); + var isAC = !!(flags&64); + var mode = "?", units = ""; + if (flags2==240) { + if (flags&128) { + mode = "current"; + units = ["","nA","uA","mA","A","kA","MA",""][range]; + } else { + mode = "voltage"; + units = ["","nV","uV","mV","V","kV","MV",""][range] + " " + (isAC?"AC":"DC"); + } + } else if (flags2==241) { + if (isAC) { + mode = "capacitance"; + units = ["","nF","uF","mF","F","kF","MF",""][range]; + } else if (flags&128) { + mode = "frequency"; + units = "Hz"; + } else { + mode = "resistance"; + units = ["","nOhm","uOhm","mOhm","Ohm","kOhm","MOhm",""][range]; + } + } else if (flags2==242) { + if (flags&128) mode = isAC ? "continuity" : "diode"; + else { + mode = "temperature"; + units = isAC ? "F" : "C"; + } + } else if (flags2==243) mode = "ncv"; + //console.log(mode+" "+value+" "+units,new Uint8Array(d.buffer).slice()); + decoded = { + value : value, + mode : mode, // current/voltage/capacitance/frequency/resistance/temperature + units : units, // eg 'mA' + raw : new Uint8Array(d.buffer).slice(), + }; + updateDisplay(decoded); +} + +function updateDisplay(d) { + var mode = d.mode; + mode = mode.substr(0,1).toUpperCase()+mode.substr(1); + var s = d.value.toString(); + + var R = Bangle.appRect; + g.reset().clearRect(R); + g.setFont("12x20").setFontAlign(-1,-1).drawString(mode, R.x, R.y); + g.setFont("12x20").setFontAlign(1,1).drawString(d.units, R.x+R.w-1, R.y+R.h-1); + var fontSize = 80; + g.setFont("Vector",fontSize).setFontAlign(0,0); + while (g.stringWidth(s) > R.w-20) { + fontSize -= 2; + g.setFont("Vector", fontSize); + } + g.drawString(s, R.x+R.w/2, R.y+R.h/2); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +E.showMessage(/*LANG*/"Connecting..."); + +NRF.requestDevice({ filters: [{ name: 'BDM' }] }).then(function(device) { + return device.gatt.connect(); +}).then(function(g) { + gatt = g; + return gatt.getPrimaryService(0xFFF0); +}).then(function(service) { + return service.getCharacteristic(0xFFF4); +}).then(function(c) { + c.on('characteristicvaluechanged', function(event) { + d = event.target.value; + decode(d); + }); + return c.startNotifications(); +}).then(function() { + E.showMessage(/*LANG*/"Connected."); +}).catch(function(e) { + E.showMessage(e.toString()); +}); diff --git a/apps/btmultimeter/app.png b/apps/btmultimeter/app.png new file mode 100644 index 000000000..e9e75c76e Binary files /dev/null and b/apps/btmultimeter/app.png differ diff --git a/apps/btmultimeter/metadata.json b/apps/btmultimeter/metadata.json new file mode 100644 index 000000000..3a9a72063 --- /dev/null +++ b/apps/btmultimeter/metadata.json @@ -0,0 +1,15 @@ +{ "id": "btmultimeter", + "name": "Bluetooth Multimeter", + "shortName":"BT Meter", + "version":"0.01", + "description": "Connect to compatible a Bluetooth Multimeters and display the result on your wrist!", + "icon": "app.png", + "tags": "bluetooth,tool", + "screenshots" : [ { "url":"screenshot.png" } ], + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"btmultimeter.app.js","url":"app.js"}, + {"name":"btmultimeter.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/btmultimeter/screenshot.png b/apps/btmultimeter/screenshot.png new file mode 100644 index 000000000..dfd64eabf Binary files /dev/null and b/apps/btmultimeter/screenshot.png differ diff --git a/apps/clockcal/ChangeLog b/apps/clockcal/ChangeLog index f73c6ed97..8b40a87ac 100644 --- a/apps/clockcal/ChangeLog +++ b/apps/clockcal/ChangeLog @@ -1,3 +1,3 @@ 0.01: Initial upload -0.2: Added scrollable calendar and swipe gestures -0.3: Configurable drag gestures +0.02: Added scrollable calendar and swipe gestures +0.03: Configurable drag gestures diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json index dde32f746..3998215d7 100644 --- a/apps/clockcal/metadata.json +++ b/apps/clockcal/metadata.json @@ -1,7 +1,7 @@ { "id": "clockcal", "name": "Clock & Calendar", - "version": "0.3", + "version": "0.03", "description": "Clock with Calendar", "readme":"README.md", "icon": "app.png", diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog new file mode 100644 index 000000000..48a1ffb03 --- /dev/null +++ b/apps/dragboard/ChangeLog @@ -0,0 +1,5 @@ +0.01: New App! +0.02: Added some missing code. +0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights. +0.04: Now displays the opened text string at launch. +0.05: Now scrolls text when string gets longer than screen width. diff --git a/apps/dragboard/README.md b/apps/dragboard/README.md new file mode 100644 index 000000000..8960e5749 --- /dev/null +++ b/apps/dragboard/README.md @@ -0,0 +1,16 @@ +Swipe along the red field and release to select a letter. + +Do the same for green field to select number or punctuation. + +Release on left or right part of black field for backspace or space. + +Swiping between the different fields is possible! + +The drag in Dragboard is a nod to the javascript 'drag' event, which is used to select the characters. Also, you can't help but feel somewhat glamorous and risque when this is your keyboard! + +Known bugs: +- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes. +- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case. + +To do: +- Possibly provide a dragboard.settings.js file diff --git a/apps/dragboard/app.png b/apps/dragboard/app.png new file mode 100644 index 000000000..26e751896 Binary files /dev/null and b/apps/dragboard/app.png differ diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js new file mode 100644 index 000000000..b9b19f982 --- /dev/null +++ b/apps/dragboard/lib.js @@ -0,0 +1,261 @@ +//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes +//Bangle.setLCDTimeout(30); +//Bangle.setLCDPower(1); + +exports.input = function(options) { + options = options||{}; + var text = options.text; + if ("string"!=typeof text) text=""; + + var BGCOLOR = g.theme.bg; + var HLCOLOR = g.theme.fg; + var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; + var NUMCOLOR = g.toColor(0,1,0);//'#00FF00'; + var BIGFONT = '6x8:3'; + var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1))); + var SMALLFONT = '6x8:1'; + var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1))); + + var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); + var ABCPADDING = (g.getWidth()-6*ABC.length)/2; + + var NUM = ' 1234567890!?,.- '; + var NUMHIDDEN = ' 1234567890!?,.- '; + var NUMPADDING = (g.getWidth()-6*NUM.length)/2; + + var rectHeight = 40; + + + var delSpaceLast; + + function drawAbcRow() { + g.clear(); + g.setFont(SMALLFONT); + g.setColor(ABCCOLOR); + g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight()); + } + + function drawNumRow() { + g.setFont(SMALLFONT); + g.setColor(NUMCOLOR); + g.drawString(NUM, NUMPADDING, g.getHeight()/4); + + g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3); + } + + function updateTopString() { + "ram" + g.setColor(BGCOLOR); + g.fillRect(0,4+20,176,13+20); + g.setColor(0.2,0,0); + var rectLen = text.length<27? text.length*6:27*6; + g.fillRect(3,4+20,5+rectLen,13+20); + g.setColor(0.7,0,0); + g.fillRect(rectLen+5,4+20,rectLen+10,13+20); + g.setColor(1,1,1); + g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20); + } + + drawAbcRow(); + drawNumRow(); + updateTopString(); + + var abcHL; + var abcHLPrev = -10; + var numHL; + var numHLPrev = -10; + var type = ''; + var typePrev = ''; + var largeCharOffset = 6; + + function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) { + "ram" + // Small character in list + g.setColor(rowColor); + g.setFont(SMALLFONT); + g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor); + // Large character + g.setColor(BGCOLOR); + g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); + //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. + // mark in the list + } + function showChars(char, HL, typePadding, heightDivisor) { + "ram" + // mark in the list + g.setColor(HLCOLOR); + g.setFont(SMALLFONT); + if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor); + // show new large character + g.setFont(BIGFONT); + g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3); + g.setFont(SMALLFONT); + } + + function changeCase(abcHL) { + g.setColor(BGCOLOR); + g.drawString(ABC, ABCPADDING, g.getHeight()/2); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase(); + else ABC = ABC.toUpperCase(); + g.setColor(ABCCOLOR); + g.drawString(ABC, ABCPADDING, g.getHeight()/2); + } + return new Promise((resolve,reject) => { + // Interpret touch input + Bangle.setUI({ + mode: 'custom', + back: ()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + resolve(text); + }, + drag: function(event) { + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // Choose character by draging along red rectangle at bottom of screen + if (event.y >= ( g.getHeight() - 12 )) { + // Translate x-position to character + if (event.x < ABCPADDING) { abcHL = 0; } + else if (event.x >= 176-ABCPADDING) { abcHL = 25; } + else { abcHL = Math.floor((event.x-ABCPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + if (abcHL != abcHLPrev) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); + } + // Print string at top of screen + if (event.b == 0) { + text = text + ABC.charAt(abcHL); + updateTopString(); + + // Autoswitching letter case + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); + } + // Update previous character to current one + abcHLPrev = abcHL; + typePrev = 'abc'; + } + + + + + + + + + + // 12345678901234567890 + // Choose number or puctuation by draging on green rectangle + else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) { + // Translate x-position to character + if (event.x < NUMPADDING) { numHL = 0; } + else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } + else { numHL = Math.floor((event.x-NUMPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + if (numHL != numHLPrev) { + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); + } + // Print string at top of screen + if (event.b == 0) { + g.setColor(HLCOLOR); + // Backspace if releasing before list of numbers/punctuation + if (event.x < NUMPADDING) { + // show delete sign + showChars('del', 0, g.getWidth()/2 +6 -27 , 4); + delSpaceLast = 1; + text = text.slice(0, -1); + updateTopString(); + //print(text); + } + // Append space if releasing after list of numbers/punctuation + else if (event.x > g.getWidth()-NUMPADDING) { + //show space sign + showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + text = text + ' '; + updateTopString(); + //print(text); + } + // Append selected number/punctuation + else { + text = text + NUMHIDDEN.charAt(numHL); + updateTopString(); + + // Autoswitching letter case + if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + } + } + // Update previous character to current one + numHLPrev = numHL; + typePrev = 'num'; + } + + + + + + + + + // Make a space or backspace by swiping right or left on screen above green rectangle + else if (event.y > 20+4) { + if (event.b == 0) { + g.setColor(HLCOLOR); + if (event.x < g.getWidth()/2) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + // show delete sign + showChars('del', 0, g.getWidth()/2 +6 -27 , 4); + delSpaceLast = 1; + + // Backspace and draw string upper right corner + text = text.slice(0, -1); + updateTopString(); + if (text.length==0) changeCase(abcHL); + //print(text, 'undid'); + } + else { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + //show space sign + showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + + // Append space and draw string upper right corner + text = text + NUMHIDDEN.charAt(0); + updateTopString(); + //print(text, 'made space'); + } + } + } + } + }); +}); +/* return new Promise((resolve,reject) => { + Bangle.setUI({mode:"custom", back:()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + Bangle.setUI(); + resolve(text); + }}); + }); */ + +}; diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json new file mode 100644 index 000000000..f9c73ddde --- /dev/null +++ b/apps/dragboard/metadata.json @@ -0,0 +1,14 @@ +{ "id": "dragboard", + "name": "Dragboard", + "version":"0.05", + "description": "A library for text input via swiping keyboard", + "icon": "app.png", + "type":"textinput", + "tags": "keyboard", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"textinput","url":"lib.js"} + ] +} diff --git a/apps/dragboard/screenshot.png b/apps/dragboard/screenshot.png new file mode 100644 index 000000000..dbe27f408 Binary files /dev/null and b/apps/dragboard/screenshot.png differ diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 66fda2f29..95952b9fe 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -10,3 +10,4 @@ 0.10: added "one click exit" setting for Bangle 2 0.11: Fix bangle.js 1 white icons not displaying 0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock. +0.13: Added swipeExit setting so that left-right to exit is an option diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 7202e4f33..8466a7414 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -6,7 +6,8 @@ var settings = Object.assign({ showClocks: true, showLaunchers: true, direct: false, - oneClickExit:false + oneClickExit:false, + swipeExit: false }, require('Storage').readJSON("dtlaunch.json", true) || {}); if( settings.oneClickExit) @@ -88,14 +89,14 @@ function drawPage(p){ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ selected = 0; oldselected=-1; - if (dirUpDown==-1){ + if(settings.swipeExit && dirLeftRight==1) showClock(); + if (dirUpDown==-1||dirLeftRight==-1){ ++page; if (page>maxPage) page=0; drawPage(page); - } else if (dirUpDown==1){ + } else if (dirUpDown==1||dirLeftRight==1){ --page; if (page<0) page=maxPage; drawPage(page); } - if (dirLeftRight==1) showClock(); }); function showClock(){ diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index a5391cef0..7784972ca 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.12", + "version": "0.13", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js index 8eca46a7e..7ead63be0 100644 --- a/apps/dtlaunch/settings-b2.js +++ b/apps/dtlaunch/settings-b2.js @@ -5,7 +5,8 @@ showClocks: true, showLaunchers: true, direct: false, - oneClickExit:false + oneClickExit:false, + swipeExit: false }, require('Storage').readJSON(FILE, true) || {}); function writeSettings() { @@ -39,6 +40,14 @@ writeSettings(); } }, + 'Swipe Exit': { + value: settings.swipeExit, + format: v => v?"On":"Off", + onchange: v => { + settings.swipeExit = v; + writeSettings(); + } + }, 'One click exit': { value: settings.oneClickExit, format: v => v?"On":"Off", diff --git a/apps/flipper/ChangeLog b/apps/flipper/ChangeLog index 9db0e26c5..2e94a2286 100644 --- a/apps/flipper/ChangeLog +++ b/apps/flipper/ChangeLog @@ -1 +1,2 @@ 0.01: first release +0.02: updated dark theme bg2 color value diff --git a/apps/flipper/flipper.app.js b/apps/flipper/app.js similarity index 95% rename from apps/flipper/flipper.app.js rename to apps/flipper/app.js index 7171306b1..ad5aa383c 100644 --- a/apps/flipper/flipper.app.js +++ b/apps/flipper/app.js @@ -18,7 +18,7 @@ function flipTheme() { if (!g.theme.dark) { upd({ fg:cl("#fff"), bg:cl("#000"), - fg2:cl("#0ff"), bg2:cl("#000"), + fg2:cl("#fff"), bg2:cl("#004"), fgH:cl("#fff"), bgH:cl("#00f"), dark:true }); diff --git a/apps/flipper/flipper.png b/apps/flipper/app.png similarity index 100% rename from apps/flipper/flipper.png rename to apps/flipper/app.png diff --git a/apps/flipper/flipper.icon.js b/apps/flipper/icon.js similarity index 100% rename from apps/flipper/flipper.icon.js rename to apps/flipper/icon.js diff --git a/apps/flipper/metadata.json b/apps/flipper/metadata.json index aac4f1643..366145d95 100644 --- a/apps/flipper/metadata.json +++ b/apps/flipper/metadata.json @@ -2,17 +2,17 @@ { "id": "flipper", "name": "flipper", - "version": "0.01", + "version": "0.02", "description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.", "readme":"README.md", - "screenshots": [{"url":"flipper.png"}], - "icon": "flipper.png", + "screenshots": [{"url":"app.png"}], + "icon": "app.png", "type": "app", - "tags": "game", + "tags": "tool", "supports": ["BANGLEJS2"], "allow_emulator": true, "storage": [ - {"name":"flipper.app.js","url":"flipper.app.js"}, - {"name":"flipper.img","url":"flipper.icon.js","evaluate":true} + {"name":"flipper.app.js","url":"app.js"}, + {"name":"flipper.img","url":"icon.js","evaluate":true} ] } diff --git a/apps/fuzzyw/ChangeLog b/apps/fuzzyw/ChangeLog index 7b83706bf..0e5b66949 100644 --- a/apps/fuzzyw/ChangeLog +++ b/apps/fuzzyw/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Move translations to locale module (removed watch settings, now pick language in Bangle App Loader, More..., Settings) diff --git a/apps/fuzzyw/README.md b/apps/fuzzyw/README.md index 906eb167b..49d0fe0d5 100644 --- a/apps/fuzzyw/README.md +++ b/apps/fuzzyw/README.md @@ -4,12 +4,11 @@ An imprecise clock for when you're not in a rush. This clock is a remake of one of my favourite Pebble watchfaces, Fuzzy Text International. I use this watch for weekends and holidays, when 'within 5 minutes of the actual time' is close enough! -By default it will use the language set on the watch, go to settings to pick: -* en_GB - English -* en_US - American +Translations are supported to get the time in the language of your choice! To choose language, in the Bangle App Loader, navigate to the 'More...' tab and pick language under 'Settings'. Currently supported languages are below, but if you want to contribution a translation please feel free!: +* en_GB - English (Default) * es_ES - Spanish * fr_FR - French -* no_NO - Norwegian +* nn_NO - Norwegian Nynorsk (thank you zerodogg) * sv_SE - Swedish * de_DE - German diff --git a/apps/fuzzyw/fuzzy_strings.json b/apps/fuzzyw/fuzzy_strings.json deleted file mode 100644 index 55f443813..000000000 --- a/apps/fuzzyw/fuzzy_strings.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "en_GB":{ - "hours":[ - "midnight", "one", "two", "three", "four", "five", - "six", "seven", "eight", "nine", "ten", "eleven", - "twelve", "one", "two", "three", "four", "five", - "six", "seven", "eight", "nine", "ten", "eleven" - ], - "minutes":[ - "*$1 o'clock", - "five past *$1", - "ten past *$1", - "quarter past *$1", - "twenty past *$1", - "twenty five past *$1", - "half past *$1", - "twenty five to *$2", - "twenty to *$2", - "quarter to *$2", - "ten to *$2", - "five to *$2" - ], - "text_scale":3.5 - }, - "en_US":{ - "hours":[ - "midnight", "one", "two", "three", "four", "five", - "six", "seven", "eight", "nine", "ten", "eleven", - "twelve", "one", "two", "three", "four", "five", - "six", "seven", "eight", "nine", "ten", "eleven" - ], - "minutes":[ - "*$1 o'clock", - "five after *$1", - "ten after *$1", - "quarter after *$1", - "twenty after *$1", - "twenty five after *$1", - "half past *$1", - "twenty five to *$2", - "twenty to *$2", - "quarter to *$2", - "ten to *$2", - "five to *$2" - ], - "text_scale":3.5 - }, - "es_ES":{ - "hours":[ - "doce", "una", "dos", "tres", "cuatro", "cinco", - "seis", "siete", "ocho", "nueve", "diez", "once", - "doce", "una", "dos", "tres", "cuatro", "cinco", - "seis", "siete", "ocho", "nueve", "diez", "once" - ], - "minutes":[ - "*$1 en punto", - "*$1 y cinco", - "*$1 y diez", - "*$1 y cuarto", - "*$1 y veinte", - "*$1 y veinti- cinco", - "*$1 y media", - "*$2 menos veinti- cinco", - "*$2 menos veinte", - "*$2 menos cuarto", - "*$2 menos diez", - "*$2 menos cinco" - ], - "text_scale":3.5 - }, - "fr_FR":{ - "hours":[ - "douze", "une", "deux", "trois", "quatre", "cinq", - "six", "sept", "huit", "neuf", "dix", "onze", - "douze", "une", "deux", "trois", "quatre", "cinq", - "six", "sept", "huit", "neuf", "dix", "onze" - ], - "minutes":[ - "*$1 heures", - "*$1 heures cinq", - "*$1 heures dix", - "*$1 heures et quart", - "*$1 heures vingt", - "*$1 heures vingt- cinq", - "*$1 heures et demie", - "*$2 moins vingt- cinq", - "*$2 heures moins vingt", - "*$2 moins le quart", - "*$2 heures moins dix", - "*$2 heures moins cinq" - ], - "text_scale":3.5 - }, - "no_NB":{ - "hours":[ - "tolv", "ett", "to", "tre", "fire", "fem", - "seks", "sju", "åtte", "ni", "ti", "elleve", - "tolv", "ett", "to", "tre", "fire", "fem", - "seks", "sju", "åtte", "ni", "ti", "elleve" - ], - "minutes":[ - "klokka er *$1", - "fem over *$1", - "ti over *$1", - "kvart over *$1", - "ti på halv *$2", - "fem på halv *$2", - "halv *$2", - "fem over halv *$2", - "ti over halv *$2", - "kvart på *$2", - "ti på *$2", - "fem på *$2" - ], - "text_scale":3.5 - }, - "nn_NO":{ - "hours":[ - "tolv", "eitt", "to", "tre", "fire", "fem", - "seks", "sju", "åtte", "ni", "ti", "elleve", - "tolv", "eitt", "to", "tre", "fire", "fem", - "seks", "sju", "åtte", "ni", "ti", "elleve" - ], - "minutes":[ - "klokka er *$1", - "fem over *$1", - "ti over *$1", - "kvart over *$1", - "ti på halv *$2", - "fem på halv *$2", - "halv *$2", - "fem over halv *$2", - "ti over halv *$2", - "kvart på *$2", - "ti på *$2", - "fem på *$2" - ], - "text_scale":3.5 - }, - "sv_SE":{ - "hours":[ - "tolv", "ett", "två", "tre", "fyra", "fem", - "sex", "sju", "åtta", "nio", "tio", "elva", - "tolv", "ett", "två", "tre", "fyra", "fem", - "sex", "sju", "åtta", "nio", "tio", "elva" - ], - "minutes":[ - "*$1", - "fem över *$1", - "tio över *$1", - "kvart över *$1", - "tjugo över *$1", - "fem i halv *$2", - "halv *$2", - "fem över halv *$2", - "tjugo i *$2", - "kvart i *$2", - "tio i *$2", - "fem i *$2" - ], - "text_scale":3.5 - }, - "de_DE":{ - "hours":[ - "zwölf", "eins", "zwei", "drei", "vier", "fünf", - "sechs", "sieben", "acht", "neun", "zehn", "elf", - "zwölf", "eins", "zwei", "drei", "vier", "fünf", - "sechs", "sieben", "acht", "neun", "zehn", "elf" - ], - "minutes":[ - "*$1 uhr", - "fünf nach *$1", - "zehn nach *$1", - "viertel nach *$1", - "zwanzig nach *$1", - "fünf for halb *$2", - "halb *$2", - "fünf nach halb *$2", - "zwanzig vor *$2", - "viertel vor *$2", - "zehn vor *$2", - "fünf vor *$2" - ], - "text_scale":3.5 - } -} diff --git a/apps/fuzzyw/fuzzyw.app.js b/apps/fuzzyw/fuzzyw.app.js index 07b5c4068..debcb3c4d 100644 --- a/apps/fuzzyw/fuzzyw.app.js +++ b/apps/fuzzyw/fuzzyw.app.js @@ -1,15 +1,37 @@ // adapted from https://github.com/hallettj/Fuzzy-Text-International/ -const fuzzy_strings = require("Storage").readJSON("fuzzy_strings.json"); -const SETTINGS_FILE = "fuzzyw.settings.json"; -let settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'language': 'System', 'alignment':'Centre'}; - -if (settings.language == 'System') { - settings.language = require('locale').name; -} - -let fuzzy_string = fuzzy_strings[settings.language]; +let fuzzy_string = { + "hours":[ + /*LANG*/"twelve", + /*LANG*/"one", + /*LANG*/"two", + /*LANG*/"three", + /*LANG*/"four", + /*LANG*/"five", + /*LANG*/"six", + /*LANG*/"seven", + /*LANG*/"eight", + /*LANG*/"nine", + /*LANG*/"ten", + /*LANG*/"eleven" + ], + "minutes":[ + /*LANG*/"*$1 o'clock", + /*LANG*/"five past *$1", + /*LANG*/"ten past *$1", + /*LANG*/"quarter past *$1", + /*LANG*/"twenty past *$1", + /*LANG*/"twenty five past *$1", + /*LANG*/"half past *$1", + /*LANG*/"twenty five to *$2", + /*LANG*/"twenty to *$2", + /*LANG*/"quarter to *$2", + /*LANG*/"ten to *$2", + /*LANG*/"five to *$2" + ] +}; +let text_scale = 3.5; let timeout = 2.5*60; let drawTimeout; @@ -24,24 +46,15 @@ function queueDraw(seconds) { const h = g.getHeight(); const w = g.getWidth(); -let align_mode = 0; -let align_pos = w/2; -if (settings.alignment =='Left') { - align_mode = -1; - align_pos = 0; -} else if (settings.alignment == 'Right') { - align_mode = 1; - align_pos = w; -} function getTimeString(date) { let segment = Math.round((date.getMinutes()*60 + date.getSeconds() + 1)/300); let hour = date.getHours() + Math.floor(segment/12); f_string = fuzzy_string.minutes[segment % 12]; if (f_string.includes('$1')) { - f_string = f_string.replace('$1', fuzzy_string.hours[(hour) % 24]); + f_string = f_string.replace('$1', fuzzy_string.hours[(hour) % 12]); } else { - f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 24]); + f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 12]); } return f_string; } @@ -49,11 +62,11 @@ function getTimeString(date) { function draw() { let time_string = getTimeString(new Date()).replace('*', ''); // print(time_string); - g.setFont('Vector', (h-24*2)/fuzzy_string.text_scale); - g.setFontAlign(align_mode, 0); + g.setFont('Vector', (h-24*2)/text_scale); + g.setFontAlign(0, 0); g.clearRect(0, 24, w, h-24); g.setColor(g.theme.fg); - g.drawString(g.wrapString(time_string, w).join("\n"), align_pos, h/2); + g.drawString(g.wrapString(time_string, w).join("\n"), w/2, h/2); queueDraw(timeout); } diff --git a/apps/fuzzyw/fuzzyw.settings.js b/apps/fuzzyw/fuzzyw.settings.js deleted file mode 100644 index 00219accf..000000000 --- a/apps/fuzzyw/fuzzyw.settings.js +++ /dev/null @@ -1,46 +0,0 @@ -(function(back) { - const SETTINGS_FILE = "fuzzyw.settings.json"; - - var align_options = ['Left','Centre','Right']; - var language_options = ['System', 'en_GB', 'en_US', 'es_ES', 'fr_FR', 'no_NO', 'sv_SE', 'de_DE']; - - // initialize with default settings... - let s = {'language': 'System', 'alignment': 'Centre'}; - - // ...and overwrite them with any saved values - // This way saved values are preserved if a new version adds more settings - const storage = require('Storage') - let settings = storage.readJSON(SETTINGS_FILE, 1) || s; - const saved = settings || {} - for (const key in saved) { - s[key] = saved[key] - } - - function save() { - settings = s - storage.write(SETTINGS_FILE, settings) - } - - E.showMenu({ - '': { 'title': 'Fuzzy Text Clock' }, - '< Back': back, - 'Language': { - value: 0 | language_options.indexOf(s.language), - min: 0, max: language_options.length - 1, - format: v => language_options[v], - onchange: v => { - s.language = language_options[v]; - save(); - } - }, - 'Alignment': { - value: 0 | align_options.indexOf(s.alignment), - min: 0, max: align_options.length - 1, - format: v => align_options[v], - onchange: v => { - s.alignment = align_options[v]; - save(); - } - }, - }); -}) diff --git a/apps/fuzzyw/metadata.json b/apps/fuzzyw/metadata.json index ebd20e49f..5d3045edb 100644 --- a/apps/fuzzyw/metadata.json +++ b/apps/fuzzyw/metadata.json @@ -2,7 +2,7 @@ "id":"fuzzyw", "name":"Fuzzy Text Clock", "shortName": "Fuzzy Text", - "version": "0.01", + "version": "0.02", "description": "An imprecise clock for when you're not in a rush", "readme": "README.md", "icon":"fuzzyw.png", @@ -13,8 +13,6 @@ "allow_emulator": true, "storage": [ {"name":"fuzzyw.app.js","url":"fuzzyw.app.js"}, - {"name":"fuzzyw.settings.js","url":"fuzzyw.settings.js"}, - {"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true}, - {"name":"fuzzy_strings.json","url":"fuzzy_strings.json"} + {"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true} ] } diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 5252ac0ac..49f54dac9 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -86,7 +86,8 @@ function infoColor(name) { * @param l */ function rScroller(l) { - g.setFont("Vector", Math.round(g.getHeight()*l.fsz.slice(0, -1)/100)); + var size=l.font.split(":")[1].slice(0,-1); + g.setFont("Vector", Math.round(g.getHeight()*size/100)); const w = g.stringWidth(l.label)+40, y = l.y+l.h/2; l.offset = l.offset%w; diff --git a/apps/gpsrec/metadata.json b/apps/gpsrec/metadata.json index 8f4736066..c870157df 100644 --- a/apps/gpsrec/metadata.json +++ b/apps/gpsrec/metadata.json @@ -2,7 +2,7 @@ "id": "gpsrec", "name": "GPS Recorder", "version": "0.28", - "description": "Application that allows you to record a GPS track. Can run in background", + "description": "(NOT RECOMMENDED) - please use the more flexible 'Recorder' app instead. Application that allows you to record a GPS track. Can run in background", "icon": "app.png", "tags": "tool,outdoors,gps,widget", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 7dbb9c458..74be2faec 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -11,3 +11,5 @@ 0.10: Adds additional 3 minute setting for HRM 0.11: Pre-minified boot&lib - folds constants and saves RAM 0.12: Add setting for Daily Step Goal +0.13: Add support for internationalization +0.14: Move settings diff --git a/apps/health/app.js b/apps/health/app.js index e39590e2d..a1fe1a4f7 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -1,43 +1,13 @@ -function getSettings() { - return require("Storage").readJSON("health.json",1)||{}; -} - -function setSettings(healthSettings) { - require("Storage").writeJSON("health.json",healthSettings); -} - function menuMain() { swipe_enabled = false; clearButton(); E.showMenu({ - "":{title:"Health Tracking"}, - "< Back":()=>load(), - "Step Counting":()=>menuStepCount(), - "Movement":()=>menuMovement(), - "Heart Rate":()=>menuHRM(), - "Settings":()=>menuSettings() - }); -} - -function menuSettings() { - swipe_enabled = false; - clearButton(); - var healthSettings=getSettings(); - //print(healthSettings); - E.showMenu({ - "":{title:"Health Tracking"}, - "< Back":()=>menuMain(), - "Heart Rt":{ - value : 0|healthSettings.hrm, - min : 0, max : 3, - format : v=>["Off","3 mins","10 mins","Always"][v], - onchange : v => { healthSettings.hrm=v;setSettings(healthSettings); } - }, - "Daily Step Goal":{ - value : (healthSettings.stepGoal ? healthSettings.stepGoal : 10000), - min : 0, max : 20000, step : 100, - onchange : v => { healthSettings.stepGoal=v;setSettings(healthSettings); } - } + "": { title: /*LANG*/"Health Tracking" }, + /*LANG*/"< Back": () => load(), + /*LANG*/"Step Counting": () => menuStepCount(), + /*LANG*/"Movement": () => menuMovement(), + /*LANG*/"Heart Rate": () => menuHRM(), + /*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>menuMain()) }); } @@ -45,10 +15,10 @@ function menuStepCount() { swipe_enabled = false; clearButton(); E.showMenu({ - "":{title:"Step Counting"}, - "< Back":()=>menuMain(), - "per hour":()=>stepsPerHour(), - "per day":()=>stepsPerDay() + "": { title:/*LANG*/"Steps" }, + /*LANG*/"< Back": () => menuMain(), + /*LANG*/"per hour": () => stepsPerHour(), + /*LANG*/"per day": () => stepsPerDay() }); } @@ -56,10 +26,10 @@ function menuMovement() { swipe_enabled = false; clearButton(); E.showMenu({ - "":{title:"Movement"}, - "< Back":()=>menuMain(), - "per hour":()=>movementPerHour(), - "per day":()=>movementPerDay(), + "": { title:/*LANG*/"Movement" }, + /*LANG*/"< Back": () => menuMain(), + /*LANG*/"per hour": () => movementPerHour(), + /*LANG*/"per day": () => movementPerDay(), }); } @@ -67,17 +37,16 @@ function menuHRM() { swipe_enabled = false; clearButton(); E.showMenu({ - "":{title:"Heart Rate"}, - "< Back":()=>menuMain(), - "per hour":()=>hrmPerHour(), - "per day":()=>hrmPerDay(), + "": { title:/*LANG*/"Heart Rate" }, + /*LANG*/"< Back": () => menuMain(), + /*LANG*/"per hour": () => hrmPerHour(), + /*LANG*/"per day": () => hrmPerDay(), }); } - function stepsPerHour() { E.showMessage(/*LANG*/"Loading..."); - var data = new Uint16Array(24); + let data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); g.clear(1); Bangle.drawWidgets(); @@ -88,7 +57,7 @@ function stepsPerHour() { function stepsPerDay() { E.showMessage(/*LANG*/"Loading..."); - var data = new Uint16Array(31); + let data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); g.clear(1); Bangle.drawWidgets(); @@ -99,8 +68,8 @@ function stepsPerDay() { function hrmPerHour() { E.showMessage(/*LANG*/"Loading..."); - var data = new Uint16Array(24); - var cnt = new Uint8Array(23); + let data = new Uint16Array(24); + let cnt = new Uint8Array(23); require("health").readDay(new Date(), h=>{ data[h.hr]+=h.bpm; if (h.bpm) cnt[h.hr]++; @@ -115,8 +84,8 @@ function hrmPerHour() { function hrmPerDay() { E.showMessage(/*LANG*/"Loading..."); - var data = new Uint16Array(31); - var cnt = new Uint8Array(31); + let data = new Uint16Array(31); + let cnt = new Uint8Array(31); require("health").readDailySummaries(new Date(), h=>{ data[h.day]+=h.bpm; if (h.bpm) cnt[h.day]++; @@ -131,7 +100,7 @@ function hrmPerDay() { function movementPerHour() { E.showMessage(/*LANG*/"Loading..."); - var data = new Uint16Array(24); + let data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.movement); g.clear(1); Bangle.drawWidgets(); @@ -142,7 +111,7 @@ function movementPerHour() { function movementPerDay() { E.showMessage(/*LANG*/"Loading..."); - var data = new Uint16Array(31); + let data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); g.clear(1); Bangle.drawWidgets(); @@ -164,7 +133,7 @@ var chart_data; var swipe_enabled = false; var btn; -// find the max value in the array, using a loop due to array size +// find the max value in the array, using a loop due to array size function max(arr) { var m = -Infinity; @@ -176,10 +145,10 @@ function max(arr) { // find the end of the data, the array might be for 31 days but only have 2 days of data in it function get_data_length(arr) { var nlen = arr.length; - + for(var i = arr.length - 1; i > 0 && arr[i] == 0; i--) nlen--; - + return nlen; } @@ -198,10 +167,10 @@ function drawBarChart() { const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre var bar_top; var bar; - + g.setColor(g.theme.bg); g.fillRect(0,24,w,h); - + for (bar = 1; bar < 10; bar++) { if (bar == 5) { g.setFont('6x8', 2); @@ -214,7 +183,7 @@ function drawBarChart() { } // draw a fake 0 height bar if chart_index is outside the bounds of the array - if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len) + if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len) bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum; else bar_top = bar_bot; @@ -244,7 +213,7 @@ Bangle.on('swipe', dir => { function setButton(fn) { // cancel callback, otherwise a slight up down movement will show the E.showMenu() Bangle.setUI("updown", undefined); - + if (process.env.HWVERSION == 1) btn = setWatch(fn, BTN2); else @@ -260,4 +229,5 @@ function clearButton() { Bangle.loadWidgets(); Bangle.drawWidgets(); + menuMain(); diff --git a/apps/health/metadata.json b/apps/health/metadata.json index 5d096dc07..58762c77a 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -1,7 +1,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.12", + "version": "0.14", "description": "Logs health data and provides an app to view it", "icon": "app.png", "tags": "tool,system,health", @@ -12,6 +12,8 @@ {"name":"health.app.js","url":"app.js"}, {"name":"health.img","url":"app-icon.js","evaluate":true}, {"name":"health.boot.js","url":"boot.min.js"}, - {"name":"health","url":"lib.min.js"} - ] + {"name":"health","url":"lib.min.js"}, + {"name":"health.settings.js","url":"settings.js"} + ], + "data": [{"name":"health.json"}] } diff --git a/apps/health/settings.js b/apps/health/settings.js new file mode 100644 index 000000000..f94efbb16 --- /dev/null +++ b/apps/health/settings.js @@ -0,0 +1,43 @@ +(function (back) { + var settings = Object.assign({ + hrm: 0, + stepGoal: 10000 + }, require("Storage").readJSON("health.json", true) || {}); + + E.showMenu({ + "": { title: /*LANG*/"Health Tracking" }, + + /*LANG*/"< Back": () => back(), + + /*LANG*/"HRM Interval": { + value: settings.hrm, + min: 0, + max: 3, + format: v => [ + /*LANG*/"Off", + /*LANG*/"3 min", + /*LANG*/"10 min", + /*LANG*/"Always" + ][v], + onchange: v => { + settings.hrm = v; + setSettings(settings); + } + }, + + /*LANG*/"Daily Step Goal": { + value: settings.stepGoal, + min: 0, + max: 20000, + step: 250, + onchange: v => { + settings.stepGoal = v; + setSettings(settings); + } + } + }); + + function setSettings(settings) { + require("Storage").writeJSON("health.json", settings); + } +}) diff --git a/apps/heartzone/ChangeLog b/apps/heartzone/ChangeLog index cc2219306..2a37193a3 100644 --- a/apps/heartzone/ChangeLog +++ b/apps/heartzone/ChangeLog @@ -1 +1 @@ -1.0: Initial release. +0.01: Initial release. diff --git a/apps/heartzone/metadata.json b/apps/heartzone/metadata.json index e243063a2..7c385ce0d 100644 --- a/apps/heartzone/metadata.json +++ b/apps/heartzone/metadata.json @@ -1,7 +1,7 @@ { "id": "heartzone", "name": "HeartZone", - "version": "1.0", + "version": "0.01", "description": "Exercise app for keeping your heart rate in the aerobic zone. Buzzes the watch at configurable intervals when your heart rate is outside of configured limits.", "readme":"README.md", "screenshots": [ diff --git a/apps/hebrew_calendar/ChangeLog b/apps/hebrew_calendar/ChangeLog index fdd29db66..1fb04080d 100644 --- a/apps/hebrew_calendar/ChangeLog +++ b/apps/hebrew_calendar/ChangeLog @@ -1,4 +1,6 @@ 0.01: New App! 0.02: using TS and rollup to bundle 0.03: bug fixes and support bangle 1 -0.04: removing TS \ No newline at end of file +0.04: removing TS +0.05: major overhaul; now you customize your calendar based on your location for candle lighting times +0.06: bug fixes and improvements \ No newline at end of file diff --git a/apps/hebrew_calendar/HebrewCalendar-Screenshot.png b/apps/hebrew_calendar/HebrewCalendar-Screenshot.png new file mode 100644 index 000000000..f60314c99 Binary files /dev/null and b/apps/hebrew_calendar/HebrewCalendar-Screenshot.png differ diff --git a/apps/hebrew_calendar/README.md b/apps/hebrew_calendar/README.md index 7a96a97db..d4e6b837a 100644 --- a/apps/hebrew_calendar/README.md +++ b/apps/hebrew_calendar/README.md @@ -1,19 +1,22 @@ # Hebrew Calendar -Displays the current hebrew calendar date -Add screen shots (if possible) to the app folder and link then into this file with ![](.png) +Displays the current hebrew calendar date and upcoming holidays alongside a clock + +![](./HebrewCalendar-Screenshot.png) ## Usage -Open the app, and it shows a menu with the date components +Set it up as your clock in the settings ## Features -Shows the hebrew date, month, and year; alongside the gregorian date +- Shows the hebrew date, month, and year; alongside the gregorian date +- Shows when upcoming holidays start +- Shows the gregorian day of week, date, and current time ## Controls -Name the buttons and what they are used for +N/A ## Requests @@ -22,5 +25,5 @@ Michael Salaverry (github.com/barakplasma) ## Creator Michael Salaverry -with help from https://github.com/IonicaBizau/hebrew-date (MIT license) +with help from https://github.com/hebcal/hebcal-es6 (MIT license) which is used to calculate the calendar
Icons made by Smashicons from [www.flaticon.com](https://www.flaticon.com/premium-icon/calendar_3130060?term=jewish&page=1&position=10&page=1&position=10&related_id=3130060&origin=tag)
\ No newline at end of file diff --git a/apps/hebrew_calendar/app.js b/apps/hebrew_calendar/app.js index 399d124f3..3d098c60a 100644 --- a/apps/hebrew_calendar/app.js +++ b/apps/hebrew_calendar/app.js @@ -1,26 +1,181 @@ -g.clear(); +const dayInMS = 86400000; -let now = new Date(); +const DateProvider = { now: () => Date.now() }; -let today = require('hebrewDate').hebrewDate(now); +const Layout = require("Layout"); +const Locale = require("locale"); -var mainmenu = { - "": { - "title": "Hebrew Date" - }, - greg: { - // @ts-ignore - value: require('locale').date(now, 1), - }, - date: { - value: today.date, - }, - month: { - value: today.month_name, - }, - year: { - value: today.year, +let nextEndingEvent; + +function getCurrentEvents() { + const now = DateProvider.now(); + + const current = hebrewCalendar.filter( + (x) => x.startEvent <= now && x.endEvent >= now + ); + + nextEndingEvent = current.reduce((acc, ev) => { + return Math.min(acc, ev.endEvent); + }, Infinity); + + return current.map((event, i) => { + return { + type: "txt", + font: "12x20", + id: "currentEvents" + i, + label: event.desc, + pad: 2, + bgCol: g.theme.bg, + }; + }); +} + +function getUpcomingEvents() { + const now = DateProvider.now(); + + const futureEvents = hebrewCalendar.filter( + (x) => x.startEvent >= now && x.startEvent <= now + dayInMS + ); + + let warning; + let eventsLeft = hebrewCalendar.filter( + (x) => x.startEvent >= now && x.startEvent <= now + dayInMS * 14 + ).length; + + if (eventsLeft < 14) { + warning = { + startEvent: 0, + type: "txt", + font: "4x6", + id: "warning", + label: "only " + eventsLeft + " events left in calendar; update soon", + pad: 2, + bgCol: g.theme.bg, + }; } -}; -// @ts-ignore -E.showMenu(mainmenu); \ No newline at end of file + + return futureEvents + .slice(0, 2) + .map((event, i) => { + return { + startEvent: event.startEvent, + type: "txt", + font: "6x8", + id: "upcomingEvents" + 1, + label: event.desc + " at " + Locale.time(new Date(event.startEvent), 1), + pad: 2, + bgCol: g.theme.bg, + }; + }) + .concat(warning) + .sort(function (a, b) { + return a.startEvent - b.startEvent; + }); +} + +function dateTime() { + return ( + Locale.dow(new Date(), 1) + + " " + + Locale.date(new Date(), 1) + + " " + + Locale.time(new Date(), 1) + ); +} + +function makeLayout() { + return new Layout( + { + type: "v", + c: [ + { + type: "txt", + font: "6x8", + id: "title", + label: "-- Hebrew Calendar Events --", + pad: 2, + bgCol: g.theme.bg2, + }, + { + type: "txt", + font: "6x8", + id: "currently", + label: "Currently", + pad: 2, + bgCol: g.theme.bgH, + }, + ] + .concat(getCurrentEvents()) + .concat([ + { + type: "txt", + font: "6x8", + label: "Upcoming", + id: "upcoming", + pad: 2, + bgCol: g.theme.bgH, + }, + ]) + .concat(getUpcomingEvents()) + .concat([ + { + type: "txt", + font: "Vector14", + id: "time", + label: dateTime(), + pad: 2, + bgCol: undefined, + }, + ]), + }, + { lazy: true } + ); +} +let layout = makeLayout(); +// see also https://www.espruino.com/Bangle.js+Layout#updating-the-screen + +// timeout used to update every minute +let drawTimeout; + +function draw() { + layout.time.label = dateTime(); + layout.render(); + + // schedule a draw for the next minute + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, 60000 - (DateProvider.now() % 60000)); + console.log("updated time"); +} + +// update time and draw +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); + +function findNextEvent() { + return hebrewCalendar.find((ev) => { + return ev.startEvent > DateProvider.now(); + }); +} + +function updateCalendar() { + layout.clear(); + layout = makeLayout(); + layout.forgetLazyState(); + layout.render(); + + let nextChange = Math.min( + findNextEvent().startEvent - DateProvider.now() + 5000, + nextEndingEvent - DateProvider.now() + 5000 + ); + setTimeout(updateCalendar, nextChange); + console.log("updated events"); +} + +updateCalendar(); + +Bangle.setUI("clock"); \ No newline at end of file diff --git a/apps/hebrew_calendar/customizer.html b/apps/hebrew_calendar/customizer.html new file mode 100644 index 000000000..bea860e53 --- /dev/null +++ b/apps/hebrew_calendar/customizer.html @@ -0,0 +1,46 @@ + + + + + + + + Hebrew Calendar Customizer + + + + +
+
+
+
Hebrew Calendar Loader
+
+
+
Your location
+
+
+ + + + +
get your latitude and longitude from plus.codes or:
+ + + +
+
+
+ +
+ + +
+ + + \ No newline at end of file diff --git a/apps/hebrew_calendar/customizer.mjs b/apps/hebrew_calendar/customizer.mjs new file mode 100644 index 000000000..06716a63b --- /dev/null +++ b/apps/hebrew_calendar/customizer.mjs @@ -0,0 +1,329 @@ +import { + HebrewCalendar, + HDate, + Location, + Zmanim, +} from "https://cdn.skypack.dev/@hebcal/core@^3?min"; + +function onload(event) { + event.preventDefault(); + const latLon = getLatLonFromForm(); + const events = generateHebCal(latLon); + const calendar = serializeEvents(events); + console.debug(calendar); + globalThis["cal"] = calendar; + loadWatch(calendar); +} + +function loadWatch(json) { + sendCustomizedApp({ + id: "hebrew_calendar", + + storage: [ + { + name: "hebrew_calendar.app.js", + url: "app.js", + // content below is same as app.js except for the first line which customizes the hebrewCalendar object used + content: ` +let hebrewCalendar = ${json}; + +const dayInMS = 86400000; + +const DateProvider = { now: () => Date.now() }; + +const Layout = require("Layout"); +const Locale = require("locale"); + +let nextEndingEvent; + +function getCurrentEvents() { + const now = DateProvider.now(); + + const current = hebrewCalendar.filter( + (x) => x.startEvent <= now && x.endEvent >= now + ); + + nextEndingEvent = current.reduce((acc, ev) => { + return Math.min(acc, ev.endEvent); + }, Infinity); + + return current.map((event, i) => { + return { + type: "txt", + font: "12x20", + id: "currentEvents" + i, + label: event.desc, + pad: 2, + bgCol: g.theme.bg, + }; + }); +} + +function getUpcomingEvents() { + const now = DateProvider.now(); + + const futureEvents = hebrewCalendar.filter( + (x) => x.startEvent >= now && x.startEvent <= now + dayInMS + ); + + let warning; + let eventsLeft = hebrewCalendar.filter( + (x) => x.startEvent >= now && x.startEvent <= now + dayInMS * 14 + ).length; + + if (eventsLeft < 14) { + warning = { + startEvent: 0, + type: "txt", + font: "4x6", + id: "warning", + label: "only " + eventsLeft + " events left in calendar; update soon", + pad: 2, + bgCol: g.theme.bg, + }; + } + + return futureEvents + .slice(0, 2) + .map((event, i) => { + return { + startEvent: event.startEvent, + type: "txt", + font: "6x8", + id: "upcomingEvents" + 1, + label: event.desc + " at " + Locale.time(new Date(event.startEvent), 1), + pad: 2, + bgCol: g.theme.bg, + }; + }) + .concat(warning) + .sort(function (a, b) { + return a.startEvent - b.startEvent; + }); +} + +function dateTime() { + return ( + Locale.dow(new Date(), 1) + + " " + + Locale.date(new Date(), 1) + + " " + + Locale.time(new Date(), 1) + ); +} + +function makeLayout() { + return new Layout( + { + type: "v", + c: [ + { + type: "txt", + font: "6x8", + id: "title", + label: "-- Hebrew Calendar Events --", + pad: 2, + bgCol: g.theme.bg2, + }, + { + type: "txt", + font: "6x8", + id: "currently", + label: "Currently", + pad: 2, + bgCol: g.theme.bgH, + }, + ] + .concat(getCurrentEvents()) + .concat([ + { + type: "txt", + font: "6x8", + label: "Upcoming", + id: "upcoming", + pad: 2, + bgCol: g.theme.bgH, + }, + ]) + .concat(getUpcomingEvents()) + .concat([ + { + type: "txt", + font: "Vector14", + id: "time", + label: dateTime(), + pad: 2, + bgCol: undefined, + }, + ]), + }, + { lazy: true } + ); +} +let layout = makeLayout(); +// see also https://www.espruino.com/Bangle.js+Layout#updating-the-screen + +// timeout used to update every minute +let drawTimeout; + +function draw() { + layout.time.label = dateTime(); + layout.render(); + + // schedule a draw for the next minute + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, 60000 - (DateProvider.now() % 60000)); + console.log("updated time"); +} + +// update time and draw +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); + +function findNextEvent() { + return hebrewCalendar.find((ev) => { + return ev.startEvent > DateProvider.now(); + }); +} + +function updateCalendar() { + layout.clear(); + layout = makeLayout(); + layout.forgetLazyState(); + layout.render(); + + let nextChange = Math.min( + findNextEvent().startEvent - DateProvider.now() + 5000, + nextEndingEvent - DateProvider.now() + 5000 + ); + setTimeout(updateCalendar, nextChange); + console.log("updated events"); +} + +updateCalendar(); + +Bangle.setUI("clock"); + `, + }, + ], + }); +} + +document + .querySelector("button[type=submit]") + .addEventListener("click", onload, false); + +document.querySelector("#geoloc")?.addEventListener("click", (event) => { + event.preventDefault(); + navigator.geolocation.getCurrentPosition( + (pos) => { + const { + coords: { latitude, longitude }, + } = pos; + locationElements[0].value = latitude; + locationElements[1].value = longitude; + console.debug(pos); + }, + (err) => { + if (err.PERMISSION_DENIED) { + alert("permission required to use geolocation api; enter manually"); + } + if (err.POSITION_UNAVAILABLE) { + alert("position unavailable; enter manually"); + } + }, + { enableHighAccuracy: false } + ); +}); + +document.querySelector( + "#hDate" +).innerText = `Today is ${new Date().toLocaleDateString()} & ${new HDate().toString()}`; + +const locationElements = [ + document.querySelector("#lat"), + document.querySelector("#lon"), +]; + +function getLatLonFromForm() { + const latLon = locationElements.map((el) => el.value); + if (locationElements.every((x) => x.checkValidity())) { + return latLon; + } else { + console.debug("lat lon invalid error"); + return [0, 0]; + } +} + +function groupBy(arr, fn) { + return arr + .map(typeof fn === "function" ? fn : (val) => val[fn]) + .reduce((acc, val, i) => { + acc[val] = (acc[val] || []).concat(arr[i]); + return acc; + }, {}); +} + +function generateHebCal(latLon) { + const location = new Location( + ...latLon, + document.querySelector("#inIL").checked + ); + + const now = new Date(); + + const options = { + year: now.getFullYear(), + isHebrewYear: false, + candlelighting: true, + location, + addHebrewDates: true, + addHebrewDatesForEvents: true, + sedrot: true, + start: now, + end: new Date(now.getFullYear(), now.getMonth() + 3), + }; + + const events = HebrewCalendar.calendar(options).map((ev) => { + const { desc, eventTime, startEvent, endEvent } = ev; + + const zman = new Zmanim(ev.date, ...latLon.map(Number)); + + let output = { + desc, + startEvent: startEvent?.eventTime?.getTime() || zman.gregEve().getTime(), + endEvent: endEvent?.eventTime?.getTime() || zman.shkiah().getTime(), + }; + + if (eventTime) { + delete output.startEvent; + delete output.endEvent; + output.startEvent = eventTime.getTime(); + output.endEvent = eventTime.getTime() + 60000 * 15; + } + + return output; + }); + + // console.table(events) + + return events.sort((a, b) => { + return a.startEvent - b.startEvent; + }); +} + +function enc(data) { + return btoa(heatshrink.compress(new TextEncoder().encode(data))); +} + +function serializeEvents(events) { + // const splitByGregorianMonth = groupBy(events, (evt) => { + // return new Date(evt.startEvent).getMonth(); + // }); + return JSON.stringify(events); +} diff --git a/apps/hebrew_calendar/hebrewDate.js b/apps/hebrew_calendar/hebrewDate.js deleted file mode 100644 index da0c9cf50..000000000 --- a/apps/hebrew_calendar/hebrewDate.js +++ /dev/null @@ -1,311 +0,0 @@ -/*! - * This script was taked from this page http://www.shamash.org/help/javadate.shtml and ported to Node.js by Ionică Bizău in https://github.com/IonicaBizau/hebrew-date - * - * This script was adapted from C sources written by - * Scott E. Lee, which contain the following copyright notice: - * - * Copyright 1993-1995, Scott E. Lee, all rights reserved. - * Permission granted to use, copy, modify, distribute and sell so long as - * the above copyright and this permission statement are retained in all - * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK. - * - * Bill Hastings - * RBI Software Systems - * bhastings@rbi.com - */ -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; -var GREG_SDN_OFFSET = 32045, DAYS_PER_5_MONTHS = 153, DAYS_PER_4_YEARS = 1461, DAYS_PER_400_YEARS = 146097; -var HALAKIM_PER_HOUR = 1080, HALAKIM_PER_DAY = 25920, HALAKIM_PER_LUNAR_CYCLE = 29 * HALAKIM_PER_DAY + 13753, HALAKIM_PER_METONIC_CYCLE = HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7); -var HEB_SDN_OFFSET = 347997, NEW_MOON_OF_CREATION = 31524, NOON = 18 * HALAKIM_PER_HOUR, AM3_11_20 = 9 * HALAKIM_PER_HOUR + 204, AM9_32_43 = 15 * HALAKIM_PER_HOUR + 589; -var SUN = 0, MON = 1, TUES = 2, WED = 3, THUR = 4, FRI = 5, SAT = 6; -function weekdayarr(d0, d1, d2, d3, d4, d5, d6) { - this[0] = d0; - this[1] = d1; - this[2] = d2; - this[3] = d3; - this[4] = d4; - this[5] = d5; - this[6] = d6; -} -function gregmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11) { - this[0] = m0; - this[1] = m1; - this[2] = m2; - this[3] = m3; - this[4] = m4; - this[5] = m5; - this[6] = m6; - this[7] = m7; - this[8] = m8; - this[9] = m9; - this[10] = m10; - this[11] = m11; -} -function hebrewmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13) { - this[0] = m0; - this[1] = m1; - this[2] = m2; - this[3] = m3; - this[4] = m4; - this[5] = m5; - this[6] = m6; - this[7] = m7; - this[8] = m8; - this[9] = m9; - this[10] = m10; - this[11] = m11; - this[12] = m12; - this[13] = m13; -} -function monthsperyeararr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18) { - this[0] = m0; - this[1] = m1; - this[2] = m2; - this[3] = m3; - this[4] = m4; - this[5] = m5; - this[6] = m6; - this[7] = m7; - this[8] = m8; - this[9] = m9; - this[10] = m10; - this[11] = m11; - this[12] = m12; - this[13] = m13; - this[14] = m14; - this[15] = m15; - this[16] = m16; - this[17] = m17; - this[18] = m18; -} -var gWeekday = new weekdayarr("Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"), gMonth = new gregmontharr("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"), hMonth = new hebrewmontharr("Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "AdarI", "AdarII", "Nisan", "Iyyar", "Sivan", "Tammuz", "Av", "Elul"), mpy = new monthsperyeararr(12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 13); -/** - * hebrewDate - * Convert the Gregorian dates into Hebrew calendar dates. - * - * @name hebrewDate - * @function - * @param {Date|Number} inputDate The date object (representing the Gregorian date) or the year. - * @return {Object} An object containing: - * - * - `year`: The Hebrew year. - * - `month`: The Hebrew month. - * - `month_name`: The Hebrew month name. - * - `date`: The Hebrew date. - */ -function hebrewDate(inputDateOrYear) { - var inputMonth, inputDate; - var hebrewMonth = 0, hebrewDate = 0, hebrewYear = 0, metonicCycle = 0, metonicYear = 0, moladDay = 0, moladHalakim = 0; - function GregorianToSdn(inputYear, inputMonth, inputDay) { - var year = 0, month = 0, sdn = void 0; - // Make year a positive number - if (inputYear < 0) { - year = inputYear + 4801; - } - else { - year = inputYear + 4800; - } - // Adjust the start of the year - if (inputMonth > 2) { - month = inputMonth - 3; - } - else { - month = inputMonth + 9; - year--; - } - sdn = Math.floor(Math.floor(year / 100) * DAYS_PER_400_YEARS / 4); - sdn += Math.floor(year % 100 * DAYS_PER_4_YEARS / 4); - sdn += Math.floor((month * DAYS_PER_5_MONTHS + 2) / 5); - sdn += inputDay - GREG_SDN_OFFSET; - return sdn; - } - function SdnToHebrew(sdn) { - var tishri1 = 0, tishri1After = 0, yearLength = 0, inputDay = sdn - HEB_SDN_OFFSET; - FindTishriMolad(inputDay); - tishri1 = Tishri1(metonicYear, moladDay, moladHalakim); - if (inputDay >= tishri1) { - // It found Tishri 1 at the start of the year. - hebrewYear = metonicCycle * 19 + metonicYear + 1; - if (inputDay < tishri1 + 59) { - if (inputDay < tishri1 + 30) { - hebrewMonth = 1; - hebrewDate = inputDay - tishri1 + 1; - } - else { - hebrewMonth = 2; - hebrewDate = inputDay - tishri1 - 29; - } - return; - } - // We need the length of the year to figure this out,so find Tishri 1 of the next year. - moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear]; - moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY); - moladHalakim = moladHalakim % HALAKIM_PER_DAY; - tishri1After = Tishri1((metonicYear + 1) % 19, moladDay, moladHalakim); - } - else { - // It found Tishri 1 at the end of the year. - hebrewYear = metonicCycle * 19 + metonicYear; - if (inputDay >= tishri1 - 177) { - // It is one of the last 6 months of the year. - if (inputDay > tishri1 - 30) { - hebrewMonth = 13; - hebrewDate = inputDay - tishri1 + 30; - } - else if (inputDay > tishri1 - 60) { - hebrewMonth = 12; - hebrewDate = inputDay - tishri1 + 60; - } - else if (inputDay > tishri1 - 89) { - hebrewMonth = 11; - hebrewDate = inputDay - tishri1 + 89; - } - else if (inputDay > tishri1 - 119) { - hebrewMonth = 10; - hebrewDate = inputDay - tishri1 + 119; - } - else if (inputDay > tishri1 - 148) { - hebrewMonth = 9; - hebrewDate = inputDay - tishri1 + 148; - } - else { - hebrewMonth = 8; - hebrewDate = inputDay - tishri1 + 178; - } - return; - } - else { - if (mpy[(hebrewYear - 1) % 19] == 13) { - hebrewMonth = 7; - hebrewDate = inputDay - tishri1 + 207; - if (hebrewDate > 0) - return; - hebrewMonth--; - hebrewDate += 30; - if (hebrewDate > 0) - return; - hebrewMonth--; - hebrewDate += 30; - } - else { - hebrewMonth = 6; - hebrewDate = inputDay - tishri1 + 207; - if (hebrewDate > 0) - return; - hebrewMonth--; - hebrewDate += 30; - } - if (hebrewDate > 0) - return; - hebrewMonth--; - hebrewDate += 29; - if (hebrewDate > 0) - return; - // We need the length of the year to figure this out,so find Tishri 1 of this year. - tishri1After = tishri1; - FindTishriMolad(moladDay - 365); - tishri1 = Tishri1(metonicYear, moladDay, moladHalakim); - } - } - yearLength = tishri1After - tishri1; - moladDay = inputDay - tishri1 - 29; - if (yearLength == 355 || yearLength == 385) { - // Heshvan has 30 days - if (moladDay <= 30) { - hebrewMonth = 2; - hebrewDate = moladDay; - return; - } - moladDay -= 30; - } - else { - // Heshvan has 29 days - if (moladDay <= 29) { - hebrewMonth = 2; - hebrewDate = moladDay; - return; - } - moladDay -= 29; - } - // It has to be Kislev. - hebrewMonth = 3; - hebrewDate = moladDay; - } - function FindTishriMolad(inputDay) { - // Estimate the metonic cycle number. Note that this may be an under - // estimate because there are 6939.6896 days in a metonic cycle not - // 6940,but it will never be an over estimate. The loop below will - // correct for any error in this estimate. - metonicCycle = Math.floor((inputDay + 310) / 6940); - // Calculate the time of the starting molad for this metonic cycle. - MoladOfMetonicCycle(); - // If the above was an under estimate,increment the cycle number until - // the correct one is found. For modern dates this loop is about 98.6% - // likely to not execute,even once,because the above estimate is - // really quite close. - while (moladDay < inputDay - 6940 + 310) { - metonicCycle++; - moladHalakim += HALAKIM_PER_METONIC_CYCLE; - moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY); - moladHalakim = moladHalakim % HALAKIM_PER_DAY; - } - // Find the molad of Tishri closest to this date. - for (metonicYear = 0; metonicYear < 18; metonicYear++) { - if (moladDay > inputDay - 74) - break; - moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear]; - moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY); - moladHalakim = moladHalakim % HALAKIM_PER_DAY; - } - } - function MoladOfMetonicCycle() { - var r1 = void 0, r2 = void 0, d1 = void 0, d2 = void 0; - // Start with the time of the first molad after creation. - r1 = NEW_MOON_OF_CREATION; - // Calculate gMetonicCycle * HALAKIM_PER_METONIC_CYCLE. The upper 32 - // bits of the result will be in r2 and the lower 16 bits will be in r1. - r1 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE & 0xFFFF); - r2 = r1 >> 16; - r2 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE >> 16 & 0xFFFF); - // Calculate r2r1 / HALAKIM_PER_DAY. The remainder will be in r1,the - // upper 16 bits of the quotient will be in d2 and the lower 16 bits - // will be in d1. - d2 = Math.floor(r2 / HALAKIM_PER_DAY); - r2 -= d2 * HALAKIM_PER_DAY; - r1 = r2 << 16 | r1 & 0xFFFF; - d1 = Math.floor(r1 / HALAKIM_PER_DAY); - r1 -= d1 * HALAKIM_PER_DAY; - moladDay = d2 << 16 | d1; - moladHalakim = r1; - } - function Tishri1(metonicYear, moladDay, moladHalakim) { - var tishri1 = moladDay, dow = tishri1 % 7, leapYear = metonicYear == 2 || metonicYear == 5 || metonicYear == 7 || metonicYear == 10 || metonicYear == 13 || metonicYear == 16 || metonicYear == 18, lastWasLeapYear = metonicYear == 3 || metonicYear == 6 || metonicYear == 8 || metonicYear == 11 || metonicYear == 14 || metonicYear == 17 || metonicYear == 0; - // Apply rules 2,3 and 4 - if (moladHalakim >= NOON || !leapYear && dow == TUES && moladHalakim >= AM3_11_20 || lastWasLeapYear && dow == MON && moladHalakim >= AM9_32_43) { - tishri1++; - dow++; - if (dow == 7) - dow = 0; - } - // Apply rule 1 after the others because it can cause an additional delay of one day. - if (dow == WED || dow == FRI || dow == SUN) { - tishri1++; - } - return tishri1; - } - var inputYear = inputDateOrYear; - if ((typeof inputYear === "undefined" ? "undefined" : _typeof(inputYear)) === "object") { - inputMonth = inputDateOrYear.getMonth() + 1; - inputDate = inputDateOrYear.getDate(); - inputYear = inputDateOrYear.getFullYear(); - } - SdnToHebrew(GregorianToSdn(inputYear, inputMonth, inputDate)); - return { - year: hebrewYear, - month: hebrewMonth, - date: hebrewDate, - month_name: hMonth[hebrewMonth - 1] - }; -} - -exports.hebrewDate = hebrewDate; diff --git a/apps/hebrew_calendar/metadata.json b/apps/hebrew_calendar/metadata.json index a2b7932b6..dc10a99f2 100644 --- a/apps/hebrew_calendar/metadata.json +++ b/apps/hebrew_calendar/metadata.json @@ -2,25 +2,19 @@ "id": "hebrew_calendar", "name": "Hebrew Calendar", "shortName": "HebCal", - "version": "0.04", - "description": "lists the date according to the hebrew calendar", + "version": "0.06", + "description": "lists the date & holidays according to the hebrew calendar", "icon": "app.png", "allow_emulator": false, - "tags": "tool,locale", + "tags": "clocks,tools", + "custom": "customizer.html", "supports": [ "BANGLEJS", "BANGLEJS2" ], + "type": "clock", "readme": "README.md", "storage": [ - { - "name": "hebrew_calendar.app.js", - "url": "app.js" - }, - { - "name": "hebrewDate", - "url": "hebrewDate.js" - }, { "name": "hebrew_calendar.img", "url": "app-icon.js", diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index 87fb43d3d..f0dc54b69 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -1,2 +1,4 @@ 0.01: New App! 0.02: Now keeps user input trace intact by changing how the screen is updated. +0.03: Positioning of marker now takes the height of the widget field into account. +0.04: Fix issue if going back without typing. diff --git a/apps/kbswipe/README.md b/apps/kbswipe/README.md index 1a402a9f3..3f5575777 100644 --- a/apps/kbswipe/README.md +++ b/apps/kbswipe/README.md @@ -4,6 +4,7 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty To get a legend of available characters, just tap the screen. +![](key.png) ## Usage diff --git a/apps/kbswipe/key.png b/apps/kbswipe/key.png new file mode 100644 index 000000000..c5ed9bb9b Binary files /dev/null and b/apps/kbswipe/key.png differ diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 7837a6984..417ac98d9 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -54,18 +54,18 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); if (strArr.length == 0) { Rx1 = 4; Rx2 = 6*4; - Ry1 = 8*4; - Ry2 = 8*4 + 3; + Ry1 = 8*4 + R.y; + Ry2 = 8*4 + 3 + R.y; } else if (strArr.length <= 4) { Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ; Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4; - Ry1 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4); - Ry2 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3; + Ry1 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + R.y; + Ry2 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3 + R.y; } else { Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ; Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4; - Ry1 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4); - Ry2 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3; + Ry1 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + R.y; + Ry2 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3 + R.y; } //print(Rx1,Rx2,Ry1, Ry2); return {x:Rx1,y:Ry1,x2:Rx2,y2:Ry2}; @@ -82,6 +82,24 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); g.drawString(l.join("\n"),R.x+4,R.y+4); } + /* + // This draws a big image to use in the README + (function() { + E.defrag(); + var b = Graphics.createArrayBuffer(500,420,1,{msb:true}); + var n=0; + exports.getStrokes((id,s) => { + var x = n%6; + var y = (n-x)/6; + s = b.transformVertices(s, {scale:0.55, x:x*85-20, y:y*85-20}); + b.fillCircle(s[0],s[1],3); + b.drawPoly(s); + n++; + }); + b.dump(); + })() + */ + function show() { g.reset(); g.clearRect(R).setColor("#f00"); @@ -94,7 +112,6 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); g.drawPoly(s); n++; }); - } function strokeHandler(o) { @@ -130,7 +147,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); show(); }, back:()=>{ Bangle.removeListener("stroke", strokeHandler); - clearInterval(flashInterval); + if (flashInterval) clearInterval(flashInterval); Bangle.setUI(); g.clearRect(Bangle.appRect); resolve(text); diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index f1e7cf7d6..d4026c815 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,12 +1,12 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.02", + "version":"0.04", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", "tags": "keyboard", "supports" : ["BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], + "screenshots": [{"url":"screenshot.png"}], "readme": "README.md", "storage": [ {"name":"textinput","url":"lib.js"} diff --git a/apps/locale/README.md b/apps/locale/README.md index fd43c2825..99fba7945 100644 --- a/apps/locale/README.md +++ b/apps/locale/README.md @@ -1,25 +1,34 @@ -# Languages (locale) +Languages (locale) +================== Country-specific app internationalisation. This is not an app, but instead it is a library that can be used by -other applications or widgets to display messages. +other applications or widgets to provide locale-friendly -## Usage +- Dates +- Time (12h/24h) +- Days of the Week +- Months +- Currency values +- Distances/Lengths/Speed (metric/imperial) +- Temperature (°C/°F) -Some menus that pop up are translated automatically, but if you're -writing an application you can use the `locale` library to +Usage +----- + +If you're writing an application you can use the `locale` library to do all the translation for you. See https://www.espruino.com/Bangle.js+Locale for full examples. ```JS // Date to date string (long) ->require('locale').date(new Date()) +>require("locale").date(new Date()) ="Donnerstag, 02. April 2020" // Date to date string (short) ->require('locale').date(new Date(),1) +>require("locale").date(new Date(), 1) ="02.04.2020" ``` diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index a3ee37326..47697277c 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -47,3 +47,4 @@ 0.32: Added an option to allow quiet mode to override message auto-open 0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going 0.34: Don't buzz for 'map' update messages +0.35: Reset graphics colors before rendering a message (possibly fix #1752) diff --git a/apps/messages/app.js b/apps/messages/app.js index 85d818bd5..644f780b4 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -89,7 +89,7 @@ function getNegImage() { function getMessageImage(msg) { if (msg.img) return atob(msg.img); var s = (msg.src||"").toLowerCase(); - if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); + if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); @@ -193,7 +193,7 @@ function showMapMessage(msg) { ]}, {type:"txt", font:"6x8:2", label:eta } ]}); - g.clearRect(Bangle.appRect); + g.reset().clearRect(Bangle.appRect); layout.render(); Bangle.setUI("updown",function() { // any input to mark as not new and return to menu @@ -268,7 +268,7 @@ function showMusicMessage(msg) { ]}:{}, {type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" } ]}); - g.clearRect(Bangle.appRect); + g.reset().clearRect(Bangle.appRect); layout.render(); updateLabelsInterval = setInterval(function() { @@ -434,7 +434,7 @@ function showMessage(msgid) { } }, {type:"h",fillx:1, c: buttons} ]}); - g.clearRect(Bangle.appRect); + g.reset().clearRect(Bangle.appRect); layout.render(); // ensure button-press on Bangle.js 2 takes us back if (process.env.HWVERSION>1) Bangle.btnWatches = [ diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index ffb4f8b8a..9a2b0a880 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.34", + "version": "0.35", "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/mtnclock/README.md b/apps/mtnclock/README.md new file mode 100644 index 000000000..58538509d --- /dev/null +++ b/apps/mtnclock/README.md @@ -0,0 +1,21 @@ +# Mountain Pass Clock + +Based on the Pebble watchface Weather Land. + +Mountain Pass Clock changes depending on time (day/night) and weather conditions. + +This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification). To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather. + +The scene will change according to the following OpenWeatherMap conditions: clear, cloudy, overcast, lightning, drizzle, rain, fog and snow. Each weather condition has night/day scenes. + +If you choose not to set up weather (or are not connected to Gadgetbridge, for that matter), this clock will default to clear weather, and the scenery will still change from night to day. + +Special thanks to Serj for testing this on the original Bangle. + +## Images + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) +![](screenshot5.png) diff --git a/apps/mtnclock/app-icon.js b/apps/mtnclock/app-icon.js new file mode 100644 index 000000000..8324c248a --- /dev/null +++ b/apps/mtnclock/app-icon.js @@ -0,0 +1 @@ +atob("MDCEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVQAzAFVVVVVVVVVVVVVVVVVVVVVVVVVVVQLzIFVVVVVVAFVVVVVVVVVVVVVVVVVVUB//MQVVVVVQAQVVVVVVVVVVVVVVVVVVAB8RMQBVVVVQIwBVVVVVVVVVVVVVVVVQAQAAABAFVVUB8zAFVVVVVVVVVVVVVVVQExAiAYEFVVAD/zIFVVVVVVVVVVVVVVUBMzgzg4gQVQAvMvMAVVVVVVVVVVVVVVAIMzMzM4iABQACABAABVVVVVVVVVVVVQAjMzMzMziCABgQAQAiAFVVVVVVVVVVVQEzMzMzMzOIECMyIyKIEFVVVVVVVVVVUBMzMzMzMzM4gQIzMzM4gQVVVVVVVVVVAIMzMzMzMzM4iACDMzM4iABVVVVVVVVQAjMzMzMzMzMziCATMzMziCAFVVVVVVVQEzMzMzMzMzMzOIECMzMzOIEFVVVVVVUBMzMzMzMzMzMzM4gQIzMzM4gQVVVVVVAIMiMzMzMzMzMiM4iACDMzMiiABVVVVQAjIAIzMzMzMzIAIziCATMzIAKCAFVVVQE4AACDMzMzM4AACDOIECM4AACIEFVVUAERAiARERERERAiAREREAERAiAREAVVUAAAEyEAAAAAAAEyEAAAAAAAEyEAAAVVVVUBMzIQVVVVUBMzIQVVVVUBMzIQVVVVVVABEREQBVVVABEREQBVVVABEREQBVVVVVAAAAAABVVVAAAAAABVVVAAAAAABVVVVVUGIiJgVVVVUGIiJgVVVVUGIiJgVVVVVVACIiIgBVVVACIiIgBVVVACIiIgBVVVVQAiIiInAFVQAiIiInAFVQAiIiInAFVVVQEiIiInYFVQEiIiInYFVQEiIiInYFVVUAAAAAAAAAUAAAAAAAAAUAAAAAAAAAVVVVAHd3dwBVVVAHd3dwBVVVAHd3dwBVVVVQB3d3d3AFVQB3d3d3AFVQB3d3d3AFVVVQYiIiInYFVQYiIiInYFVQYiIiInYFVVUAIiIiIicAUAIiIiIicAUAIiIiIicAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAARAAAAUAAAARAAAAUAAAARAAAAVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEAFVVVVVVBEBVVVVVVQBEBVVVVVVVVVAAAAAAAAVVBEBVUAAAAAAABVVVVVVVUAASM/MyIQAAAAAAABIjPzMhAAVVVVVVABP///////MyIiIjP///////MQBVVVVQE////////////////////////zEFVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==") \ No newline at end of file diff --git a/apps/mtnclock/app.js b/apps/mtnclock/app.js new file mode 100644 index 000000000..28ba25882 --- /dev/null +++ b/apps/mtnclock/app.js @@ -0,0 +1,350 @@ +var data = require("Storage").readJSON("mtnclock.json", 1) || {}; + +//seeded RNG to generate stars, snow, etc +function sfc32(a, b, c, d) { + return function() { + a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; + var t = (a + b) | 0; + a = b ^ b >>> 9; + b = c + (c << 3) | 0; + c = (c << 21 | c >>> 11); + d = d + 1 | 0; + t = t + d | 0; + c = c + t | 0; + return (t >>> 0) / 4294967296; + }; +} + +//scale x, y coords to screen +function px(x) { + return x*g.getWidth()/100; +} + +function py(y) { + return y*g.getHeight()/100; +} + +function drawMtn(color, coord, dimen) { + //scale mountains to different sizes + g.setColor(color.mtn1).fillPoly([ + coord.x,coord.y, + coord.x,coord.y+dimen.h, + coord.x-dimen.w/2,coord.y+dimen.h + ]); + g.setColor(color.mtn2).fillPoly([ + coord.x,coord.y, + coord.x,coord.y+dimen.h, + coord.x+dimen.w/2,coord.y+dimen.h + ]); +} + +function drawTree(color, coord, dimen) { + //scale trees to different sizes + g.setColor(color.tree1).fillPoly([ + coord.x,coord.y, + coord.x-dimen.w/5,coord.y+dimen.h/4, + coord.x-dimen.w/12,coord.y+dimen.h/4, + coord.x-dimen.w/2.8,coord.y+1.95*dimen.h/4, + coord.x-dimen.w/8,coord.y+1.95*dimen.h/4, + coord.x-dimen.w/2,coord.y+3*dimen.h/4, + coord.x,coord.y+3*dimen.h/4 + ]); + g.setColor(color.tree2).fillPoly([ + coord.x,coord.y, + coord.x+dimen.w/5,coord.y+dimen.h/4, + coord.x+dimen.w/12,coord.y+dimen.h/4, + coord.x+dimen.w/2.8,coord.y+1.95*dimen.h/4, + coord.x+dimen.w/8,coord.y+1.95*dimen.h/4, + coord.x+dimen.w/2,coord.y+3*dimen.h/4, + coord.x,coord.y+3*dimen.h/4 + ]); + g.setColor(color.tree3).fillRect( + coord.x-dimen.w/12,coord.y+3*dimen.h/4, + coord.x+dimen.w/12,coord.y+dimen.h + ); +} + +function drawSnow(color, coord, size) { + g.setColor(color).drawLine(coord.x-px(size),coord.y-py(size),coord.x+px(size),coord.y+py(size)); + g.drawLine(coord.x-px(size),coord.y+py(size),coord.x+px(size),coord.y-py(size)); + g.drawLine(coord.x,coord.y+py(size),coord.x,coord.y-py(size)); + g.drawLine(coord.x-px(size),coord.y,coord.x+px(size),coord.y); +} + +function draw(color) { + +var seed; +var rand; + +g.clear(); +//background + g.setColor(color.bg1).fillRect( + px(0),py(0), + px(100),py(45) + ); + g.setColor(color.bg2).fillRect( + px(0),py(45), + px(100),py(100) + ); + //lightning + if (color.ltn) { + g.setColor(color.ltn).fillPoly([ + px(70),py(20), + px(60),py(28), + px(71),py(29), + px(63),py(40), + px(75),py(28), + px(64),py(27) + ]); + g.fillPoly([ + px(40),py(20), + px(30),py(28), + px(41),py(29), + px(33),py(40), + px(45),py(28), + px(34),py(27) + ]); + } + //stars + if (color.star) { + seed = 4; + rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed); + for (let i = 0; i < 40; i++) { + g.setColor(color.star).drawCircle(Math.floor(rand() * px(100)),Math.floor(rand() * py(33)),Math.floor(rand() * 2)); + } + } + //birds + if (color.bird) { + g.setColor(color.bird).fillCircle(px(17),py(12),px(5)).fillCircle(px(23),py(10),px(5)); + g.setColor(color.bg1).fillCircle(px(18),py(15),px(6)).fillCircle(px(24),py(13),px(6)); + g.setColor(color.bird).fillCircle(px(28),py(19),px(4)).fillCircle(px(33),py(19),px(4)); + g.setColor(color.bg1).fillCircle(px(28),py(21),px(5)).fillCircle(px(34),py(21),px(5)); + } + //sun/moon + if (color.sun) g.setColor(color.sun).fillCircle(px(65), py(22), py(20)); + //path + g.setColor(color.path).fillPoly([ + px(60),py(44), + px(39),py(55), + px(72),py(57), + px(30),py(100), + px(70),py(100), + px(78),py(55), + px(46),py(53) + ]); + //fog + if (color.fog) { + g.setColor(color.fog); + for (let i = 1; i <= 47; i = i+2) { + g.drawLine(px(0),py(i),px(100),py(i)); + } + } + //rain + if (color.rain1) { + g.setColor(color.rain1); + for (let i = 0; i <= 6; i++) { + g.drawLine(px(6+i*20),py(20),px(-14+i*20),py(45)); + } + if (color.rain2) { + for (let i = 0; i <= 6; i++) { + g.drawLine(px(16+i*20),py(20),px(-4+i*20),py(45)); + } + } + } + //snow + if (color.snow) { + seed = 11; + rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed); + for (let i = 0; i < 30; i++) { + drawSnow(color.snow, {x:Math.floor(rand() * px(100)), y:(Math.floor(rand() * py(25))+py(20))}, Math.floor(rand() * 3)); + } + } + //mountains + drawMtn({mtn1:color.mtn2, mtn2:color.mtn1}, {x:px(35), y:py(30)}, {w:px(38), h:py(17)}); + drawMtn({mtn1:color.mtn2, mtn2:color.mtn1}, {x:px(10), y:py(20)}, {w:px(50), h:py(30)}); + drawMtn({mtn1:color.mtn1, mtn2:color.mtn2}, {x:px(90), y:py(20)}, {w:px(70), h:py(30)}); + //lake + g.setColor(color.lake).fillEllipse(px(-15), py(52), px(30), py(57)); + //trees + drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(12),y:py(52)}, {w:px(13),h:py(13)}); + drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(48),y:py(52)}, {w:px(13),h:py(13)}); + drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(34),y:py(46)}, {w:px(6),h:py(6)}); + drawTree({tree1:color.tree1, tree2:color.tree2, tree3:color.tree3}, {x:px(70),y:py(46)}, {w:px(6),h:py(6)}); + drawTree({tree1:color.tree1, tree2:color.tree2, tree3:color.tree3}, {x:px(90),y:py(52)}, {w:px(13),h:py(13)}); + //clouds + if (color.cloud1) { + g.setColor(color.cloud1); + if (color.cloud2) g.fillRect(0, 0, px(100), py(10)); + g.fillCircle(px(3), py(12), py(4)); + g.fillCircle(px(10), py(12), py(5)); + g.fillCircle(px(16), py(11), py(6)); + g.fillCircle(px(24), py(10), py(8)); + g.fillCircle(px(30), py(11), py(6)); + g.fillCircle(px(35), py(12), py(5)); + g.fillCircle(px(40), py(12), py(6)); + g.fillCircle(px(48), py(13), py(5)); + g.fillCircle(px(55), py(14), py(5)); + g.fillCircle(px(60), py(12), py(5)); + g.fillCircle(px(65), py(11), py(6)); + g.fillCircle(px(75), py(10), py(8)); + g.fillCircle(px(85), py(11), py(6)); + g.fillCircle(px(90), py(12), py(5)); + g.fillCircle(px(97), py(13), py(4)); + } + + //clock text + (color.clock == undefined) ? g.setColor(0xFFFF) : g.setColor(color.clock); + g.setFont("Vector", py(20)).setFontAlign(-1, -1).drawString((require("locale").time(new Date(), 1).replace(" ", "")), px(2), py(67)); + g.setFont("Vector", py(10)).drawString(require('locale').dow(new Date(), 1)+" "+new Date().getDate()+" "+require('locale').month(new Date(), 1)+((data.temp == undefined) ? "" : " | "+require('locale').temp(Math.round(data.temp-273.15)).replace(".0", "")), px(2), py(87)); +} + +var i = 0; + +function setWeather() { + var a = {}; + //clear day/night is default weather + if ((data.code >= 800 && data.code <=802) || data.code == undefined) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-clear + a = { + bg1:0x4FFF, bg2:0x03E0, + sun:0xFD20, + path:0x8200, + mtn1:0x045f, mtn2:0x000F, + lake:0x000F, + tree1:0x07E0, tree2:0, tree3:0x7BE0, + bird:0xFFFF + }; + //day-cloudy + if (data.code == 801 || data.code == 802) a.cloud1 = 0xFFFF; + } + else { + //night-clear + a = { + bg1:0, bg2:0x0005, + sun:0xC618, + path:0, + mtn1:0x0210, mtn2:0x0010, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + star:0xFFFF + }; + //night-cloudy + if (data.code == 801 || data.code == 802) a.cloud1 = 0x4208; + } + } + else if (((data.code >= 300) && (data.code < 600)) || ((data.code >= 200) && (data.code < 300)) || data.code == 803 || data.code == 804) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-overcast + a = { + bg1:0xC618, bg2:0x0200, + path:0x3000, + mtn1:0x3B38, mtn2:0x0005, + lake:0x000F, + tree1:0x03E0, tree2:0, tree3:0x59E0, + cloud1:0x7BEF, cloud2:1 + }; + //day-lightning + if (data.code >= 200 && data.code < 300) a.ltn = 0xFFFF; + //day-drizzle + if ((data.code >= 300 && data.code < 600) || (data.code >= 200 && data.code <= 202) || (data.code >= 230 && data.code <= 232)) a.rain1 = 0xFFFF; + //day-rain + if ((data.code >= 500 && data.code < 600) || (data.code >= 200 && data.code <= 202)) a.rain2 = 1; + } + else { + //night-overcast + a = { + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + cloud1:0x4208, cloud2:1 + }; + //night-lightning + if (data.code >= 200 && data.code < 300) a.ltn = 0xFFFF; + //night-drizzle + if ((data.code >= 300 && data.code < 600) || (data.code >= 200 && data.code <= 202) || (data.code >= 230 && data.code <= 232)) a.rain1 = 0xC618; + //night-rain + if ((data.code >= 500 && data.code < 600) || (data.code >= 200 && data.code <= 202)) rain2 = 1; + } + } + else if ((data.code >= 700) && (data.code < 800)) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-fog + a = { + bg1:0xC618, bg2:0x0200, + path:0x3000, + mtn1:0x3B38, mtn2:0x0005, + lake:0x000F, + tree1:0x03E0, tree2:0, tree3:0x59E0, + fog:0xFFFF + }; + } + else { + //night-fog + a = { + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + fog:0x7BEF + }; + } + } + else if ((data.code >= 600) && (data.code < 700)) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-snow + a = { + bg1:0, bg2:0x7BEF, + path:0xC618, + mtn1:0xFFFF, mtn2:0x7BEF, + lake:0x07FF, + tree1:0xC618, tree2:0xC618, tree3:0x59E0, + cloud1:0x7BEF, cloud2:1, + snow:0xFFFF, + clock: 0 + }; + } + else { + //night-snow + a = { + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x39E7, tree2:0x39E7, tree3:0x59E0, + cloud1:0x4208, cloud2:1, + snow:0xFFFF + }; + } + } + draw(a); +} + +const _GB = global.GB; +global.GB = (event) => { + if (event.t==="weather") { + data = event; + require("Storage").write('mtnclock.json', event); + setWeather(); + } + if (_GB) setTimeout(_GB, 0, event); +}; + +var drawTimeout; + +//update watchface in next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + setWeather(); + queueDraw(); + }, 60000 - (Date.now() % 60000)); +} + +queueDraw(); +setWeather(); +Bangle.setUI("clock"); diff --git a/apps/mtnclock/app.png b/apps/mtnclock/app.png new file mode 100644 index 000000000..083304294 Binary files /dev/null and b/apps/mtnclock/app.png differ diff --git a/apps/mtnclock/metadata.json b/apps/mtnclock/metadata.json new file mode 100644 index 000000000..a3a173069 --- /dev/null +++ b/apps/mtnclock/metadata.json @@ -0,0 +1,25 @@ +{ + "id": "mtnclock", + "name": "Mountain Pass Clock", + "shortName": "Mtn Clock", + "version": "0.01", + "description": "A clock that changes scenery based on time and weather.", + "readme":"README.md", + "icon": "app.png", + "screenshots": [ + {"url":"screenshot1.png"}, + {"url":"screenshot2.png"}, + {"url":"screenshot3.png"}, + {"url":"screenshot4.png"}, + {"url":"screenshot5.png"} + ], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"mtnclock.app.js","url":"app.js"}, + {"name":"mtnclock.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"mtnclock.json"}] +} \ No newline at end of file diff --git a/apps/mtnclock/screenshot1.png b/apps/mtnclock/screenshot1.png new file mode 100644 index 000000000..a89bbe2db Binary files /dev/null and b/apps/mtnclock/screenshot1.png differ diff --git a/apps/mtnclock/screenshot2.png b/apps/mtnclock/screenshot2.png new file mode 100644 index 000000000..730edbab5 Binary files /dev/null and b/apps/mtnclock/screenshot2.png differ diff --git a/apps/mtnclock/screenshot3.png b/apps/mtnclock/screenshot3.png new file mode 100644 index 000000000..abd93d504 Binary files /dev/null and b/apps/mtnclock/screenshot3.png differ diff --git a/apps/mtnclock/screenshot4.png b/apps/mtnclock/screenshot4.png new file mode 100644 index 000000000..8f2e1de90 Binary files /dev/null and b/apps/mtnclock/screenshot4.png differ diff --git a/apps/mtnclock/screenshot5.png b/apps/mtnclock/screenshot5.png new file mode 100644 index 000000000..b34a3111c Binary files /dev/null and b/apps/mtnclock/screenshot5.png differ diff --git a/apps/mylocation/ChangeLog b/apps/mylocation/ChangeLog index ab8af7620..1239554f0 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -2,3 +2,5 @@ 0.02: Enhanced icon, make it bolder 0.03: Fixed issue with defaulting back to London 0.04: Fixed issue selecting Frankfurt not saved +0.05: Fixed issue with back option +0.06: renamed source files to match standard diff --git a/apps/mylocation/mylocation.app.js b/apps/mylocation/app.js similarity index 98% rename from apps/mylocation/mylocation.app.js rename to apps/mylocation/app.js index b9451e0fb..fd5c9cc6d 100644 --- a/apps/mylocation/mylocation.app.js +++ b/apps/mylocation/app.js @@ -55,7 +55,7 @@ function showMainMenu() { //console.log("showMainMenu"); const mainmenu = { '': { 'title': 'My Location' }, - '{ load(); }, + '< Back': ()=>{ load(); }, 'City': { value: 0 | locations.indexOf(s.location), min: 0, max: locations.length - 1, diff --git a/apps/mylocation/mylocation.png b/apps/mylocation/app.png similarity index 100% rename from apps/mylocation/mylocation.png rename to apps/mylocation/app.png diff --git a/apps/mylocation/mylocation.icon.js b/apps/mylocation/icon.js similarity index 100% rename from apps/mylocation/mylocation.icon.js rename to apps/mylocation/icon.js diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index 9182ba160..16549b2ba 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -1,17 +1,17 @@ { "id": "mylocation", "name": "My Location", "shortName":"My Location", - "icon": "mylocation.png", + "icon": "app.png", "type": "app", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.04", + "version":"0.06", "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", "readme": "README.md", "tags": "tool,utility", "supports": ["BANGLEJS", "BANGLEJS2"], "storage": [ - {"name":"mylocation.app.js","url":"mylocation.app.js"}, - {"name":"mylocation.img","url":"mylocation.icon.js","evaluate": true } + {"name":"mylocation.app.js","url":"app.js"}, + {"name":"mylocation.img","url":"icon.js","evaluate": true } ], "data": [ {"name":"mylocation.json"} diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 877b1354a..140567068 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -19,3 +19,4 @@ 0.13: Fix for when widget is used before app 0.14: Remove unneeded variable assignment 0.15: Show distance more accurately in conjunction with new locale app (fix #1523) +0.16: Ability to append to existing track (fix #1712) diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 4146e92be..3d50bbd14 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.15", + "version": "0.16", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 4a105754b..be714c19d 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -142,7 +142,7 @@ }; } } - + /* eg. foobar.recorder.js (function(recorders) { recorders.foobar = { @@ -193,7 +193,7 @@ settings.record.forEach(r => { var recorder = recorders[r]; if (!recorder) { - console.log("Recorder for "+E.toJS(r)+"+not found"); + console.log(/*LANG*/"Recorder for "+E.toJS(r)+/*LANG*/"+not found"); return; } var activeRecorder = recorder(); @@ -231,11 +231,11 @@ },getRecorders:getRecorders,reload:function() { reload(); Bangle.drawWidgets(); // relayout all widgets - },setRecording:function(isOn) { + },setRecording:function(isOn, forceAppend) { var settings = loadSettings(); if (isOn && !settings.recording && !settings.file) { settings.file = "recorder.log0.csv"; - } else if (isOn && !settings.recording && require("Storage").list(settings.file).length){ + } else if (isOn && !forceAppend && !settings.recording && require("Storage").list(settings.file).length){ var logfiles=require("Storage").list(/recorder.log.*/); var maxNumber=0; for (var c of logfiles){ @@ -246,18 +246,19 @@ newFileName="recorder.log" + (maxNumber + 1) + ".csv"; updateSettings(settings); } - var buttons={Yes:"yes",No:"no"}; - if (newFileName) buttons["New"] = "new"; - return E.showPrompt("Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:"Recorder",buttons:buttons}).then(selection=>{ - if (selection==="no") return false; // just cancel - if (selection==="yes") { + var buttons={/*LANG*/"Yes":"overwrite",/*LANG*/"No":"cancel"}; + if (newFileName) buttons[/*LANG*/"New"] = "new"; + buttons[/*LANG*/"Append"] = "append"; + return E.showPrompt(/*LANG*/"Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:/*LANG*/"Recorder",buttons:buttons}).then(selection=>{ + if (selection==="cancel") return false; // just cancel + if (selection==="overwrite") require("Storage").open(settings.file,"r").erase(); - } if (selection==="new"){ settings.file = newFileName; updateSettings(settings); } - return WIDGETS["recorder"].setRecording(1); + // if (selection==="append") // we do nothing - all is fine + return WIDGETS["recorder"].setRecording(1,true/*force append*/); }); } settings.recording = isOn; diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index 7372f9c4a..717763e19 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -4,3 +4,6 @@ 0.04: Fix `getTimeToAlarm` to check for next dow if alarm.t lower currentTime. 0.05: Export new functions (`newDefaultAlarm/Timer`), add Settings page 0.06: Refactor some methods to library +0.07: Update settings + Correct `decodeTime(t)` to return a more likely expected time + diff --git a/apps/sched/README.md b/apps/sched/README.md index 47507fc14..57acdc0b7 100644 --- a/apps/sched/README.md +++ b/apps/sched/README.md @@ -16,6 +16,7 @@ Global Settings - `Unlock at Buzz` - If `Yes` the alarm/timer will unlock the watch - `Default Auto Snooze` - Default _Auto Snooze_ value for newly created alarms (_Alarms_ only) - `Default Snooze` - Default _Snooze_ value for newly created alarms/timers +- `Default Repeat` - Default _Repeat_ value for newly created alarms (_Alarms_ only) - `Buzz Count` - The number of buzzes before the watch goes silent - `Buzz Interval` - The interval between one buzz and the next - `Default Alarm/Timer Pattern` - Default vibration pattern for newly created alarms/timers @@ -38,7 +39,7 @@ Alarms are stored in an array in `sched.json`, and take the form: // WED = 8 // THU = 16 // FRI = 32 - // SAT = 64 + // SAT = 64 date : "2022-04-04", // OPTIONAL date for the alarm, in YYYY-MM-DD format // eg (new Date()).toISOString().substr(0,10) diff --git a/apps/sched/lib.js b/apps/sched/lib.js index 58ba5daf0..891776263 100644 --- a/apps/sched/lib.js +++ b/apps/sched/lib.js @@ -47,7 +47,7 @@ exports.getTimeToAlarm = function(alarm, time) { /// Force a reload of the current alarms and widget exports.reload = function() { eval(require("Storage").read("sched.boot.js")); - if (WIDGETS["alarm"]) { + if (global.WIDGETS && WIDGETS["alarm"]) { WIDGETS["alarm"].reload(); Bangle.drawWidgets(); } @@ -59,8 +59,8 @@ exports.newDefaultAlarm = function () { let alarm = { t: 12 * 3600000, // Default to 12:00 on: true, - rp: false, // repeat not the default - as: settings.defaultAutoSnooze || false, + rp: settings.defaultRepeat, + as: settings.defaultAutoSnooze, dow: 0b1111111, last: 0, vibrate: settings.defaultAlarmPattern, @@ -95,6 +95,7 @@ exports.getSettings = function () { unlockAtBuzz: false, defaultSnoozeMillis: 600000, // 10 minutes defaultAutoSnooze: false, + defaultRepeat: false, buzzCount: 10, buzzIntervalMillis: 3000, // 3 seconds defaultAlarmPattern: "..", @@ -110,9 +111,9 @@ exports.setSettings = function(settings) { // time in ms -> { hrs, mins } exports.decodeTime = function(t) { - t = 0 | t; // sanitise - let hrs = 0 | (t / 3600000); - return { hrs: hrs, mins: Math.round((t - hrs * 3600000) / 60000) }; + t = Math.ceil(t / 60000); // sanitise to full minutes + let hrs = 0 | (t / 60); + return { hrs: hrs, mins: t - hrs * 60 }; } // time in { hrs, mins } -> ms diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 5f61d7d04..c41e5b5b3 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.06", + "version": "0.07", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", diff --git a/apps/sched/settings.js b/apps/sched/settings.js index 642e43b43..5ddb4dab2 100644 --- a/apps/sched/settings.js +++ b/apps/sched/settings.js @@ -36,6 +36,15 @@ } }, + /*LANG*/"Default Repeat": { + value: settings.defaultRepeat, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.defaultRepeat = v; + require("sched").setSettings(settings); + } + }, + /*LANG*/"Buzz Count": { value: settings.buzzCount, min: 5, diff --git a/apps/stardateclock/ChangeLog b/apps/stardateclock/ChangeLog index 7ebdc317a..431463bc8 100644 --- a/apps/stardateclock/ChangeLog +++ b/apps/stardateclock/ChangeLog @@ -1 +1 @@ -1.0: Initial release on the app repository for Bangle.js 1 and 2 +0.01: Initial release on the app repository for Bangle.js 1 and 2 diff --git a/apps/stardateclock/metadata.json b/apps/stardateclock/metadata.json index 8be981038..2f4f27425 100644 --- a/apps/stardateclock/metadata.json +++ b/apps/stardateclock/metadata.json @@ -3,7 +3,7 @@ "name":"Stardate Clock", "shortName":"Stardate Clock", "description": "A clock displaying a stardate along with a 'standard' digital/analog clock in LCARS design", - "version":"1.0", + "version":"0.01", "icon": "app.png", "type":"clock", "tags": "clock", diff --git a/apps/tapkb/ChangeLog b/apps/tapkb/ChangeLog new file mode 100644 index 000000000..624f1b0fb --- /dev/null +++ b/apps/tapkb/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version \ No newline at end of file diff --git a/apps/tapkb/README.md b/apps/tapkb/README.md new file mode 100644 index 000000000..4f49b1494 --- /dev/null +++ b/apps/tapkb/README.md @@ -0,0 +1,7 @@ +# Tap Keyboard +This was originally designed for the Noteify app. With the new keyboard system in place, it has become its own keyboard app. + +## Usage +* Swipe left or right to cycle between the alphabet, numerals, and symbols. +* Hitting "caps" once will capitalize one character only. Hitting "caps" twice activates caps lock, and all subsequent characters will be capitalized until you hit "caps" again. +* "New line" is represented by a pilcrow (¶). When you hit the back button, these symbols will be converted into newline. diff --git a/apps/tapkb/app-icon.js b/apps/tapkb/app-icon.js new file mode 100644 index 000000000..c2b495917 --- /dev/null +++ b/apps/tapkb/app-icon.js @@ -0,0 +1 @@ +atob("MDCBAf////////////////AAAAAAD8AAAAAAA4AAAAAAAYAAAAAAAQf/////4A//////8A//////8A//////8A//////8A//////8A///8P/8A///8P/8A///8P/8A///8P/8A///8P/8A///8P/8A///8P//w///8P//w///8MP/w///8MP/w///8MMPw///8MMPw///8AMMA///8AAMA///8AAAA///8AAAA///8OAAA///8P+AA//wcP/8Af/AEP/8IA+AAP/8IA+AAP/8MA+DAP/8PA+BgP/8P//B4P/8P//g8P/8P//wfP/8P//4Pv/8P//8H//8P//8D//8P//+D//8P///B//4P///gAAAf///wAAAf///4AAA////8AADw==") \ No newline at end of file diff --git a/apps/tapkb/app.png b/apps/tapkb/app.png new file mode 100644 index 000000000..2b01ad280 Binary files /dev/null and b/apps/tapkb/app.png differ diff --git a/apps/tapkb/lib.js b/apps/tapkb/lib.js new file mode 100644 index 000000000..5ff524165 --- /dev/null +++ b/apps/tapkb/lib.js @@ -0,0 +1,162 @@ +exports.input = function(options) { + options = options||{}; + var text = options.text; + if ("string"!=typeof text) text=""; + +var layer = 0; +var caps = 0; + +class keyPad { + constructor(x1, y1, x2, y2, func) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.func = !func ? "" : func; + } + + draw() { + g.setColor(g.theme.fg).drawRect(this.x1, this.y1, this.x2, this.y2).clearRect(this.x1+1, this.y1+1, this.x2-1, this.y2-1).setFont("6x8",2).setFontAlign(0, 0, 0).drawString(this.func, (((this.x2-this.x1)/2)+this.x1), (((this.y2-this.y1)/2)+this.y1)); + } + + onTouch(xy) { + if (this.func == "space") text += " "; + else if (this.func == "<-") text = text.slice(0, -1); + else if (this.func == "new\nline") text += String.fromCharCode(182); + else if (this.func == "caps") { + caps = 1; + renderKeys(); + } + else if (this.func == "Caps") { + caps = 2; + renderKeys(); + } + else if (this.func == "CAPS") { + caps = 0; + renderKeys(); + } + else { + text += this.func; + if (caps == 1) caps = 0; + } + g.clearRect(25, 0, g.getWidth(), 25).setFontAlign(-1, -1).drawString(text.substring(text.length-12, text.length)+"_", 25, 7); + } +} + +function renderKeys() { + var a; + var i; + if (layer == 0) { + if (caps == 0) { + a = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "caps", "space", "<-"]; + } + else a = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "Caps", "space", "<-"]; + if (caps == 2) a[9] = "CAPS"; + for (i = 0; i < a.length; i++) { + pad[i].func = a[i]; + } + } + else if (layer == 1) { + if (caps == 0) { + a = ["j", "k", "l", "m", "n", "o", "p", "q", "r", "caps", "space", "<-"]; + } + else a = ["J", "K", "L", "M", "N", "O", "P", "Q", "R", "Caps", "space", "<-"]; + if (caps == 2) a[9] = "CAPS"; + for (i = 0; i < a.length; i++) { + pad[i].func = a[i]; + } + } + else if (layer == 2) { + if (caps == 0) { + a = ["s", "t", "u", "v", "w", "x", "y", "z", "0", "caps", "space", "<-"]; + } + else a = ["S", "T", "U", "V", "W", "X", "Y", "Z", "0", "Caps", "space", "<-"]; + if (caps == 2) a[9] = "CAPS"; + for (i = 0; i < a.length; i++) { + pad[i].func = a[i]; + } + } + else if (layer == 3) { + if (caps == 0) { + a = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "caps", "space", "<-"]; + } + else a = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "Caps", "space", "<-"]; + if (caps == 2) a[9] = "CAPS"; + for (i = 0; i < a.length; i++) { + pad[i].func = a[i]; + } + } + else if (layer == 4) { + if (caps == 0) { + a = [".", ",", "?", "!", "(", ")", "-", "\'", "new\nline", "caps", "space", "<-"]; + } + else a = ["-", "+", "/", "*", ":", "#", "$", "%", "new\nline", "Caps", "space", "<-"]; + if (caps == 2) a[9] = "CAPS"; + for (i = 0; i < a.length; i++) { + pad[i].func = a[i]; + } + } + + for (var j = 0; j < pad.length; j++) { + pad[j].draw(); + } +} + +var pad = []; +pad[0] = new keyPad(0, 29, 57, 64); +pad[1] = new keyPad(59, 29, 116, 64); +pad[2] = new keyPad(118, 29, 175, 64); +pad[3] = new keyPad(0, 66, 57, 101); +pad[4] = new keyPad(59, 66, 116, 101); +pad[5] = new keyPad(118, 66, 175, 101); +pad[6] = new keyPad(0, 103, 57, 138); +pad[7] = new keyPad(59, 103, 116, 138); +pad[8] = new keyPad(118, 103, 175, 138); +pad[9] = new keyPad(0, 140, 57, 175); +pad[10] = new keyPad(59, 140, 116, 175); +pad[11] = new keyPad(118, 140, 175, 175); +g.clear(); +renderKeys(); + +var drag; +var e; + +return new Promise((resolve,reject) => { + + Bangle.setUI({mode:"custom", drag:e=>{ + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } + else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + //horizontal swipes + if (Math.abs(dx)>Math.abs(dy)+10) { + //swipe left + if (dx<0) layer == 4 ? layer = 0 : layer++; + //swipe right + if (dx>0) layer == 0 ? layer = 4 : layer--; + } + } + renderKeys(); + },touch:(button, xy)=>{ + for (var i = 0; i < pad.length; i++) { + if ((xy.x >= pad[i].x1) && (xy.x <= pad[i].x2) && (xy.y >= pad[i].y1) && (xy.y <= pad[i].y2)) { + pad[i].onTouch(xy); + i = pad.length; + } + } + },back:()=>{ + Bangle.setUI(); + g.clear(); + resolve(text.replace(new RegExp(String.fromCharCode(182), 'g'), '\n')); + }}); + g.clearRect(25, 0, g.getWidth(), 25).setColor(g.theme.fg).setFont("6x8", 2); + if (text == "") g.setFontAlign(0, -1).drawString("<-Swipe->", g.getWidth()/2, 7); + else { + text = text.replace(/\n/g, String.fromCharCode(182)); + g.setFontAlign(-1, -1).drawString(text.substring(text.length-12, text.length)+"_", 25, 7); + } +}); + +}; diff --git a/apps/tapkb/metadata.json b/apps/tapkb/metadata.json new file mode 100644 index 000000000..c450f3d47 --- /dev/null +++ b/apps/tapkb/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "tapkb", + "name": "Tap keyboard", + "version":"0.01", + "description": "An onscreen tap keyboard.", + "icon": "app.png", + "type":"textinput", + "tags": "keyboard", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"textinput","url":"lib.js"} + ] +} \ No newline at end of file diff --git a/apps/tapkb/screenshot.png b/apps/tapkb/screenshot.png new file mode 100644 index 000000000..bd1caed38 Binary files /dev/null and b/apps/tapkb/screenshot.png differ diff --git a/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog index 14159bc19..4e53f6f8b 100644 --- a/apps/terminalclock/ChangeLog +++ b/apps/terminalclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Rename "Activity" in "Motion" and display the true values for it 0.03: Add Banglejs 1 compatibility +0.04: Fix settings bug diff --git a/apps/terminalclock/metadata.json b/apps/terminalclock/metadata.json index de369bf10..a34602913 100644 --- a/apps/terminalclock/metadata.json +++ b/apps/terminalclock/metadata.json @@ -3,7 +3,7 @@ "name": "Terminal Clock", "shortName":"Terminal Clock", "description": "A terminal cli like clock displaying multiple sensor data", - "version":"0.03", + "version":"0.04", "icon": "app.png", "type": "clock", "tags": "clock", diff --git a/apps/terminalclock/settings.js b/apps/terminalclock/settings.js index 77df69b12..6b686058b 100644 --- a/apps/terminalclock/settings.js +++ b/apps/terminalclock/settings.js @@ -18,7 +18,7 @@ "" : { "title" : "Terminal Clock" }, "< Back" : () => back(), 'HR confidence': { - value: 50|settings.HRMinConfidence, // 0| converts undefined to 0 + value: settings.HRMinConfidence, min: 0, max: 100, onchange: v => { settings.HRMinConfidence = v; @@ -26,7 +26,7 @@ } }, 'Show date': { - value: !!settings.showDate, + value: settings.showDate, format: v => v?"Yes":"No", onchange: v => { settings.showDate = v; @@ -34,7 +34,7 @@ } }, 'Show HRM': { - value: !!settings.showHRM, + value: settings.showHRM, format: v => v?"Yes":"No", onchange: v => { settings.showHRM = v; @@ -42,7 +42,7 @@ } }, 'Show Activity': { - value: !!settings.showActivity, + value: settings.showActivity, format: v => v?"Yes":"No", onchange: v => { settings.showActivity = v; @@ -50,7 +50,7 @@ } }, 'Show Steps': { - value: !!settings.showStepCount, + value: settings.showStepCount, format: v => v?"Yes":"No", onchange: v => { settings.showStepCount = v; diff --git a/core b/core index 89049a5c7..44d49cdbd 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 89049a5c7c80d2b56dc235d135fc63b80789db96 +Subproject commit 44d49cdbd59dc6a7e09cc0facee89d338ccc7d04 diff --git a/index.html b/index.html index 7a94f684a..de7facd5a 100644 --- a/index.html +++ b/index.html @@ -75,11 +75,14 @@
+ + +