diff --git a/.gitignore b/.gitignore index 523dc5f20..fce2efb1a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ appdates.csv _config.yml tests/Layout/bin/tmp.* tests/Layout/testresult.bmp -apps.local.json \ No newline at end of file +apps.local.json +_site diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js index 58738d04a..9163aaf3a 100644 --- a/apps/2047pp/2047pp.app.js +++ b/apps/2047pp/2047pp.app.js @@ -17,7 +17,7 @@ class TwoK { bh = Math.floor(h/4); bw = Math.floor(w/4); g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0); - g.setFont("Vector", 16).setColor("#fff").drawString("Score:"+this.score.toString(), g.getWidth()/2, 8); + g.setFont("Vector", 16).setColor(g.theme.fg).drawString("Score:"+this.score.toString(), g.getWidth()/2, 8); this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false); for (y=0; y<4; ++y) for (x=0; x<4; ++x) { diff --git a/apps/2047pp/ChangeLog b/apps/2047pp/ChangeLog new file mode 100644 index 000000000..a1f88e5ec --- /dev/null +++ b/apps/2047pp/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Better support for watch themes diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json index f0fd6c1e3..033354ac6 100644 --- a/apps/2047pp/metadata.json +++ b/apps/2047pp/metadata.json @@ -2,7 +2,7 @@ "name": "2047pp", "shortName":"2047pp", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Bangle version of a tile shifting game", "supports" : ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, diff --git a/apps/accellog/app.js b/apps/accellog/app.js index c54c5002b..4bead361e 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -29,7 +29,7 @@ function showMenu() { } function viewLog(n) { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var f = require("Storage").open(getFileName(n), "r"); var records = 0, l = "", ll=""; while ((l=f.readLine())!==undefined) {records++;ll=l;} diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/activityreminder/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/activityreminder/README.md b/apps/activityreminder/README.md new file mode 100644 index 000000000..03466bbbf --- /dev/null +++ b/apps/activityreminder/README.md @@ -0,0 +1,13 @@ +# Activity reminder + +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: +- 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 +- 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-icon.js b/apps/activityreminder/app-icon.js new file mode 100644 index 000000000..418657961 --- /dev/null +++ b/apps/activityreminder/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwYda7dtwAQNmwRB2wQMgO2CIXACJcNCIfYCJYOCCgQRNJQYRM2ADBgwR/CKprRWAKPQWZ0DCIjXLjYREGpYODAQVgCBB3Btj+EAoQAGO4IdCgImDCAwLCAoo4IF4J3DCIPDCIQ4FO4VtwARCAoIRGRgQCBa4IRCKAQRERgOwIIIRDAoOACIoIBwwRHLIqMCFgIRCGQQRIWAYRLYQoREWwTmHO4IRCFgLXHPoi/CbogAFEAIRCWwTpKEwZBCHwK5BCJZEBCJZcCGQTLDCJK/BAQIRKMoaSDOIYAFeQYRMcYRWBXIUAWYPACIq8DagfACJQLCCIYsBU4QRF7B9CAogRGI4QLCAoprIMoZKER5C/DAoShMAo4AGfAQFIACQ=")) diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js new file mode 100644 index 000000000..9fb52e9ac --- /dev/null +++ b/apps/activityreminder/app.js @@ -0,0 +1,37 @@ +function drawAlert(){ + E.showPrompt("Inactivity detected",{ + title:"Activity reminder", + buttons : {"Ok": true,"Dismiss": false} + }).then(function(v) { + if(v == true){ + stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - 3); + require("activityreminder").saveStepsArray(stepsArray); + } + if(v == false){ + stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - activityreminder.dismissDelayMin); + require("activityreminder").saveStepsArray(stepsArray); + } + load(); + }); + + Bangle.buzz(400); + setTimeout(load, 10000); +} + +function run(){ + if(stepsArray.length == activityreminder.maxInnactivityMin){ + if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){ + drawAlert(); + } + }else{ + eval(require("Storage").read("activityreminder.settings.js"))(()=>load()); + } +} + + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +activityreminder = require("activityreminder").loadSettings(); +stepsArray = require("activityreminder").loadStepsArray(); +run(); diff --git a/apps/activityreminder/app.png b/apps/activityreminder/app.png new file mode 100644 index 000000000..91073c444 Binary files /dev/null and b/apps/activityreminder/app.png differ diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js new file mode 100644 index 000000000..0f89bf543 --- /dev/null +++ b/apps/activityreminder/boot.js @@ -0,0 +1,29 @@ +function run(){ + var now = new Date(); + var h = now.getHours(); + if(h >= activityreminder.startHour && h < activityreminder.endHour){ + var health = Bangle.getHealthStatus("day"); + stepsArray.unshift(health.steps); + stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin); + require("activityreminder").saveStepsArray(stepsArray); + } + else{ + if(stepsArray != []){ + stepsArray = []; + require("activityreminder").saveStepsArray(stepsArray); + } + } + if(stepsArray.length >= activityreminder.maxInnactivityMin){ + if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){ + load('activityreminder.app.js'); + } + } +} + + +activityreminder = require("activityreminder").loadSettings(); +if(activityreminder.enabled) { + stepsArray = require("activityreminder").loadStepsArray(); + setInterval(run, 60000); +} + diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js new file mode 100644 index 000000000..fee30e4c3 --- /dev/null +++ b/apps/activityreminder/lib.js @@ -0,0 +1,22 @@ +exports.loadSettings = function() { + return Object.assign({ + enabled: true, + startHour: 9, + endHour: 20, + maxInnactivityMin: 30, + dismissDelayMin: 15, + minSteps: 50 + }, require("Storage").readJSON("ar.settings.json", true) || {}); +}; + +exports.writeSettings = function(settings){ + require("Storage").writeJSON("ar.settings.json", settings); +}; + +exports.saveStepsArray = function(stepsArray) { + require("Storage").writeJSON("ar.stepsarray.json", stepsArray); +}; + +exports.loadStepsArray = function(){ + return require("Storage").readJSON("ar.stepsarray.json") || []; +}; \ No newline at end of file diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json new file mode 100644 index 000000000..bc31776d6 --- /dev/null +++ b/apps/activityreminder/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "activityreminder", + "name": "Activity Reminder", + "shortName":"Activity Reminder", + "description": "A reminder to take short walks for the ones with a sedentary lifestyle", + "version":"0.01", + "icon": "app.png", + "type": "app", + "tags": "tool,activity", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name": "activityreminder.app.js", "url":"app.js"}, + {"name": "activityreminder.boot.js", "url": "boot.js"}, + {"name": "activityreminder.settings.js", "url": "settings.js"}, + {"name": "activityreminder", "url": "lib.js"}, + {"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true} + ], + "data": [ + {"name": "ar.settings.json", "name": "ar.stepsarray.json"} + ] +} diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js new file mode 100644 index 000000000..65a19feb2 --- /dev/null +++ b/apps/activityreminder/settings.js @@ -0,0 +1,58 @@ +(function(back) { + // Load settings + var settings = require("activityreminder").loadSettings(); + + // Show the menu + E.showMenu({ + "" : { "title" : "Activity Reminder" }, + "< Back" : () => back(), + 'Enable': { + value: !!settings.enabled, + format: v => v?"Yes":"No", + onchange: v => { + settings.enabled = v; + require("activityreminder").writeSettings(settings); + } + }, + 'Start hour': { + value: 9|settings.startHour, + min: 0, max: 24, + onchange: v => { + settings.startHour = v; + require("activityreminder").writeSettings(settings); + } + }, + 'End hour': { + value: 20|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, + onchange: v => { + settings.maxInnactivityMin = v; + require("activityreminder").writeSettings(settings); + } + }, + 'Dismiss delay': { + value: 10|settings.dismissDelayMin, + min: 5, max: 15, + onchange: v => { + settings.dismissDelayMin = v; + require("activityreminder").writeSettings(settings); + } + }, + 'Min steps': { + value: 50|settings.minSteps, + min: 10, max: 500, + onchange: v => { + settings.minSteps = v; + require("activityreminder").writeSettings(settings); + } + } + }); +}) diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index fcafc386f..e3b69e76e 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -18,3 +18,6 @@ 0.17: Moving alarm internals to 'sched' library 0.18: Cope with >1 identical alarm at once (#1667) 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 diff --git a/apps/alarm/app.js b/apps/alarm/app.js index b9404358e..15a4c3774 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -1,28 +1,11 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); -var alarms = require("sched").getAlarms(); // An array of alarm objects (see sched/README.md) - -// time in ms -> { hrs, mins } -function decodeTime(t) { - t = 0|t; // sanitise - var hrs = 0|(t/3600000); - return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) }; -} - -// time in { hrs, mins } -> ms -function encodeTime(o) { - return o.hrs*3600000 + o.mins*60000; -} - -function formatTime(t) { - var o = decodeTime(t); - return o.hrs+":"+("0"+o.mins).substr(-2); -} +let alarms = require("sched").getAlarms(); function getCurrentTime() { - var time = new Date(); + let time = new Date(); return ( time.getHours() * 3600000 + time.getMinutes() * 60000 + @@ -39,7 +22,7 @@ function showMainMenu() { // Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w") // Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA") const menu = { - '': { 'title': 'Alarm/Timer' }, + '': { 'title': /*LANG*/'Alarms&Timers' }, /*LANG*/'< Back' : ()=>{load();}, /*LANG*/'New Alarm': ()=>editAlarm(-1), /*LANG*/'New Timer': ()=>editTimer(-1) @@ -48,10 +31,10 @@ function showMainMenu() { var type,txt; // a leading space is currently required (JS error in Espruino 2v12) if (alarm.timer) { type = /*LANG*/"Timer"; - txt = " "+formatTime(alarm.timer); + txt = " "+require("sched").formatTime(alarm.timer); } else { type = /*LANG*/"Alarm"; - txt = " "+formatTime(alarm.t); + txt = " "+require("sched").formatTime(alarm.t); } if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA="); // rename duplicate alarms @@ -76,13 +59,13 @@ function showMainMenu() { function editDOW(dow, onchange) { const menu = { '': { 'title': /*LANG*/'Days of Week' }, - '< Back' : () => onchange(dow) + /*LANG*/'< Back' : () => onchange(dow) }; - for (var i = 0; i < 7; i++) (i => { - var dayOfWeek = require("locale").dow({ getDay: () => i }); + for (let i = 0; i < 7; i++) (i => { + let dayOfWeek = require("locale").dow({ getDay: () => i }); menu[dayOfWeek] = { value: !!(dow&(1< v ? "Yes" : "No", + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: v => v ? dow |= 1< showMainMenu(), + /*LANG*/'< Back' : () => showMainMenu(), /*LANG*/'Hours': { value: t.hrs, min : 0, max : 23, wrap : true, onchange: v => t.hrs=v @@ -117,27 +92,31 @@ function editAlarm(alarmIndex, alarm) { }, /*LANG*/'Enabled': { value: a.on, - format: v=>v?"On":"Off", + format: v => v ? /*LANG*/"On" : /*LANG*/"Off", onchange: v=>a.on=v }, /*LANG*/'Repeat': { value: a.rp, - format: v=>v?"Yes":"No", - onchange: v=>a.rp=v + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => a.rp = v }, /*LANG*/'Days': { value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)}) + onchange: () => editDOW(a.dow, d => { + a.dow = d; + a.t = encodeTime(t); + editAlarm(alarmIndex, a); + }) }, /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), - /*LANG*/'Auto snooze': { + /*LANG*/'Auto Snooze': { value: a.as, - format: v=>v?"Yes":"No", - onchange: v=>a.as=v + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => a.as = v } }; menu[/*LANG*/"Save"] = function() { - a.t = encodeTime(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; @@ -155,23 +134,15 @@ function editAlarm(alarmIndex, alarm) { } function editTimer(alarmIndex, alarm) { - var newAlarm = alarmIndex<0; - var a = { - timer : 5*60*1000, // 5 minutes - on : true, - rp : false, - as : false, - dow : 0b1111111, - last : 0, - vibrate : ".." - } + let newAlarm = alarmIndex < 0; + let a = require("sched").newDefaultTimer(); if (!newAlarm) Object.assign(a, alarms[alarmIndex]); if (alarm) Object.assign(a,alarm); - var t = decodeTime(a.timer); + let t = require("sched").decodeTime(a.timer); const menu = { '': { 'title': /*LANG*/'Timer' }, - '< Back' : () => showMainMenu(), + /*LANG*/'< Back' : () => showMainMenu(), /*LANG*/'Hours': { value: t.hrs, min : 0, max : 23, wrap : true, onchange: v => t.hrs=v @@ -182,13 +153,13 @@ function editTimer(alarmIndex, alarm) { }, /*LANG*/'Enabled': { value: a.on, - format: v=>v?"On":"Off", - onchange: v=>a.on=v + format: v => v ? /*LANG*/"On" : /*LANG*/"Off", + onchange: v => a.on = v }, /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), }; menu[/*LANG*/"Save"] = function() { - a.timer = encodeTime(t); + a.timer = require("sched").encodeTime(t); a.t = getCurrentTime() + a.timer; a.last = 0; if (newAlarm) alarms.push(a); diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 9636257ca..906df810f 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -1,8 +1,8 @@ { "id": "alarm", - "name": "Alarm & Timer", + "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.19", + "version": "0.22", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 46391d874..119cd2c2c 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -1,7 +1,7 @@ /* This rewrites boot0.js based on current settings. If settings changed then it recalculates, but this avoids us doing a whole bunch of reconfiguration most of the time. */ -E.showMessage("Updating boot0..."); +E.showMessage(/*LANG*/"Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 var boot = "", bootPost = ""; @@ -209,7 +209,7 @@ delete bootPost; delete bootFiles; delete fileSize; delete fileOffset; -E.showMessage("Reloading..."); +E.showMessage(/*LANG*/"Reloading..."); eval(require('Storage').read('.boot0')); // .bootcde should be run automatically after if required, since // we normally get called automatically from '.boot0' diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index 05a22774f..11569af0c 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -2,4 +2,5 @@ 0.02: Use build in function for steps and other improvements. 0.03: Adapt colors based on the theme of the user. 0.04: Steps can be hidden now such that the time is even larger. -0.05: Included icons for information. \ No newline at end of file +0.05: Included icons for information. +0.06: Design and usability improvements. \ No newline at end of file diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md index a5b66df71..f282bd187 100644 --- a/apps/bwclk/README.md +++ b/apps/bwclk/README.md @@ -1,13 +1,13 @@ -# Black & White clock +# BW Clock ![](screenshot.png) ## Features - Fullscreen on/off -- The design is adapted to the theme of your bangle. - Tab left/right of screen to show steps, temperature etc. - Enable / disable lock icon in the settings. - If the "sched" app is installed tab top / bottom of the screen to set the timer. +- The design is adapted to the theme of your bangle. ## Thanks to Icons created by Flaticon diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index d4e6c50ab..5240e69ec 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -99,21 +99,36 @@ var imgCharging = { buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA=")) }; +var imgWatch = { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC")) +}; /* - * Draw timeout + * INFO ENTRIES */ -// timeout used to update every minute -var drawTimeout; +var infoArray = [ + function(){ return [ null, null, "left" ] }, + function(){ return [ "Bangle", imgWatch, "right" ] }, + function(){ return [ E.getBattery() + "%", imgBattery, "left" ] }, + function(){ return [ getSteps(), imgSteps, "left" ] }, + function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm, "left"] }, + function(){ return [ getWeather().temp, imgTemperature, "left" ] }, + function(){ return [ getWeather().wind, imgWind, "left" ] }, +]; +const NUM_INFO=infoArray.length; -// schedule a draw for the next minute -function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); + +function getInfoEntry(){ + if(isAlarmEnabled()){ + return [getAlarmMinutes() + " min.", imgTimer, "left"] + } else if(Bangle.isCharging()){ + return [E.getBattery() + "%", imgCharging, "left"] + } else{ + return infoArray[settings.showInfo](); + } } @@ -121,19 +136,21 @@ function queueDraw() { * Helper */ function getSteps() { + var steps = 0; try{ if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); + steps = WIDGETS.wpedom.getSteps(); } else if (WIDGETS.activepedom !== undefined) { - return WIDGETS.activepedom.getSteps(); + steps = WIDGETS.activepedom.getSteps(); } else { - return Bangle.getHealthStatus("day").steps; + steps = Bangle.getHealthStatus("day").steps; } } catch(ex) { // In case we failed, we can only show 0 steps. } - return 0; + steps = Math.round(steps/100) / 10; // This ensures that we do not show e.g. 15.0k and 15k instead + return steps + "k"; } @@ -225,111 +242,109 @@ function decreaseAlarm(){ /* - * D R A W + * DRAW functions */ + function draw() { - // queue draw in one minute + // Queue draw again queueDraw(); - // Set info - var showInfo = settings.showInfo; - if(isAlarmEnabled()){ - showInfo = 100; - } - - if(Bangle.isCharging()){ - showInfo = 101; - } + // Draw clock + drawDate(); + drawTime(); + drawLock(); + drawWidgets(); +} +function drawDate(){ + // Draw background + var y = H/5*2 + (settings.fullscreen ? 0 : 8); + g.reset().clearRect(0,0,W,W); + + // Draw date + y -= settings.fullscreen ? 8 : 0; + var date = new Date(); + var dateStr = date.getDate(); + dateStr = ("0" + dateStr).substr(-2); + g.setMediumFont(); // Needed to compute the width correctly + var dateW = g.stringWidth(dateStr); + + g.setSmallFont(); + var dayStr = locale.dow(date, true); + var monthStr = locale.month(date, 1); + var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr)); + var fullDateW = dateW + 10 + dayW; + + g.setFontAlign(-1,1); + g.setMediumFont(); + g.setColor(g.theme.fg); + g.drawString(dateStr, W/2 - fullDateW / 2, y+5); + + g.setSmallFont(); + g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+3); + g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-23); +} + + +function drawTime(){ // Draw background - var yOffset = settings.fullscreen ? 0 : 10; - var y = H/5*2 + yOffset; - g.reset().clearRect(0,0,W,W); + var y = H/5*2 + (settings.fullscreen ? 0 : 8); g.setColor(g.theme.fg); g.fillRect(0,y,W,H); - - // Draw date - y -= settings.fullscreen ? 5 : 0; var date = new Date(); - g.setColor(g.theme.fg); - g.setFontAlign(1,1); - g.setMediumFont(); - var dateStr = date.getDate(); - dateStr = ("0" + dateStr).substr(-2); - g.drawString(dateStr, W/2-1, y+4); - - g.setSmallFont(); - g.setFontAlign(-1,1); - g.drawString(locale.dow(date, true), W/2 + 10, y-23); - g.drawString(locale.month(date, 1), W/2 + 10, y+1); // Draw time g.setColor(g.theme.bg); g.setFontAlign(0,-1); var timeStr = locale.time(date,1); - y += settings.fullscreen ? 20 : 10; + y += settings.fullscreen ? 14 : 10; - if(showInfo == 0){ - y += 8; + var infoEntry = getInfoEntry(); + var infoStr = infoEntry[0]; + var infoImg = infoEntry[1]; + var printImgLeft = infoEntry[2] == "left"; + + // Show large or small time depending on info entry + if(infoStr == null){ + y += 10; g.setLargeFont(); } else { g.setMediumFont(); } - g.drawString(timeStr, W/2, y); - // Draw info or timer - y += H/5*2-5; - g.setFontAlign(0,0); - if(showInfo > 0){ - g.setSmallFont(); - - var infoStr = ""; - var infoImg; - if(showInfo == 100){ - infoStr = getAlarmMinutes() + " min."; - infoImg = imgTimer; - } else if(showInfo == 101){ - infoStr = E.getBattery() + "%"; - infoImg = imgCharging; - } else if (showInfo == 1){ - infoStr = E.getBattery() + "%"; - infoImg = imgBattery; - } else if (showInfo == 2){ - infoStr = getSteps() - infoStr = Math.round(infoStr/100) / 10; // This ensures that we do not show e.g. 15.0k and 15k instead - infoStr = infoStr + "k"; - infoImg = imgSteps; - } else if (showInfo == 3){ - infoStr = Math.round(Bangle.getHealthStatus("day").bpm) + " bpm"; - infoImg = imgBpm; - } else if (showInfo == 4){ - var weather = getWeather(); - infoStr = weather.temp; - infoImg = imgTemperature; - } else if (showInfo == 5){ - var weather = getWeather(); - infoStr = weather.wind; - infoImg = imgWind; - } - - var imgWidth = 0; - if(infoImg !== undefined){ - imgWidth = infoImg.width; - var strWidth = g.stringWidth(infoStr); - g.drawImage(infoImg, W/2 - strWidth/2 - infoImg.width/2 - 5, y - infoImg.height/2); - } - g.drawString(infoStr, W/2 + imgWidth/2, y+3); + // Draw info if set + if(infoStr == null){ + return; } - // Draw lock + y += H/5*2-5; + g.setFontAlign(0,0); + g.setSmallFont(); + var imgWidth = 0; + if(infoImg !== undefined){ + imgWidth = infoImg.width; + var strWidth = g.stringWidth(infoStr); + g.drawImage( + infoImg, + W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - infoImg.width/2, + y - infoImg.height/2 + ); + } + g.drawString(infoStr, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3); +} + + +function drawLock(){ if(settings.showLock && Bangle.isLocked()){ g.setColor(g.theme.fg); g.drawImage(imgLock, W-16, 2); } +} - // Draw widgets if not fullscreen + +function drawWidgets(){ if(settings.fullscreen){ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } else { @@ -337,6 +352,27 @@ function draw() { } } + + +/* + * Draw timeout + */ +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +/* + * Load clock, widgets and listen for events + */ Bangle.loadWidgets(); // Clear the screen once, at startup and set the correct theme. @@ -381,32 +417,34 @@ Bangle.on('touch', function(btn, e){ if(is_upper){ Bangle.buzz(40, 0.6); increaseAlarm(); - draw(); + drawTime(); } if(is_lower){ Bangle.buzz(40, 0.6); decreaseAlarm(); - draw(); + drawTime(); } - var maxInfo = 6; if(is_right){ Bangle.buzz(40, 0.6); - settings.showInfo = (settings.showInfo+1) % maxInfo; - storage.write(SETTINGS_FILE, settings); - draw(); + settings.showInfo = (settings.showInfo+1) % NUM_INFO; + drawTime(); } if(is_left){ Bangle.buzz(40, 0.6); settings.showInfo = settings.showInfo-1; - settings.showInfo = settings.showInfo < 0 ? maxInfo-1 : settings.showInfo; - storage.write(SETTINGS_FILE, settings); - draw(); + settings.showInfo = settings.showInfo < 0 ? NUM_INFO-1 : settings.showInfo; + drawTime(); } }); +E.on("kill", function(){ + storage.write(SETTINGS_FILE, settings); +}); + + // Show launcher when middle button pressed Bangle.setUI("clock"); diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index babaa37bf..8b13cd256 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,11 +1,11 @@ { "id": "bwclk", - "name": "BlackWhite Clock", - "version": "0.05", - "description": "Black and white clock.", + "name": "BW Clock", + "version": "0.06", + "description": "BW Clock.", "readme": "README.md", "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}], + "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png index 02ef7a704..b30ba4166 100644 Binary files a/apps/bwclk/screenshot.png and b/apps/bwclk/screenshot.png differ diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png index 2f31c8b3c..ea2dc780b 100644 Binary files a/apps/bwclk/screenshot_2.png and b/apps/bwclk/screenshot_2.png differ diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png new file mode 100644 index 000000000..fb5b153b8 Binary files /dev/null and b/apps/bwclk/screenshot_3.png differ diff --git a/apps/bwclk/settings.js b/apps/bwclk/settings.js index 82d1cad0b..0fdaf1a28 100644 --- a/apps/bwclk/settings.js +++ b/apps/bwclk/settings.js @@ -18,7 +18,7 @@ E.showMenu({ - '': { 'title': 'BlackWhite Clock' }, + '': { 'title': 'BW Clock' }, '< Back': back, 'Fullscreen': { value: settings.fullscreen, diff --git a/apps/choozi/ChangeLog b/apps/choozi/ChangeLog index 5560f00bc..03f7ef832 100644 --- a/apps/choozi/ChangeLog +++ b/apps/choozi/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Support Bangle.js 2 +0.03: Fix bug for Bangle.js 2 where g.flip was not being called. diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js new file mode 100644 index 000000000..5f217f638 --- /dev/null +++ b/apps/choozi/appb2.js @@ -0,0 +1,207 @@ +/* Choozi - Choose people or things at random using Bangle.js. + * Inspired by the "Chwazi" Android app + * + * James Stanley 2021 + */ + +var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff']; + +var stepAngle = 0.18; // radians - resolution of polygon +var gapAngle = 0.035; // radians - gap between segments +var perimMin = 80; // px - min. radius of perimeter +var perimMax = 87; // px - max. radius of perimeter + +var segmentMax = 70; // px - max radius of filled-in segment +var segmentStep = 5; // px - step size of segment fill animation +var circleStep = 4; // px - step size of circle fill animation + +// rolling ball animation: +var maxSpeed = 0.08; // rad/sec +var minSpeed = 0.001; // rad/sec +var animStartSteps = 300; // how many steps before it can start slowing? +var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate +var ballSize = 3; // px - ball radius +var ballTrack = 75; // px - radius of ball path + +var centreX = 88; // px - centre of screen +var centreY = 88; // px - centre of screen + +var fontSize = 50; // px + +var radians = 2*Math.PI; // radians per circle + +var defaultN = 3; // default value for N +var minN = 2; +var maxN = colours.length; +var N; +var arclen; + +// https://www.frankmitchell.org/2015/01/fisher-yates/ +function shuffle (array) { + var i = 0 + , j = 0 + , temp = null; + + for (i = array.length - 1; i > 0; i -= 1) { + j = Math.floor(Math.random() * (i + 1)); + temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} + +// draw an arc between radii minR and maxR, and between +// angles minAngle and maxAngle +function arc(minR, maxR, minAngle, maxAngle) { + var step = stepAngle; + var angle = minAngle; + var inside = []; + var outside = []; + var c, s; + while (angle < maxAngle) { + c = Math.cos(angle); + s = Math.sin(angle); + inside.push(centreX+c*minR); // x + inside.push(centreY+s*minR); // y + // outside coordinates are built up in reverse order + outside.unshift(centreY+s*maxR); // y + outside.unshift(centreX+c*maxR); // x + angle += step; + } + c = Math.cos(maxAngle); + s = Math.sin(maxAngle); + inside.push(centreX+c*minR); + inside.push(centreY+s*minR); + outside.unshift(centreY+s*maxR); + outside.unshift(centreX+c*maxR); + + var vertices = inside.concat(outside); + g.fillPoly(vertices, true); +} + +// draw the arc segments around the perimeter +function drawPerimeter() { + g.clear(); + for (var i = 0; i < N; i++) { + g.setColor(colours[i%colours.length]); + var minAngle = (i/N)*radians; + arc(perimMin,perimMax,minAngle,minAngle+arclen); + } +} + +// animate a ball rolling around and settling at "target" radians +function animateChoice(target) { + var angle = 0; + var speed = 0; + var oldx = -10; + var oldy = -10; + var decelFromAngle = -1; + var allowDecel = false; + for (var i = 0; true; i++) { + angle = angle + speed; + if (angle > radians) angle -= radians; + if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) { + speed = speed + accel; + if (speed > maxSpeed) { + speed = maxSpeed; + /* when we reach max speed, we know how long it takes + * to accelerate, and therefore how long to decelerate, so + * we can work out what angle to start decelerating from */ + if (decelFromAngle < 0) { + decelFromAngle = target-angle; + while (decelFromAngle < 0) decelFromAngle += radians; + while (decelFromAngle > radians) decelFromAngle -= radians; + } + } + } else { + if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true; + if (allowDecel) speed = speed - accel; + if (speed < minSpeed) speed = minSpeed; + if (speed == minSpeed && angle < target && angle+speed >= target) return; + } + + var r = i/2; + if (r > ballTrack) r = ballTrack; + var x = centreX+Math.cos(angle)*r; + var y = centreY+Math.sin(angle)*r; + g.setColor('#000000'); + g.fillCircle(oldx,oldy,ballSize+1); + g.setColor('#ffffff'); + g.fillCircle(x, y, ballSize); + oldx=x; + oldy=y; + g.flip(); + } +} + +// choose a winning segment and animate its selection +function choose() { + var chosen = Math.floor(Math.random()*N); + var minAngle = (chosen/N)*radians; + var maxAngle = minAngle + arclen; + animateChoice((minAngle+maxAngle)/2); + g.setColor(colours[chosen%colours.length]); + for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep) + arc(i, perimMax, minAngle, maxAngle); + arc(0, perimMax, minAngle, maxAngle); + for (var r = 1; r < segmentMax; r += circleStep) + g.fillCircle(centreX,centreY,r); + g.fillCircle(centreX,centreY,segmentMax); +} + +// draw the current value of N in the middle of the screen, with +// up/down arrows +function drawN() { + g.setColor(g.theme.fg); + g.setFont("Vector",fontSize); + g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2); + if (N < maxN) + g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]); + if (N > minN) + g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]); +} + +// update number of segments, with min/max limit, "arclen" update, +// and screen reset +function setN(n) { + N = n; + if (N < minN) N = minN; + if (N > maxN) N = maxN; + arclen = radians/N - gapAngle; + drawPerimeter(); +} + +// save N to choozi.txt +function writeN() { + var file = require("Storage").open("choozi.txt","w"); + file.write(N); +} + +// load N from choozi.txt +function readN() { + var file = require("Storage").open("choozi.txt","r"); + var n = file.readLine(); + if (n !== undefined) setN(parseInt(n)); + else setN(defaultN); +} + +shuffle(colours); // is this really best? +Bangle.setLCDTimeout(0); // keep screen on +readN(); +drawN(); + +setWatch(() => { + writeN(); + drawPerimeter(); + choose(); +}, BTN1, {repeat:true}); + +Bangle.on('touch', function(zone,e) { + if(e.x>+88){ + setN(N-1); + drawN(); + }else{ + setN(N+1); + drawN(); + } +}); diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json index b75ef062a..79af76fa2 100644 --- a/apps/choozi/metadata.json +++ b/apps/choozi/metadata.json @@ -1,16 +1,17 @@ { "id": "choozi", "name": "Choozi", - "version": "0.01", + "version": "0.03", "description": "Choose people or things at random using Bangle.js.", "icon": "app.png", "tags": "tool", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}], "storage": [ - {"name":"choozi.app.js","url":"app.js"}, + {"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]}, + {"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]}, {"name":"choozi.img","url":"app-icon.js","evaluate":true} ] } diff --git a/apps/dane_tcr/app.js b/apps/dane_tcr/app.js index aa25379d3..ce75c55cb 100644 --- a/apps/dane_tcr/app.js +++ b/apps/dane_tcr/app.js @@ -244,7 +244,7 @@ function run(){ Bangle.setLCDMode(); g.clear(); g.flip(); - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); load(app.src); } diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index da07af798..66fda2f29 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -9,3 +9,4 @@ 0.09: fix the trasparent widget bar if there are no widgets for Bangle 2 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. diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index e0f7f825f..7202e4f33 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -85,18 +85,25 @@ function drawPage(p){ g.flip(); } -Bangle.on("swipe",(dir)=>{ +Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ selected = 0; oldselected=-1; - if (dir<0){ + if (dirUpDown==-1){ ++page; if (page>maxPage) page=0; drawPage(page); - } else { + } else if (dirUpDown==1){ --page; if (page<0) page=maxPage; drawPage(page); - } + } + if (dirLeftRight==1) showClock(); }); +function showClock(){ + var app = require("Storage").readJSON('setting.json', 1).clock; + if (app) load(app); + else E.showMessage("clock\nnot found"); +} + function isTouched(p,n){ if (n<0 || n>3) return false; var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index b3f94442f..a5391cef0 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.11", + "version": "0.12", "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/golfview/ChangeLog b/apps/golfview/ChangeLog new file mode 100644 index 000000000..b243db101 --- /dev/null +++ b/apps/golfview/ChangeLog @@ -0,0 +1 @@ +0.01: New App! Very limited course support. diff --git a/apps/golfview/LICENSE b/apps/golfview/LICENSE new file mode 100644 index 000000000..18c8d5fc3 --- /dev/null +++ b/apps/golfview/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Jason Dekarske + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/apps/golfview/README.md b/apps/golfview/README.md new file mode 100644 index 000000000..41aae02a0 --- /dev/null +++ b/apps/golfview/README.md @@ -0,0 +1,33 @@ +# Golf View + +This app leverages open source map data to give you a birds eye view of your golf game! See a preview of any hole as well as your realtime distance to the green and position on the hole. + +![hole3](screenshot.png) + +## Usage + +Swipe left and right to select your hole. Use the GPS assist app to get a faster GPS fix. + +## Contributions + +The performance of this app depends on the accuracy and consistency of user-submitted maps. + +- See official mapping guidelines [here](https://wiki.openstreetmap.org/wiki/Tag:leisure%3Dgolf_course). +- All holes and features must be within the target course's area. +- Supported features are greens, fairways, tees, bunkers, water hazards and holes. +- All features for a given hole should have the "ref" tag with the hole number as value. Shared features should list ref values separated by ';'. [example](https://www.openstreetmap.org/way/36896320). +- here must be 18 holes and they must have the following tags: handicap, par, ref, dist. +- For any mapping assistance or issues, please file in the official repo. + +[Example Course](https://www.openstreetmap.org/way/25447898) +## Controls + +Swipe to change holes and tap to see a green closeup. + +## Requests/Creator + +[Jason Dekarske](https://github.com/jdekarske) + +## Attribution + +[© OpenStreetMap contributors](https://www.openstreetmap.org/copyright) diff --git a/apps/golfview/custom.html b/apps/golfview/custom.html new file mode 100644 index 000000000..94bc551c0 --- /dev/null +++ b/apps/golfview/custom.html @@ -0,0 +1,154 @@ + + + + + + + + + +
+ + +

+
+ + +
+

A course needs a few things to be parsed correctly by this tool.

+
    +
  • See official mapping guidelines here.
  • +
  • All holes and features must be within the target course's area.
  • +
  • Supported features are greens, fairways, tees, bunkers, water hazards and holes.
  • +
  • All features for a given hole should have the "ref" tag with the hole number as value. Shared features should + list ref values separated by ';'. example.
  • +
  • There must be 18 holes and they must have the following tags: handicap, par, ref, dist
  • +
  • For any mapping assistance or issues, please file in the official + repo
  • +
+ Example Course + © OpenStreetMap contributors

+
+ + + + + + + + \ No newline at end of file diff --git a/apps/golfview/golfview-icon.js b/apps/golfview/golfview-icon.js new file mode 100644 index 000000000..71049197c --- /dev/null +++ b/apps/golfview/golfview-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEowcF23btoCRpMkyVKBxHayQOCpNbBw/UBwkrBw21BYQCCkoOIy/JCIetI4+X+QgEBxNIBxFqBxFWBxAsEBxHpBYPbsgOFqQOEy3btgOLDoIOrrYOKMoQOKOgYsKBx1KBwaGBWYgOBkVJBwKGBYQwOBiVJlIdCkmVBxdZfwwOGF4IONkoLCB2J3BBxkgQwQOKWYlJlYOUtQORtskzQOHtoOE7QOLAQdbBw21BydKBYgCD6gODBYwCNA")) diff --git a/apps/golfview/golfview.js b/apps/golfview/golfview.js new file mode 100644 index 000000000..62d6f4096 --- /dev/null +++ b/apps/golfview/golfview.js @@ -0,0 +1,212 @@ +// maptools.js +const EARTHRADIUS = 6371000; //km + +function radians(a) { + return a * Math.PI / 180; +} + +function degrees(a) { + let d = a * 180 / Math.PI; + return (d + 360) % 360; +} + +function toXY(a, origin) { + let pt = { + x: 0, + y: 0 + }; + + pt.x = EARTHRADIUS * radians(a.lon - origin.lon) * Math.cos(radians((a.lat + origin.lat) / 2)); + pt.y = EARTHRADIUS * radians(origin.lat - a.lat); + return pt; +} + +function arraytoXY(array, origin) { + let out = []; + for (var j in array) { + let newpt = toXY(array[j], origin); + out.push(newpt); + } + return out; +} + +function angle(a, b) { + let x = b.x - a.x; + let y = b.y - a.y; + return Math.atan2(-y, x); +} + +function rotateVec(a, theta) { + let pt = { + x: 0, + y: 0 + }; + c = Math.cos(theta); + s = Math.sin(theta); + pt.x = c * a.x - s * a.y; + pt.y = s * a.x + c * a.y; + return pt; +} + +function distance(a, b) { + return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)); +} + + +// golfview.js +let course = require("Storage").readJSON("golfcourse-Davis.json").holes;//TODO use the course ID +let current_hole = 1; +let hole = course[current_hole.toString()]; +let user_position = { + fix: false, + lat: 0, + lon: 0, + x: 0, + y: 0, + to_hole: 0, + last_time: getTime(), + transform: {}, +}; + +function drawUser() { + if(!user_position.fix) return; + let new_pos = g.transformVertices([user_position.x,user_position.y],user_position.transform); + g.setColor(g.theme.fg); + g.drawCircle(new_pos[0],new_pos[1],8); +} + +function drawHole(l) { + + //console.log(l); + let hole_straight_distance = distance( + hole.nodesXY[0], + hole.nodesXY[hole.nodesXY.length - 1] + ); + + let scale = 0.9 * l.h / hole_straight_distance; + + let transform = { + x: l.x + l.w / 2, // center in the box + y: l.h * 0.95, // pad it just a bit TODO use the extent of the teeboxes/green + scale: scale, // scale factor (default 1) + rotate: hole.angle - Math.PI / 2.0, // angle in radians (default 0) + }; + + user_position.transform = transform; + + // draw the fairways first + hole.features.sort((a, b) => { + if (a.type === "fairway") { + return -1; + } + }); + + for (var feature of hole.features) { + //console.log(Object.keys(feature)); + if (feature.type === "fairway") { + g.setColor(1, 0, 1); // magenta + } else if (feature.type === "tee") { + g.setColor(1, 0, 0); // red + } else if (feature.type === "green") { + g.setColor(0, 1, 0); // green + } else if (feature.type === "bunker") { + g.setColor(1, 1, 0); // yellow + } else if (feature.type === "water_hazard") { + g.setColor(0, 0, 1); // blue + } else { + continue; + } + + var nodelist = []; + feature.nodesXY.forEach(function (node) { + nodelist.push(node.x); + nodelist.push(node.y); + }); + newnodelist = g.transformVertices(nodelist, transform); + + g.fillPoly(newnodelist, true); + //console.log(feature.type); + //console.log(newnodelist); + + drawUser(); + } + + var waynodelist = []; + hole.nodesXY.forEach(function (node) { + waynodelist.push(node.x); + waynodelist.push(node.y); + }); + + newnodelist = g.transformVertices(waynodelist, transform); + g.setColor(0, 1, 1); // cyan + g.drawPoly(newnodelist); +} + +function setHole(current_hole) { + layout.hole.label = "HOLE " + current_hole; + layout.par.label = "PAR " + course[current_hole.toString()].par; + layout.hcp.label = "HCP " + course[current_hole.toString()].handicap; + layout.postyardage.label = course[current_hole.toString()].tees[course[current_hole.toString()].tees.length - 1]; //TODO only use longest hole for now + + g.clear(); + layout.render(); +} + +function updateDistanceToHole() { + let xy = toXY({ "lat": user_position.lat, "lon": user_position.lon }, hole.way[0]); + user_position.x = xy.x; + user_position.y = xy.y; + user_position.last_time = getTime(); + let new_distance = Math.round(distance(xy, hole.nodesXY[hole.nodesXY.length - 1]) * 1.093613); //TODO meters later + //console.log(new_distance); + layout.measyardage.label = (new_distance < 999) ? new_distance : "---"; + + g.clear(); + layout.render(); +} + +Bangle.on('swipe', function (direction) { + if (direction > 0) { + current_hole--; + } else { + current_hole++; + } + + if (current_hole > 18) { current_hole = 1; } + if (current_hole < 1) { current_hole = 18; } + hole = course[current_hole.toString()]; + + setHole(current_hole); +}); + +Bangle.on('GPS', (fix) => { + if (isNaN(fix.lat)) return; + //console.log(fix.hdop * 5); //precision + user_position.fix = true; + user_position.lat = fix.lat; + user_position.lon = fix.lon; + updateDistanceToHole(); + drawUser(); +}); + +// The layout, referencing the custom renderer +var Layout = require("Layout"); +var layout = new Layout({ + type: "h", c: [ + { + type: "v", c: [ + { type: "txt", font: "10%", id: "hole", label: "HOLE 18" }, + { type: "txt", font: "10%", id: "par", label: "PAR 4" }, + { type: "txt", font: "10%", id: "hcp", label: "HCP 18" }, + { type: "txt", font: "35%", id: "postyardage", label: "---" }, + { type: "txt", font: "20%", id: "measyardage", label: "---" }, + ] + }, + { type: "custom", render: drawHole, id: "graph", bgCol: g.theme.bg, fillx: 1, filly: 1 } + ], + lazy: true +}); + +Bangle.setGPSPower(1); +setHole(current_hole); +//layout.debug(); diff --git a/apps/golfview/golfview.png b/apps/golfview/golfview.png new file mode 100644 index 000000000..dfadeb5fa Binary files /dev/null and b/apps/golfview/golfview.png differ diff --git a/apps/golfview/maptools.js b/apps/golfview/maptools.js new file mode 100644 index 000000000..e475c798b --- /dev/null +++ b/apps/golfview/maptools.js @@ -0,0 +1,63 @@ +const EARTHRADIUS = 6371000; //km + +function radians(a) { + return a * Math.PI / 180; +} + +function degrees(a) { + let d = a * 180 / Math.PI; + return (d + 360) % 360; +} + +function toXY(a, origin) { + let pt = { + x: 0, + y: 0 + }; + + pt.x = EARTHRADIUS * radians(a.lon - origin.lon) * Math.cos(radians((a.lat + origin.lat) / 2)); + pt.y = EARTHRADIUS * radians(origin.lat - a.lat); + return pt; +} + +function arraytoXY(array, origin) { + let out = []; + for (var j in array) { + let newpt = toXY(array[j], origin); + out.push(newpt); + } + return out; +} + +function angle(a, b) { + let x = b.x - a.x; + let y = b.y - a.y; + return Math.atan2(-y, x); +} + +function rotateVec(a, theta) { + let pt = { + x: 0, + y: 0 + }; + c = Math.cos(theta); + s = Math.sin(theta); + pt.x = c * a.x - s * a.y; + pt.y = s * a.x + c * a.y; + return pt; +} + +function distance(a,b) { + return Math.sqrt(Math.pow(a.x-b.x,2) + Math.pow(a.y-b.y,2)) +} + +// https://stackoverflow.com/questions/19721439/download-json-object-as-a-file-from-browser +function downloadObjectAsJSON(exportObj, exportName) { + var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(exportObj); // must be stringified!! + var downloadAnchorNode = document.createElement('a'); + downloadAnchorNode.setAttribute("href", dataStr); + downloadAnchorNode.setAttribute("download", exportName + ".json"); + document.body.appendChild(downloadAnchorNode); // required for firefox + downloadAnchorNode.click(); + downloadAnchorNode.remove(); +} \ No newline at end of file diff --git a/apps/golfview/metadata.json b/apps/golfview/metadata.json new file mode 100644 index 000000000..961d24e4d --- /dev/null +++ b/apps/golfview/metadata.json @@ -0,0 +1,15 @@ +{ "id": "golfview", + "name": "Golf View", + "version":"0.01", + "description": "This app will provide you with on course data to support your golf game!", + "icon": "golfview.png", + "tags": "outdoors, gps", + "allow_emulator": true, + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "custom": "custom.html", + "storage": [ + {"name":"golfview.app.js","url":"golfview.js"}, + {"name":"golfview.img","url":"golfview-icon.js","evaluate":true} + ] +} diff --git a/apps/golfview/screenshot.png b/apps/golfview/screenshot.png new file mode 100644 index 000000000..983b19ece Binary files /dev/null and b/apps/golfview/screenshot.png differ diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js index 0eca2ccf5..a6e21af0d 100644 --- a/apps/gpsinfo/gps-info.js +++ b/apps/gpsinfo/gps-info.js @@ -5,7 +5,7 @@ function satelliteImage() { var Layout = require("Layout"); var layout; //Bangle.setGPSPower(1, "app"); -E.showMessage("Loading..."); // avoid showing rubbish on screen +E.showMessage(/*LANG*/"Loading..."); // avoid showing rubbish on screen var lastFix = { fix: -1, diff --git a/apps/gpsrec/app.js b/apps/gpsrec/app.js index 527eb780d..4595f616d 100644 --- a/apps/gpsrec/app.js +++ b/apps/gpsrec/app.js @@ -126,7 +126,7 @@ function asTime(v){ function viewTrack(n, info) { if (!info) { - E.showMessage("Loading...","GPS Track "+n); + E.showMessage(/*LANG*/"Loading...","GPS Track "+n); info = getTrackInfo(n); } const menu = { diff --git a/apps/gpstime/gpstime.js b/apps/gpstime/gpstime.js index 8c80953fa..6a01821ba 100644 --- a/apps/gpstime/gpstime.js +++ b/apps/gpstime/gpstime.js @@ -10,7 +10,7 @@ var Layout = require("Layout"); Bangle.setGPSPower(1, "app"); Bangle.loadWidgets(); Bangle.drawWidgets(); -E.showMessage("Loading..."); // avoid showing rubbish on screen +E.showMessage(/*LANG*/"Loading..."); // avoid showing rubbish on screen function setGPSTime() { if (fix.time!==undefined) { diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 1e4864af8..7dbb9c458 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -10,3 +10,4 @@ 0.09: Fix file naming so months are 1-based (not 0) (fix #1119) 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 diff --git a/apps/health/README.md b/apps/health/README.md index f44854e3e..c6b379c0a 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -24,6 +24,7 @@ Stores: * **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget * **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found * **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours) +* **Daily Step Goal** - Default 10000, daily step goal for pedometer apps to use ## Technical Info diff --git a/apps/health/app.js b/apps/health/app.js index 7a55eec27..e39590e2d 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -2,8 +2,8 @@ function getSettings() { return require("Storage").readJSON("health.json",1)||{}; } -function setSettings(s) { - require("Storage").writeJSON("health.json",s); +function setSettings(healthSettings) { + require("Storage").writeJSON("health.json",healthSettings); } function menuMain() { @@ -22,15 +22,21 @@ function menuMain() { function menuSettings() { swipe_enabled = false; clearButton(); - var s=getSettings(); + var healthSettings=getSettings(); + //print(healthSettings); E.showMenu({ "":{title:"Health Tracking"}, "< Back":()=>menuMain(), "Heart Rt":{ - value : 0|s.hrm, + value : 0|healthSettings.hrm, min : 0, max : 3, format : v=>["Off","3 mins","10 mins","Always"][v], - onchange : v => { s.hrm=v;setSettings(s); } + 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); } } }); } @@ -70,7 +76,7 @@ function menuHRM() { function stepsPerHour() { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); g.clear(1); @@ -81,7 +87,7 @@ function stepsPerHour() { } function stepsPerDay() { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); g.clear(1); @@ -92,7 +98,7 @@ function stepsPerDay() { } function hrmPerHour() { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var data = new Uint16Array(24); var cnt = new Uint8Array(23); require("health").readDay(new Date(), h=>{ @@ -108,7 +114,7 @@ function hrmPerHour() { } function hrmPerDay() { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var data = new Uint16Array(31); var cnt = new Uint8Array(31); require("health").readDailySummaries(new Date(), h=>{ @@ -124,7 +130,7 @@ function hrmPerDay() { } function movementPerHour() { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.movement); g.clear(1); @@ -135,7 +141,7 @@ function movementPerHour() { } function movementPerDay() { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); g.clear(1); @@ -199,7 +205,7 @@ function drawBarChart() { for (bar = 1; bar < 10; bar++) { if (bar == 5) { g.setFont('6x8', 2); - g.setFontAlign(0,-1) + g.setFontAlign(0,-1); g.setColor(g.theme.fg); g.drawString(chart_label + " " + (chart_index + bar -1) + " " + chart_data[chart_index + bar - 1], g.getWidth()/2, 150); g.setColor("#00f"); diff --git a/apps/health/metadata.json b/apps/health/metadata.json index 8bb986c57..5d096dc07 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -1,7 +1,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.11", + "version": "0.12", "description": "Logs health data and provides an app to view it", "icon": "app.png", "tags": "tool,system,health", diff --git a/apps/heartzone/ChangeLog b/apps/heartzone/ChangeLog new file mode 100644 index 000000000..cc2219306 --- /dev/null +++ b/apps/heartzone/ChangeLog @@ -0,0 +1 @@ +1.0: Initial release. diff --git a/apps/heartzone/README.md b/apps/heartzone/README.md new file mode 100644 index 000000000..4de60355d --- /dev/null +++ b/apps/heartzone/README.md @@ -0,0 +1,35 @@ +# HeartZone + +HeartZone continuously monitors your heart rate. If your heart rate is outside of your configured limits, you get a configurable buzz. + +Inspired by [Workout HRM](https://github.com/espruino/BangleApps/tree/master/apps/wohrm), but I wanted the following features: + +* Larger text, more contrast, and color-coding for better readability while exercising. +* Configurable buzz interval, instead of at every heart rate reading (which was too distracting). +* Pause for a rest and resume afterwards without having to restart the heart rate sensor (which takes several seconds each time to stabilize). +* Configure the minimum heart rate confidence threshold (bad readings cause buzzes that have to be ignored). + +However, compared to Workout HRM, HeartZone doesn't support: + +* In-app configuration of the heart rate thresholds - you can only do it in the Settings app. +* Bangle.js 1 - this only supports Bangle.js 2. + +## Usage + +When you first start the app, it will begin displaying your heart rate after a few seconds. Until the heart rate confidence is above your configured minimum confidence, the background will be colored red: + +![Start screen](screenshots/start.png) + +After the heart rate confidence is at an acceptable level, the background will be colored white, and you will receive buzzes on your wrist while your heart rate is out of the configured range. By default, the BPM-too-low buzz is 200ms, while the BPM-too-high buzz is 1000ms: + +![Screen while we are monitoring](screenshots/running.png) + +If you're taking a break, swipe down to turn off the buzzes while continuing to measure and display your heart rate (swipe up again to end your break): + +![Screen while we are paused](screenshots/paused.png) + +When you're done, simply press the side button to exit the app. + +## Creator + +[Uberi](https://github.com/Uberi) diff --git a/apps/heartzone/app-icon.js b/apps/heartzone/app-icon.js new file mode 100644 index 000000000..2e3692455 --- /dev/null +++ b/apps/heartzone/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBiIA/AHqFCAxQWJ5gABCIQGGABEQB4QABgIGGC5MMCAnAAwwuOABAwIC64/FABBIIC68ADBnAVJEP+AXLBoJ2H/4XN/54GBAIXOGAouBBAMAABQXBGAoHCAB4wDFwQARGAYvWL7CPDbBXAR46/DiAXJgK/Id4URGBHABobwHEAIwIBQQuHAAcYGA3AwIUKC4eAC4sIC5+IGAnAxAXQkAXDgQXRkQwC4EiC6QwCgQXTl0M4HiC6nghwXV93uC9MRC44WOGAIXFFx4ABC4oWQiMSC4chC6MRC4YWSiMeC4PhC6cRC4IWUGAIuVAH4AVA=")) diff --git a/apps/heartzone/app.js b/apps/heartzone/app.js new file mode 100644 index 000000000..9ec3f005d --- /dev/null +++ b/apps/heartzone/app.js @@ -0,0 +1,87 @@ +// clear screen and draw widgets +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var statusRect = {x: Bangle.appRect.x, y: Bangle.appRect.y, w: Bangle.appRect.w, h: 32}; +var settingsRect = {x: Bangle.appRect.x, y: Bangle.appRect.y2 - 16, w: Bangle.appRect.w, h: 16}; +var hrmRect = {x: Bangle.appRect.x, y: statusRect.y + statusRect.h, w: Bangle.appRect.w, h: Bangle.appRect.h - statusRect.h - settingsRect.h}; + +var isPaused = false; +var settings = Object.assign({ + minBpm: 120, + maxBpm: 160, + minConfidence: 60, + minBuzzIntervalSeconds: 5, + tooLowBuzzDurationMillis: 200, + tooHighBuzzDurationMillis: 1000, +}, require('Storage').readJSON("heartzone.settings.json", true) || {}); + +// draw current settings at the bottom +g.setFont6x15(1).setFontAlign(0, -1, 0); +g.drawString(settings.minBpm + "=" + settings.minConfidence + "% conf.", settingsRect.x + (settingsRect.w / 2), settingsRect.y + 4); + +function drawStatus(status) { // draw status bar at the top + g.setBgColor(g.theme.bg).setColor(g.theme.fg); + g.clearRect(statusRect); + + g.setFontVector(statusRect.h - 4).setFontAlign(0, -1, 0); + g.drawString(status, statusRect.x + (statusRect.w / 2), statusRect.y + 2); +} + +function drawHRM(hrmInfo) { // draw HRM info display + g.setBgColor(hrmInfo.confidence > settings.minConfidence ? '#fff' : '#f00').setColor(hrmInfo.confidence > settings.minConfidence ? '#000' : '#fff'); + g.setFontAlign(-1, -1, 0); + g.clearRect(hrmRect); + + var px = hrmRect.x + 10, py = hrmRect.y + 10; + g.setFontVector((hrmRect.h / 2) - 20); + g.drawString(hrmInfo.bpm, px, py); + g.setFontVector(16); + g.drawString('BPM', px + g.stringWidth(hrmInfo.bpm.toString()) + 32, py); + py += hrmRect.h / 2; + + g.setFontVector((hrmRect.h / 2) - 20); + g.drawString(hrmInfo.confidence, px, py); + g.setFontVector(16); + g.drawString('% conf.', px + g.stringWidth(hrmInfo.confidence.toString()) + 32, py); +} + +drawHRM({bpm: '?', confidence: '?'}); +drawStatus('RUNNING'); + +var lastBuzz = getTime(); +Bangle.on('HRM', function(hrmInfo) { + if (!isPaused) { + var currentTime; + if (hrmInfo.confidence > settings.minConfidence) { + if (hrmInfo.bpm < settings.minBpm) { + currentTime = getTime(); + if (currentTime - lastBuzz > settings.minBuzzIntervalSeconds) { + lastBuzz = currentTime; + Bangle.buzz(settings.tooLowBuzzDurationMillis); + } + } else if (hrmInfo.bpm > settings.maxBpm) { + currentTime = getTime(); + if (currentTime - lastBuzz > minBuzzIntervalSeconds) { + lastBuzz = currentTime; + Bangle.buzz(settings.tooHighBuzzDurationMillis); + } + } + } + } + drawHRM(hrmInfo); +}); + +Bangle.setUI('updown', function(action) { + if (action == -1) { // up + isPaused = false; + drawStatus("RUNNING"); + } else if (action == 1) { // down + isPaused = true; + drawStatus("PAUSED"); + } +}); +setWatch(function() { Bangle.setHRMPower(false, "heartzone"); load(); }, BTN1); + +Bangle.setHRMPower(true, "heartzone"); diff --git a/apps/heartzone/icon.png b/apps/heartzone/icon.png new file mode 100644 index 000000000..60ece4b32 Binary files /dev/null and b/apps/heartzone/icon.png differ diff --git a/apps/heartzone/metadata.json b/apps/heartzone/metadata.json new file mode 100644 index 000000000..e243063a2 --- /dev/null +++ b/apps/heartzone/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "heartzone", + "name": "HeartZone", + "version": "1.0", + "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": [ + {"url": "screenshots/start.png"}, + {"url": "screenshots/running.png"}, + {"url": "screenshots/paused.png"} + ], + "icon": "icon.png", + "tags": "health", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"heartzone.app.js","url":"app.js"}, + {"name":"heartzone.settings.js","url":"settings.js"}, + {"name":"heartzone.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"heartzone.settings.json"}] +} diff --git a/apps/heartzone/screenshots/paused.png b/apps/heartzone/screenshots/paused.png new file mode 100644 index 000000000..5f89ed076 Binary files /dev/null and b/apps/heartzone/screenshots/paused.png differ diff --git a/apps/heartzone/screenshots/running.png b/apps/heartzone/screenshots/running.png new file mode 100644 index 000000000..a514edff5 Binary files /dev/null and b/apps/heartzone/screenshots/running.png differ diff --git a/apps/heartzone/screenshots/start.png b/apps/heartzone/screenshots/start.png new file mode 100644 index 000000000..195957fa0 Binary files /dev/null and b/apps/heartzone/screenshots/start.png differ diff --git a/apps/heartzone/settings.js b/apps/heartzone/settings.js new file mode 100644 index 000000000..64030b0a3 --- /dev/null +++ b/apps/heartzone/settings.js @@ -0,0 +1,27 @@ +(function(back) { + var FILE = "heartzone.settings.json"; + var settings = Object.assign({ + minBpm: 120, + maxBpm: 160, + minConfidence: 60, + minBuzzIntervalSeconds: 5, + tooLowBuzzDurationMillis: 200, + tooHighBuzzDurationMillis: 1000, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "HeartZone" }, + "< Save & Return" : () => { writeSettings(); back(); }, + 'Min BPM': {value: 0 | settings.minBpm, min: 80, max: 200, step: 10, onchange: v => { settings.minBpm = v; }}, + 'Max BPM': {value: 0 | settings.maxBpm, min: 80, max: 200, step: 10, onchange: v => { settings.maxBpm = v; }}, + 'Min % conf.': {value: 0 | settings.minConfidence, min: 30, max: 100, step: 5, onchange: v => { settings.minConfidence = v; }}, + 'Min buzz int. (sec)': {value: 0 | settings.minBuzzIntervalSeconds, min: 1, max: 30, onchange: v => { settings.minBuzzIntervalSeconds = v; }}, + 'BPM too low buzz (ms)': {value: 0 | settings.tooLowBuzzDurationMillis, min: 0, max: 3000, step: 100, onchange: v => { settings.tooLowBuzzDurationMillis = v; }}, + 'BPM too high buzz (ms)': {value: 0 | settings.tooHighBuzzDurationMillis, min: 0, max: 3000, step: 100, onchange: v => { settings.tooHighBuzzDurationMillis = v; }}, + }); +}) diff --git a/apps/ios/boot.js b/apps/ios/boot.js index 50286c4a6..a3b23a79d 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -105,8 +105,10 @@ E.on('notify',msg=>{ "io.robbie.HomeAssistant": "Home Assistant", "net.weks.prowl": "Prowl", "net.whatsapp.WhatsApp": "WhatsApp", + "net.superblock.Pushover": "Pushover", "nl.ah.Appie": "Albert Heijn", "nl.postnl.TrackNTrace": "PostNL", + "org.whispersystems.signal": "Signal", "ph.telegra.Telegraph": "Telegram", "tv.twitch": "Twitch", diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index 5560f00bc..87fb43d3d 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Now keeps user input trace intact by changing how the screen is updated. diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 51f92f510..7837a6984 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -45,11 +45,39 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); var flashToggle = false; const R = Bangle.appRect; + var Rx1; + var Rx2; + var Ry1; + var Ry2; + + function findMarker(strArr) { + if (strArr.length == 0) { + Rx1 = 4; + Rx2 = 6*4; + Ry1 = 8*4; + Ry2 = 8*4 + 3; + } 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; + } 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; + } + //print(Rx1,Rx2,Ry1, Ry2); + return {x:Rx1,y:Ry1,x2:Rx2,y2:Ry2}; + } function draw(noclear) { g.reset(); - if (!noclear) g.clearRect(R); - var l = g.setFont("6x8:4").wrapString(text+(flashToggle?"_":" "), R.w-8); + var l = g.setFont("6x8:4").wrapString(text+' ', R.w-8); + if (!l) l = []; + //print(text+':'); + //print(l); + if (!noclear) (flashToggle?(g.fillRect(findMarker(l))):(g.clearRect(findMarker(l)))); if (l.length>4) l=l.slice(-4); g.drawString(l.join("\n"),R.x+4,R.y+4); } @@ -80,6 +108,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); var ch = o.stroke; if (ch=="\b") text = text.slice(0,-1); else text += ch; + g.clearRect(R); } flashToggle = true; draw(); @@ -87,7 +116,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); Bangle.on('stroke',strokeHandler); g.reset().clearRect(R); show(); - draw(true); + draw(false); var flashInterval; return new Promise((resolve,reject) => { diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index 635841e62..f1e7cf7d6 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,6 +1,6 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.01", + "version":"0.02", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index b8c198d50..7248f69c3 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -12,3 +12,4 @@ 0.11: Merge Bangle.js 1 and 2 launchers, again 0.12: Add an option to hide clocks from the app list (fix #1015) Add /*LANG*/ tags for internationalisation +0.13: Add fullscreen mode diff --git a/apps/launch/README.md b/apps/launch/README.md new file mode 100644 index 000000000..4e6185dea --- /dev/null +++ b/apps/launch/README.md @@ -0,0 +1,14 @@ +Launcher +======== + +This is the default launcher but you can replace it with a customised launcher. + +The app is needed to display a menu with all the apps installed on your Bangle. You can launch an app by touching its name/icon. + +Settings +-------- + +- `Font` - The font used (`4x6`, `6x8`, `12x20`, `6x15` or `Vector`). Default `12x20`. +- `Vector Font Size` - The size of the font if `Font` is set to `Vector`. Default `10`. +- `Show Clocks` - If set to `No` then clocks won't appear in the app list. Default `Yes`. +- `Fullscreen` - If set to `Yes` then widgets won't be loaded. Default `No`. diff --git a/apps/launch/app.js b/apps/launch/app.js index 4ceabe751..556e61bfd 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -2,7 +2,10 @@ var s = require("Storage"); var scaleval = 1; var vectorval = 20; var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; -let settings = Object.assign({ showClocks: true }, s.readJSON("launch.json", true) || {}); +let settings = Object.assign({ + showClocks: true, + fullscreen: false +}, s.readJSON("launch.json", true) || {}); if ("vectorsize" in settings) { vectorval = parseInt(settings.vectorsize); @@ -44,8 +47,11 @@ function drawApp(i, r) { } g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); + +if (!settings.fullscreen) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} E.showScroller({ h : 64*scaleval, c : apps.length, diff --git a/apps/launch/metadata.json b/apps/launch/metadata.json index 96bbf104b..da76fc4bb 100644 --- a/apps/launch/metadata.json +++ b/apps/launch/metadata.json @@ -2,8 +2,9 @@ "id": "launch", "name": "Launcher", "shortName": "Launcher", - "version": "0.12", + "version": "0.13", "description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", + "readme": "README.md", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", diff --git a/apps/launch/settings.js b/apps/launch/settings.js index 60422e75c..5d37e1c1b 100644 --- a/apps/launch/settings.js +++ b/apps/launch/settings.js @@ -1,6 +1,9 @@ // make sure to enclose the function in parentheses (function(back) { - let settings = Object.assign({ showClocks: true }, require("Storage").readJSON("launch.json", true) || {}); + let settings = Object.assign({ + showClocks: true, + fullscreen: false + }, require("Storage").readJSON("launch.json", true) || {}); let fonts = g.getFonts(); function save(key, value) { @@ -8,7 +11,7 @@ require("Storage").write("launch.json",settings); } const appMenu = { - "": {"title": /*LANG*/"Launcher Settings"}, + "": { "title": /*LANG*/"Launcher" }, /*LANG*/"< Back": back, /*LANG*/"Font": { value: fonts.includes(settings.font)? fonts.indexOf(settings.font) : fonts.indexOf("12x20"), @@ -16,15 +19,20 @@ onchange: (m) => {save("font", fonts[m])}, format: v => fonts[v] }, - /*LANG*/"Vector font size": { + /*LANG*/"Vector Font Size": { value: settings.vectorsize || 10, min:10, max: 20,step:1,wrap:true, onchange: (m) => {save("vectorsize", m)} }, - /*LANG*/"Show clocks": { + /*LANG*/"Show Clocks": { value: settings.showClocks == true, format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", - onchange: (m) => {save("showClocks", m)} + onchange: (m) => { save("showClocks", m) } + }, + /*LANG*/"Fullscreen": { + value: settings.fullscreen == true, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: (m) => { save("fullscreen", m) } } }; E.showMenu(appMenu); diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index 4935fe714..e622feb1f 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -17,4 +17,6 @@ 0.17: Settings for mph/kph and other minor improvements. 0.18: Fullscreen mode can now be enabled or disabled in the settings. 0.19: Alarms can not go bigger than 100. -0.20: Use alarm for alarm functionality instead of own implementation. \ No newline at end of file +0.20: Use alarm for alarm functionality instead of own implementation. +0.21: Add custom theming. +0.22: Fix alarm and add build in function for step counting. \ No newline at end of file diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 7024e8edf..6d4b18b9a 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -1,8 +1,7 @@ # LCARS clock A simple LCARS inspired clock. -Note: To display the steps, the wpedom app is required. To show weather data -such as temperature, humidity or window you BangleJS must be connected +To show weather data such as temperature, humidity or window you BangleJS must be connected with Gadgetbride and the weather app must be installed. To use the timer the "sched" app must be installed on your device. @@ -19,6 +18,7 @@ the "sched" app must be installed on your device. * Tap on top/bottom of screen 1 to activate an alarm. Depends on widtmr. * The lower orange line indicates the battery level. * Display graphs (day or month) for steps + hrm on the second screen. + * Customizable theming colors in the settings menu of the app. ## Data that can be configured * Steps - Steps loaded via the wpedom app. @@ -43,3 +43,4 @@ Access different screens via tap on the left/ right side of the screen ## Contributors - [Adam Schmalhofer](https://github.com/adamschmalhofer) - [Jon Warrington](https://github.com/BartokW) +- [Ronin Stegner](https://github.com/Ronin0000) diff --git a/apps/lcars/bg_left.png b/apps/lcars/bg_left.png index 91c2bb6f7..3bae5e458 100644 Binary files a/apps/lcars/bg_left.png and b/apps/lcars/bg_left.png differ diff --git a/apps/lcars/bg_left_small.png b/apps/lcars/bg_left_small.png index bfdb110d9..8223a1898 100644 Binary files a/apps/lcars/bg_left_small.png and b/apps/lcars/bg_left_small.png differ diff --git a/apps/lcars/bg_right.png b/apps/lcars/bg_right.png index 6e23a5d6e..a87cf31d1 100644 Binary files a/apps/lcars/bg_right.png and b/apps/lcars/bg_right.png differ diff --git a/apps/lcars/bg_right_small.png b/apps/lcars/bg_right_small.png index df9d32b38..62d9ea651 100644 Binary files a/apps/lcars/bg_right_small.png and b/apps/lcars/bg_right_small.png differ diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 577955d2e..07ca51fd9 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -1,14 +1,17 @@ const TIMER_IDX = "lcars"; const SETTINGS_FILE = "lcars.setting.json"; const locale = require('locale'); -const storage = require('Storage'); +const storage = require('Storage') let settings = { alarm: -1, dataRow1: "Steps", - dataRow2: "Temp", + dataRow2: "HRM", dataRow3: "Battery", speed: "kph", fullscreen: false, + themeColor1BG: "#FF9900", + themeColor2BG: "#FF00DC", + themeColor3BG: "#0094FF", }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -18,9 +21,9 @@ for (const key in saved_settings) { /* * Colors to use */ -let cBlue = "#0094FF"; -let cOrange = "#FF9900"; -let cPurple = "#FF00DC"; +let color1 = settings.themeColor3BG; +let color2 = settings.themeColor1BG; +let color3 = settings.themeColor2BG; let cWhite = "#FFFFFF"; let cBlack = "#000000"; let cGrey = "#424242"; @@ -33,33 +36,77 @@ let lcarsViewPos = 0; var plotMonth = false; -/* - * Requirements and globals - */ +function convert24to16(input) +{ + let RGB888 = parseInt(input.replace(/^#/, ''), 16); + let r = (RGB888 & 0xFF0000) >> 16; + let g = (RGB888 & 0xFF00) >> 8; + let b = RGB888 & 0xFF; + r = (r * 249 + 1014) >> 11; + g = (g * 253 + 505) >> 10; + b = (b * 249 + 1014) >> 11; + let RGB565 = 0; + RGB565 = RGB565 | (r << 11); + RGB565 = RGB565 | (g << 5); + RGB565 = RGB565 | b; + + return "0x"+RGB565.toString(16); +} + +var color1C = convert24to16(color1); +var color2C = convert24to16(color2); +var color3C = convert24to16(color3); + +/* +* Requirements and globals +*/ + +var colorPalette = new Uint16Array([ + 0x0000, // not used + color2C, // second + color3C, // third + 0x0000, // not used + color1C, // first + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000, // not used + 0x0000 // not used +],0,1); var bgLeftFullscreen = { width : 27, height : 176, bpp : 3, transparent : 0, - buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A==")) + buffer : require("heatshrink").decompress((atob("/4AB+VJkmSAQV///+BAtJn//5IIFkmf/4IGyVP/gIGpMnF41PHIImGF4ImHJoQmGJoIdK8hNHNY47C/JNGBIJZGyYJBQA5GCKH5Q/KAQAoUP7y/KH5QGDoQAy0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkqrCS4xGCWoyDCKH5Q1GShlJChQLCCg5TCHw5TMAD35FAoIIkgJB8hGGv/8Mg8/+QIFp4cB5IRGBIIvI/4IFybyCF4wTCDp5NBHZZiGz4JBLJKAGk4JBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4"))), + palette: colorPalette }; var bgLeftNotFullscreen = { width : 27, height : 152, bpp : 3, transparent : 0, - buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAGVbtu27YCCoAJBkuWrNlAQRkCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=")) + buffer : require("heatshrink").decompress((atob("/4AB+VJkmSAQV///+BAtJn//5IIFkmf/4IGyVP/gIGpMnF41PHIImGF4ImHJoQmGJoIdK8hNHNY47C/JNGBIJZGyYJBQA5GCKH5Q/KAQAy0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkqrCS4xGCWoyhCKH5Q1GShlJChQLCCg5TCHw5TMAD35FAoIIkgJB8hGGv/8Mg8/+QIFp4cB5IRGBIIvI/4IFybyCF4wTCDp5NBHZZiGz4JBLJKAGk4JBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))), + palette: colorPalette }; var bgRightFullscreen = { width : 27, height : 176, bpp : 3, transparent : 0, - buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA=")) + buffer : require("heatshrink").decompress((atob("yVJkgCCyf/AAPJBAYCBk4JB8gUFyVP//yBAoCB//5BAwUCAAIUHAAIgGChopGv5TIn5TIz4yLKYxxC/iGI/xxGKH5Q/agwAnUP7y/KH4yGeVYAJ0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkp4CS4xGCWoyhCKH5QuDoxQCDpI7GDoJZGHYIRGLIQvGO4QvGMQRNJADv+GIqTC/5PGz4JBJ41JBIPJCg2TD4QLGn4JB/gUaHwRTGHwRTHBIRTGNAQyJ8gyI+QdFp4JB/IdFk5lLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJoyJC/4A=="))), + palette: colorPalette }; var bgRightNotFullscreen = { width : 27, height : 152, bpp : 3, transparent : 0, - buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAxrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgA=")) + buffer : require("heatshrink").decompress((atob("yVJkgCCyf/AAPJBAYCBk4JB8gUFyVP//yBAoCB//5BAwUCAAIUHAAIgGChopGv5TIn5TIz4yLKYxxC/iGI/xxGKH5Q/agwAx0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkqrCS4xGCWoyhCKH5QuDoxQCDpI7GDoJZGHYIRGLIQvGO4QvGMQRNJADv+GIqTC/5PGz4JBJ41JBIPJCg2TD4QLGn4JB/gUaHwRTGHwRTHBIRTGNAQyJ8gyI+QdFp4JB/IdFk5lLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJoyJC/4A="))), + palette: colorPalette }; var bgLeft = settings.fullscreen ? bgLeftFullscreen : bgLeftNotFullscreen; @@ -191,7 +238,7 @@ function _drawData(key, y, c){ value = E.getAnalogVRef().toFixed(2) + "V"; } else if(key == "HRM"){ - value = Math.round(Bangle.getHealthStatus("day").bpm); + value = Math.round(Bangle.getHealthStatus("last").bpm); } else if (key == "TEMP"){ var weather = getWeather(); @@ -244,9 +291,11 @@ function drawInfo(){ return; } + // Draw Infor is called from different sources so + // we have to ensure that the alignment is always the same. g.setFontAlign(-1, -1, 0); g.setFontAntonioMedium(); - g.setColor(cOrange); + g.setColor(color2); g.clearRect(120, 10, g.getWidth(), 75); g.drawString("LCARS", 128, 13); @@ -256,7 +305,7 @@ function drawInfo(){ g.drawString("NOCON", 128, 33); } if(Bangle.isLocked()){ - g.setColor(cPurple); + g.setColor(color3); g.drawString("LOCK", 128, 53); } } @@ -287,7 +336,7 @@ function drawState(){ g.drawString("STATUS", 23+26, 108); } else { // Alarm within symbol - g.setColor(cOrange); + g.setColor(color2); g.drawString("ALARM", 23+26, 108); g.setColor(cWhite); g.setFontAntonioLarge(); @@ -302,19 +351,19 @@ function drawPosition0(){ // Draw background image var offset = settings.fullscreen ? 0 : 24; g.drawImage(bgLeft, 0, offset); - drawHorizontalBgLine(cBlue, 25, 120, offset, 4); - drawHorizontalBgLine(cBlue, 130, 176, offset, 4); - drawHorizontalBgLine(cPurple, 20, 70, 80, 4); - drawHorizontalBgLine(cPurple, 80, 176, 80, 4); - drawHorizontalBgLine(cOrange, 35, 110, 87, 4); - drawHorizontalBgLine(cOrange, 120, 176, 87, 4); + drawHorizontalBgLine(color1, 25, 120, offset, 4); + drawHorizontalBgLine(color1, 130, 176, offset, 4); + drawHorizontalBgLine(color3, 20, 70, 80, 4); + drawHorizontalBgLine(color3, 80, 176, 80, 4); + drawHorizontalBgLine(color2, 35, 110, 87, 4); + drawHorizontalBgLine(color2, 120, 176, 87, 4); // The last line is a battery indicator too var bat = E.getBattery() / 100.0; var batStart = 19; var batWidth = 172 - batStart; var batX2 = parseInt(batWidth * bat + batStart); - drawHorizontalBgLine(cOrange, batStart, batX2, 171, 5); + drawHorizontalBgLine(color2, batStart, batX2, 171, 5); drawHorizontalBgLine(cGrey, batX2, 172, 171, 5); for(var i=0; i+batStart<=172; i+=parseInt(batWidth/4)){ drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8) @@ -353,9 +402,9 @@ function drawPosition0(){ // Draw data g.setFontAlign(-1, -1, 0); g.setColor(cWhite); - drawData(settings.dataRow1, 97, cOrange); - drawData(settings.dataRow2, 122, cPurple); - drawData(settings.dataRow3, 147, cBlue); + drawData(settings.dataRow1, 97, color2); + drawData(settings.dataRow2, 122, color3); + drawData(settings.dataRow3, 147, color1); // Draw state drawState(); @@ -366,13 +415,13 @@ function drawPosition1(){ var offset = settings.fullscreen ? 0 : 24; g.drawImage(bgRight, 149, offset); if(settings.fullscreen){ - drawHorizontalBgLine(cBlue, 0, 140, offset, 4); + drawHorizontalBgLine(color1, 0, 140, offset, 4); } - drawHorizontalBgLine(cPurple, 0, 80, 80, 4); - drawHorizontalBgLine(cPurple, 90, 150, 80, 4); - drawHorizontalBgLine(cOrange, 0, 50, 87, 4); - drawHorizontalBgLine(cOrange, 60, 140, 87, 4); - drawHorizontalBgLine(cOrange, 0, 150, 171, 5); + drawHorizontalBgLine(color3, 0, 80, 80, 4); + drawHorizontalBgLine(color3, 90, 150, 80, 4); + drawHorizontalBgLine(color2, 0, 50, 87, 4); + drawHorizontalBgLine(color2, 60, 140, 87, 4); + drawHorizontalBgLine(color2, 0, 150, 171, 5); // Draw steps bars g.setColor(cWhite); @@ -511,17 +560,20 @@ function draw(){ * Step counter via widget */ function getSteps() { + var steps = 0; try{ if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); + steps = WIDGETS.wpedom.getSteps(); } else if (WIDGETS.activepedom !== undefined) { - return WIDGETS.activepedom.getSteps(); + steps = WIDGETS.activepedom.getSteps(); + } else { + steps = Bangle.getHealthStatus("day").steps; } } catch(ex) { // In case we failed, we can only show 0 steps. } - return 0; + return steps; } @@ -530,38 +582,35 @@ function getWeather(){ try { weatherJson = storage.readJSON('weather.json'); + var weather = weatherJson.weather; + + // Temperature + weather.temp = locale.temp(weather.temp-273.15); + + // Humidity + weather.hum = weather.hum + "%"; + + // Wind + const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); + var speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934; + weather.wind = Math.round(wind[1] * speedFactor); + + return weather + } catch(ex) { // Return default } - if(weatherJson === undefined){ - return { - temp: "-", - hum: "-", - txt: "-", - wind: "-", - wdir: "-", - wrose: "-" - }; - } - - var weather = weatherJson.weather; - - // Temperature - weather.temp = locale.temp(weather.temp-273.15); - - // Humidity - weather.hum = weather.hum + "%"; - - // Wind - const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); - var speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934; - weather.wind = Math.round(wind[1] * speedFactor); - - return weather + return { + temp: " ? ", + hum: " ? ", + txt: " ? ", + wind: " ? ", + wdir: " ? ", + wrose: " ? " + }; } - /* * Handle alarm */ @@ -594,7 +643,7 @@ function increaseAlarm(){ var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; var alarm = require('sched') alarm.setAlarm(TIMER_IDX, { - timer : (minutes+5)*60*1000, + timer : (minutes+5)*60*1000, }); alarm.reload(); } catch(ex){ } @@ -609,9 +658,9 @@ function decreaseAlarm(){ alarm.setAlarm(TIMER_IDX, undefined); if(minutes > 0){ - alarm.setAlarm(TIMER_IDX, { - timer : minutes*60*1000, - }); + alarm.setAlarm(TIMER_IDX, { + timer : minutes*60*1000, + }); } alarm.reload(); @@ -642,7 +691,6 @@ Bangle.on('charging',function(charging) { drawState(); }); - function feedback(){ Bangle.buzz(40, 0.3); } diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 75add1ece..b64feb30e 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -5,11 +5,14 @@ const storage = require('Storage') let settings = { alarm: -1, - dataRow1: "Battery", - dataRow2: "Steps", - dataRow3: "Temp", + dataRow1: "Steps", + dataRow2: "HRM", + dataRow3: "Battery", speed: "kph", fullscreen: false, + themeColor1BG: "#FF9900", + themeColor2BG: "#FF00DC", + themeColor3BG: "#0094FF", }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -20,8 +23,11 @@ storage.write(SETTINGS_FILE, settings) } + var dataOptions = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"]; var speedOptions = ["kph", "mph"]; + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White']; + var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF']; E.showMenu({ '': { 'title': 'LCARS Clock' }, @@ -69,6 +75,33 @@ settings.speed = speedOptions[v]; save(); }, + }, + 'Theme Color 1': { + value: 0 | bg_code.indexOf(settings.themeColor1BG), + min: 0, max: 7, + format: v => color_options[v], + onchange: v => { + settings.themeColor1BG = bg_code[v]; + save(); + }, + }, + 'Theme Color 2': { + value: 0 | bg_code.indexOf(settings.themeColor2BG), + min: 0, max: 7, + format: v => color_options[v], + onchange: v => { + settings.themeColor2BG = bg_code[v]; + save(); + }, + }, + 'Theme Color 3': { + value: 0 | bg_code.indexOf(settings.themeColor3BG), + min: 0, max: 7, + format: v => color_options[v], + onchange: v => { + settings.themeColor3BG = bg_code[v]; + save(); + }, } }); }) diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index 7155442f8..40da1b37f 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -3,7 +3,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.20", + "version":"0.22", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 06e959954..2bc71fd75 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -583,6 +583,24 @@ var locales = { abday: "ne,po,út,st,čt,pá,so", day: "neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota", trans: { yes: "ano", Yes: "Ano", no: "ne", No: "Ne", ok: "ok", on: "zap", off: "vyp" } + }, + "hr_HR": { + lang: "hr_HR", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "€", + int_curr_symbol: "EUR", + speed: "km/h", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: { 0: "dop.", 1: "pop." }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, + datePattern: { 0: "%-d. %b %Y", 1: "%-d.%-m.%Y" }, // "3. jan. 2020" // "3.1.2020"(short) + abmonth: "sij.,velj.,ožu.,tra.,svi,lip.,srp.,kol.,ruj.,lis.,stu.,pro.", + month: "siječanj,veljača,ožujak,travanj,svibanj,lipanj,srpanj,kolovoz,rujan,listopad,studeni,prosinac", + abday: "ned.,pon.,uto.,sri.,čet.,pet.,sub.", + day: "nedjelja,ponedjeljak,utorak,srijeda,četvrtak,petak,subota", + trans: { yes: "da", Yes: "Da", no: "ne", No: "Ne", ok: "ok", on: "Uklj.", off: "Isklj.", "< Back": "< Natrag" } }, "sl_SI": { lang: "sl_SI", @@ -662,7 +680,7 @@ var locales = { thousands_sep: " ", currency_symbol: "kr", int_curr_symbol: "NOK", - speed: "kmh", + speed: "kmt", distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, diff --git a/apps/ltherm/app.js b/apps/ltherm/app.js index 7accae2ed..2cbf26e5f 100644 --- a/apps/ltherm/app.js +++ b/apps/ltherm/app.js @@ -20,6 +20,6 @@ const avg = []; setInterval(function() { drawTemperature(); }, 2000); -E.showMessage("Loading..."); +E.showMessage(/*LANG*/"Loading..."); Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 3e676c21e..a3ee37326 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -44,3 +44,6 @@ 0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) 0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) 0.31: Option to disable icon flashing +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 diff --git a/apps/messages/app.js b/apps/messages/app.js index 821813108..85d818bd5 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -52,7 +52,8 @@ var MESSAGES = require("Storage").readJSON("messages.json",1)||[]; if (!Array.isArray(MESSAGES)) MESSAGES=[]; var onMessagesModified = function(msg) { // TODO: if new, show this new one - if (msg && msg.id!=="music" && msg.new && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { + if (msg && msg.id!=="music" && msg.new && active!="map" && + !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { if (WIDGETS["messages"]) WIDGETS["messages"].buzz(); else Bangle.buzz(); } @@ -470,8 +471,6 @@ function checkMessages(options) { // no new messages - go to clock? if (options.clockIfAllRead && newMessages.length==0) return load(); - // we don't have to time out of this screen... - cancelReloadTimeout(); active = "main"; // Otherwise show a menu E.showScroller({ diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 7d49d4c64..f584c9e93 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -56,9 +56,16 @@ exports.pushMessage = function(event) { } // otherwise load messages/show widget var loadMessages = Bangle.CLOCK || event.important; - // first, buzz var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet; - var unlockWatch = (require('Storage').readJSON('messages.settings.json',1)||{}).unlockWatch; + var appSettings = require('Storage').readJSON('messages.settings.json',1)||{}; + var unlockWatch = appSettings.unlockWatch; + var quietNoAutOpn = appSettings.quietNoAutOpn; + delete appSettings; + // don't auto-open messages in quiet mode if quietNoAutOpn is true + if(quiet && quietNoAutOpn) { + loadMessages = false; + } + // first, buzz if (!quiet && loadMessages && global.WIDGETS && WIDGETS.messages){ WIDGETS.messages.buzz(); if(unlockWatch != false){ diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 5c1e67702..ffb4f8b8a 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.31", + "version": "0.34", "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/messages/settings.js b/apps/messages/settings.js index cc0030ec5..adea36f12 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -53,6 +53,11 @@ format: v => v?/*LANG*/'Yes':/*LANG*/'No', onchange: v => updateSetting("flash", v) }, + /*LANG*/'Quiet mode disables auto-open': { + value: !!settings().quietNoAutOpn, + format: v => v?/*LANG*/'Yes':/*LANG*/'No', + onchange: v => updateSetting("quietNoAutOpn", v) + }, }; E.showMenu(mainmenu); }) diff --git a/apps/messagesmusic/ChangeLog b/apps/messagesmusic/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/messagesmusic/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/messagesmusic/README.md b/apps/messagesmusic/README.md new file mode 100644 index 000000000..7aa9209df --- /dev/null +++ b/apps/messagesmusic/README.md @@ -0,0 +1,15 @@ +Hacky app that uses Messages app and it's library to push a message that triggers the music controls. It's nearly not an app, and yet it moves. + +This app require Messages setting 'Auto-open Music' to be 'Yes'. If it isn't, the app will change it to 'Yes' and let it stay that way. + +Making the music controls accessible this way lets one start a music stream on the phone in some situations even though the message app didn't receive a music message from gadgetbridge to begin with. (I think.) + +It is suggested to use Messages Music along side the app Quick Launch. + +Messages Music v0.01 has been verified to work with Messages v0.31 on Bangle.js 2 fw2v13. + +Music Messages should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much. + +Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986). + +The icon used for this app is from [https://icons8.com](https://icons8.com). diff --git a/apps/messagesmusic/app-icon.js b/apps/messagesmusic/app-icon.js new file mode 100644 index 000000000..5252570b6 --- /dev/null +++ b/apps/messagesmusic/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AFXdAAQVVDKQWHDB0NC5PQCyoYMCxZJKFxgwKCxowJC6xGOJBALE6YwRBQnf+YXPIwvf/4YKJAgXHDBQXNDBIXO/89C5YKFC4gYIC54YHC6AYGC6IYFC9JHWO6ynLa64XJ+YWGC5wWIC5oWJC4p4F74WKOwgXG6YWKC4xIFABRGFYI4uPC7JIOIw4wPCxAwNFxIYMCxZJLCxgYJCxwZGCqIA/AC4=")) diff --git a/apps/messagesmusic/app.js b/apps/messagesmusic/app.js new file mode 100644 index 000000000..a6f7e075e --- /dev/null +++ b/apps/messagesmusic/app.js @@ -0,0 +1,15 @@ +let showMusic = () => { + Bangle.CLOCK = 1; // To pass condition in messages library + require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); + Bangle.CLOCK = undefined; +}; + +var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict +if (!settings.openMusic) { + settings.openMusic = true; // This app/hack works as intended only if this setting is true + require('Storage').writeJSON('messages.settings.json', settings); + E.showMessage("First run:\n\nMessages setting\n\n 'Auto-Open Music'\n\n set to 'Yes'"); + setTimeout(()=>{showMusic();}, 5000); +} else { + showMusic(); +} diff --git a/apps/messagesmusic/app.png b/apps/messagesmusic/app.png new file mode 100644 index 000000000..9d2967bba Binary files /dev/null and b/apps/messagesmusic/app.png differ diff --git a/apps/messagesmusic/metadata.json b/apps/messagesmusic/metadata.json new file mode 100644 index 000000000..edc6835ed --- /dev/null +++ b/apps/messagesmusic/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "messagesmusic", + "name":"Messages Music", + "version":"0.01", + "description": "Uses Messages library to push a music message which in turn displays Messages app music controls", + "icon":"app.png", + "type": "app", + "tags":"tool,music", + "screenshots": [{"url":"screenshot.png"}], + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"messagesmusic.app.js","url":"app.js"}, + {"name":"messagesmusic.img","url":"app-icon.js","evaluate":true} + ], + "dependencies": {"messages":"app"} + +} diff --git a/apps/messagesmusic/screenshot.png b/apps/messagesmusic/screenshot.png new file mode 100644 index 000000000..986b869f8 Binary files /dev/null and b/apps/messagesmusic/screenshot.png differ diff --git a/apps/mosaic/ChangeLog b/apps/mosaic/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/mosaic/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/mosaic/README.md b/apps/mosaic/README.md new file mode 100644 index 000000000..b2f31aef2 --- /dev/null +++ b/apps/mosaic/README.md @@ -0,0 +1,14 @@ +# Mosaic Clock + +A fabulously colourful clock! + +* Clearly shows the time on a colourful background that changes every minute. +* Dark and Light theme compatible, with a setting to override the digit colour scheme. +* Show or hide widgets with a setting (default shows widgets). + +![](mosaic-scr1.png) +![](mosaic-scr2.png) + +This clock is inspired by the mosaic watchface for pebble: https://apps.rebble.io/en_US/application/55386bcd2aead62b16000028 + +Written by: [Sir Indy](https://github.com/sir-indy) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/mosaic/metadata.json b/apps/mosaic/metadata.json new file mode 100644 index 000000000..267c0de55 --- /dev/null +++ b/apps/mosaic/metadata.json @@ -0,0 +1,19 @@ +{ + "id":"mosaic", + "name":"Mosaic Clock", + "shortName": "Mosaic Clock", + "version": "0.01", + "description": "A fabulously colourful clock", + "readme": "README.md", + "icon":"mosaic.png", + "screenshots": [{"url":"mosaic-scr1.png"},{"url":"mosaic-scr2.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"mosaic.app.js","url":"mosaic.app.js"}, + {"name":"mosaic.settings.js","url":"mosaic.settings.js"}, + {"name":"mosaic.img","url":"mosaic.icon.js","evaluate":true} + ] + } diff --git a/apps/mosaic/mosaic-scr1.png b/apps/mosaic/mosaic-scr1.png new file mode 100644 index 000000000..2c429a9d2 Binary files /dev/null and b/apps/mosaic/mosaic-scr1.png differ diff --git a/apps/mosaic/mosaic-scr2.png b/apps/mosaic/mosaic-scr2.png new file mode 100644 index 000000000..5a172bb34 Binary files /dev/null and b/apps/mosaic/mosaic-scr2.png differ diff --git a/apps/mosaic/mosaic.app.js b/apps/mosaic/mosaic.app.js new file mode 100644 index 000000000..8b008b848 --- /dev/null +++ b/apps/mosaic/mosaic.app.js @@ -0,0 +1,103 @@ +Array.prototype.sample = function(){ + return this[Math.floor(Math.random()*this.length)]; +}; + +const SETTINGS_FILE = "mosaic.settings.json"; +let settings; +let theme; +let timeout = 60; +let drawTimeout; +let colours = [ + '#f00', '#00f', '#0f0', '#ff0', '#f0f', '#0ff', + '#8f0', '#f08', '#f80', '#80f', '#0f8', '#08f', +]; +let digits = [ + E.toArrayBuffer(atob("BQcB/Gtax+A=")), + E.toArrayBuffer(atob("BQeCAX9c1zXNc1zX9A==")), + E.toArrayBuffer(atob("BQcB/Hsbx+A=")), + E.toArrayBuffer(atob("BQcB/Hsex+A=")), + E.toArrayBuffer(atob("BQeCAf/zPM8D/Nc1/A==")), + E.toArrayBuffer(atob("BQcB/G8ex+A=")), + E.toArrayBuffer(atob("BQcB/G8ax+A=")), + E.toArrayBuffer(atob("BQeCAf/wP81zXNc1/A==")), + E.toArrayBuffer(atob("BQcB/Gsax+A=")), + E.toArrayBuffer(atob("BQcB/Gsex+A=")) +]; + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'showWidgets': false, 'theme':'System'}; +} + +function loadThemeColors() { + theme = {fg: g.theme.fg, bg: g.theme.bg}; + if (settings.theme === "Dark") { + theme.fg = g.toColor(1,1,1); + theme.bg = g.toColor(0,0,0); + } + else if (settings.theme === "Light") { + theme.fg = g.toColor(0,0,0); + theme.bg = g.toColor(1,1,1); + } +} + +function queueDraw(seconds) { + let millisecs = seconds * 1000; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, millisecs - (Date.now() % millisecs)); +} + +function draw() { + // draw colourful grid + for (let i_x = 0; i_x < num_squares_w; i_x++) { + for (let i_y = 0; i_y < num_squares_h; i_y++) { + g.setColor(colours.sample()).fillRect( + o_w+i_x*s, o_h+i_y*s, o_w+i_x*s+s, o_h+i_y*s+s + ); + } + } + let t = new Date(); + g.setBgColor(theme.fg); + g.setColor(theme.bg); + g.drawImage(digits[Math.floor(t.getHours()/10)], (mid_x-5)*s+o_w, (mid_y-7)*s+o_h, {scale:s}); + g.drawImage(digits[t.getHours() % 10], (mid_x+1)*s+o_w, (mid_y-7)*s+o_h, {scale:s}); + g.drawImage(digits[Math.floor(t.getMinutes()/10)], (mid_x-5)*s+o_w, (mid_y+1)*s+o_h, {scale:s}); + g.drawImage(digits[t.getMinutes() % 10], (mid_x+1)*s+o_w, (mid_y+1)*s+o_h, {scale:s}); + + queueDraw(timeout); +} + +g.clear(); +loadSettings(); +loadThemeColors(); + +offset_widgets = settings.showWidgets ? 24 : 0; +let available_height = g.getHeight() - offset_widgets; + +// Calculate grid size and offsets +let s = Math.floor(available_height/17); +let num_squares_w = Math.round(g.getWidth()/s) - 1; +let num_squares_h = Math.round(available_height/s) - 1; +let o_w = Math.floor((g.getWidth() - num_squares_w * s)/2); +let o_h = Math.floor((g.getHeight() - num_squares_h * s+offset_widgets)/2); +let mid_x = Math.floor(num_squares_w/2); +let mid_y = Math.floor((num_squares_h-1)/2); + +draw(); + +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI('clock'); +if (settings.showWidgets) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} diff --git a/apps/mosaic/mosaic.icon.js b/apps/mosaic/mosaic.icon.js new file mode 100644 index 000000000..4d781cf68 --- /dev/null +++ b/apps/mosaic/mosaic.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcAtu27dt3MkyVJkgHC23UA4WSCP4R/CP4RFBAfSA4VJA4QCFCP4R/CP4RJ7oaMCP4R/CP4RFbge9BoYID3IQCkgR/CP4R/CIoA==")) diff --git a/apps/mosaic/mosaic.png b/apps/mosaic/mosaic.png new file mode 100644 index 000000000..f0c814d3b Binary files /dev/null and b/apps/mosaic/mosaic.png differ diff --git a/apps/mosaic/mosaic.settings.js b/apps/mosaic/mosaic.settings.js new file mode 100644 index 000000000..dcf725b84 --- /dev/null +++ b/apps/mosaic/mosaic.settings.js @@ -0,0 +1,44 @@ +(function(back) { + const SETTINGS_FILE = "mosaic.settings.json"; + + // initialize with default settings... + let s = {'showWidgets': false, 'theme':'System'} + + // ...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) + } + + var theme_options = ['System', 'Light', 'Dark']; + + E.showMenu({ + '': { 'title': 'Mosaic Clock' }, + '< Back': back, + 'Show Widgets': { + value: settings.showWidgets, + format: () => (settings.showWidgets ? 'Yes' : 'No'), + onchange: () => { + settings.showWidgets = !settings.showWidgets; + save(); + } + }, + 'Theme': { + value: 0 | theme_options.indexOf(s.theme), + min: 0, max: theme_options.length - 1, + format: v => theme_options[v], + onchange: v => { + s.theme = theme_options[v]; + save(); + } + }, + }); +}) diff --git a/apps/mosaic/pixel digits.xcf b/apps/mosaic/pixel digits.xcf new file mode 100644 index 000000000..f2c05891c Binary files /dev/null and b/apps/mosaic/pixel digits.xcf differ diff --git a/apps/mtgwatchface/ChangeLog b/apps/mtgwatchface/ChangeLog new file mode 100644 index 000000000..32a3cd454 --- /dev/null +++ b/apps/mtgwatchface/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Support Dark Theme. diff --git a/apps/mtgwatchface/README.md b/apps/mtgwatchface/README.md index 36c0c9efb..9f0a6deb9 100644 --- a/apps/mtgwatchface/README.md +++ b/apps/mtgwatchface/README.md @@ -1,5 +1,5 @@ ## Magic the Gathering Watch Face Magic the Gathering themed watch face. Embrace the inner wizzard. Dispay any of the different types of mana on your watch. Which color are you devoted to today? - +It supports both light and dark mode. ### Touch Enabled Simply touch the screen on the sides to switch the mana colors diff --git a/apps/mtgwatchface/app.js b/apps/mtgwatchface/app.js index d54247033..044845fcc 100644 --- a/apps/mtgwatchface/app.js +++ b/apps/mtgwatchface/app.js @@ -3,33 +3,38 @@ var blueImg = { transparent : 1, palette : new Uint16Array([31,65535,65535,59167]), buffer : require("heatshrink").decompress(atob("qoAlgoWVgIWVKqwWXioWVOasAOarJWgEMLikAhZcUgEK4IWToEK2AWV1hcToEO1ZcTCwOqLiYWCLiYWCLiZbCLiYWClRcSCwS5TCwUCLiQWCgBcSCwZcSqECCwJcSCwZcSCwZcSCwOqCwJcRqEDCwRcRCwhcRCwXALiVUCwhcQCwpcQqkACwZcQCwpcQCwPqwAWCLh4WGLiAWFLiAWB2AWCLiBWBCwhcPCwxcPgE60AWDLh9ACwpcBgIWO1gWElWACxsKCwsO0BFMoEOCwsD1CiMqEO1AWEX4JcMCxE6LhlQgWrCwpcNqkDCwxcNCwOqCwpcNCxM6f5dUgGq4AWFf5o8BCwz/NCwOACwz/MBoOwCwyiMoBqBCwyiBIpQWB0AWGG4PACxUKCxAJBaJNQh2sCw7wBIpIWB1AWHRYR0IqkC1YWIHAIuIfoIWJeIMAFw79CCxKVBFxAWBfo50DFxD9BCxLyCUY9AP4IWJP4T9QAAfqFw9QcxKjDFw7mKFxbPBcxIuKZ5guKCx0OHgK4GCxgOBwDSEoDPLAAUK1kBXArPKAAeq4AuDXALPLAAU6Fwi4OAAMD1R0DXBwuDUYa4PAAMCaQhjBCx0AlQuDUJ7SDOgShQaQSjCUKDSDIoNUUJ8CUYiKBUJ+gUYRFBgChPlYwBOgSKQFYJ0COYKKPUAJ0B0EFRQJMBABhxB4D6BgNUgSKOIQInBlWAipzQnQQBhxFBOYLOP1RtBIoJzQgWqIocQOZ8A1SLBIoMEOaL9BIoRzQlWqaIMq4FAAQJzPH4JZChQcBc5pcCgYCBLh4WBLgS2CLhyhBXIS2CLh86OYSiBLiEDaARcSgEPCARcCMQQAQLgQCCACD/CL4QAQLgMBL4QARLgL/QLgsQUR5cGgRFSCYMBUoYAQ1cAqE6IqRwBqjoQOYewipFTOYMFIqZBBgJFBRaMDOYNVIoLRR1QWBqDRSFIMVqkDIqPqwEVIqcq2EFIoMOXSCdBCwNUMIQWP0EBqtVoC6QnS4BCwNQgSnBCx6hBAAJ5CCyZ0BUZwWFOgIuOlQWEOgMKFxvqCwqjCFxnq0AWEOgMKRhcD1SxBCwlQFwK7KhwWHFwIKB4CIJBYQWFFwPqIxPqCxAuDPwJDHEIL8CFww6B3hwFFgIgBCw67BHYWvCof61RxCgoWHXYKXBAAO//4ECMoUVCw4uCh4SCAAarCCxJ1BC4xKCLRBGEAAJDC/4GCLRCNFAA4VLC5IsMC5BZLO5AVLoAA==")) -} +}; var redImg = { width : 130, height : 144, bpp : 1, transparent : 1, palette : new Uint16Array([63488,65535]), buffer : require("heatshrink").decompress(atob("/4AD8EAh4HEAA38C4UHB5QeBAAQhKwA5Ev4PI/APEgYxK+AQDj5SOgJjLEIYgH/hbDKgQgHJ4JbCKgYgGBQKNDIJIaCII0/Lg4pC/y1IBISMD4CkIc4owDYoprCHIQPDKIopCDAS0EKIjwCIAT2EKIhwCFAQlCCQRwGB4JlCAgJ5C/ZwEBQIkCGgIgBgf+OYRgBF4QEBB4IZCv/PHQZPCYwgJBh/zLQZ4DwAPCCgMBGATRF/ydDGAWPMAbqFGAnhCoXAZIowE/gLBwHweITsHv43B8F8bIowF4LLBv+HaAQcBOwTWBgP4v/8n/jFQUHFgLfBLYV+FgM//AqDK4KCCfgWDDgRgC/EPBYP+VgY+BBgPfRgYLBn+fIAMALo4OBFYPjIAUPK4SMFFYJcBAgXgTwwKBIAMPEoV4TwzoBIAODIAUeGBMPVYIEBg+AVowKC/EOGoTPHGAMB/8BwAPBniYCAAZsCOoY1BCwIAFDYLRCAAIjBZIJRGv7VBAAVgUQxRBUwkAjBxGAAYwDgwnBB5AwEwBxGKgoACOIwwHRAQAIQoQACOIwwIOI7XFAAJxKa4QACB5RREIBQgEOJTUFB5RiEWRJiFWRP9cohAJ/gqBUgRAJ/0f+LlDIBH+g/4v5RCIBH+gP4n/gIBcAv0PMIRAJFQMDeoZAIFQPAWYbCIFQPwv6BCh57DAgYKBvEfKAUDBQQWBMwQKBjxyBGATJFv6MCg+BSIZACbIYaCgfgv7SFB4YAD/EfaQoPHn0H+CRECoQAEj+BKAoPHSIN+KAh2CAAkD+EfKAg1CAAv8g5KCKAI1DAAl/gKhEa4ZQF4EgKAaWDAAkPHIjyBB48HFIhQBSA6LBEoiAIHQIJDMAJwIv4JDEoJwIn5AEMBJKBMBpKFYIJgHB4JADAoLRHLQJADYJIJBHQbBJBII6DOAJQHB4Q6DAoKhGDIQ6DOAJQHB4I6DOAPIWA46ESwPGUBBQDQAPhSIxwCJQQFB8FAMBChCAoPwmBQIMARmB+EMIBBgDCgMDYJl//EBSJH+EoY0BuAgGn4PDh4PBnjCHB4cHB4MPB40AgQUDB4MHIAwAFQgMBQ4QAMM4YALn4POj/gB5pzCABh4EABSpBwAQN/5AOagJwO/4PPWRiAB/6yNB4JxNB4JxNv4PBOJaPBAALjLh4PCOJQOEOJQ9CAASiIHoYwLgYPGMI4PHGA4PHGA4/HGBBfFSRJ/FAATDHB470HII70Kn6zOB4qzIgEfWZoPHQQ4vGKJKTHEA6CHEAzTIEAxOGMQ7iIaosHBhA")) -} +}; var greenImg = { width : 142, height : 145, bpp : 1, transparent : 1, palette : new Uint16Array([2016,65535]), buffer : require("heatshrink").decompress(atob("/4AF/kAv4JGAA4aBn4QN+ARQHwQRNEQRHOwARPNAJ0JAAvgCAUBK58AgYQL/ARDg4RL4ARDgkAh40NEpg0EAAQjJGggRMCA0Aj6/Gg40HbA3+Q4KeDAAjGFBwMDaYYAEIZrGIIZCOIPA58JCBRqFGhZqFGhZqFCBcDCAf8CJZYEVpDVIVpBYHaYJYP+ARMEYZ7MEYgQMCIZ7MfIhGNfIZGNUAaNMfIaNNLAf4LCDUMLAhGQRppGDGhsDGiCNCPZ0fPZ4RDYRpqDCBsAv40PPgQQOYYIiGh6OJRgyCC+DVFYI0DFQMDCIsfAwoZBZIMPBQs/Gg0PSgM/bwrBHnx1CDgkBGg0AvBGBDgsHYI0BIwSGFj7OKIwosBAAccXghjFC4kHF4cDTAsDC4iTERo0OfwpeDTA04dgoMDZoxGERAkDNIpGFgJYDh7MGRpMwAwt8CJJXFahAAIg5NFABUfFQoAK/4QPQ4gAMZIwAFXok/ZwcAhAvFDol+BYl4MgodECwl/AokDdgkBCwYKBDgl/MgkGUgn/UocB/4pEKQgRBfAYpBZBQjEg46ECI7JPh63RIwbJZVZQRMLBMQAws+Aws/DAVgBQrnGSoUDFo1ASgrdChxMLCAKnCvhdLCIQnBQwcPCAr4BAAK5Bv7fDv6LFh4RC/4nBBgQbBCIoQDG4YbDCIhoCAAbxDv4RFK4YAC/gJBgZyDGhJqCDYQQDDAQRHHQppFAAR8BBIX4cYgRG4CWDUoZGHEYJoBFAZ7IAAxqDPYylJSgQAKSYTlEABJYDIxpYDPY5YJCBixDYQ4AFWIbCHI35G/I0oQCgF/Gh8BERnALBxoDAAMHEJxqMIgY1NVwYRMEQw2KEQyQJ/gQICI6/DAAyLLAAi0GK5CiI+ARJfY3we4oACj7VOERB+KYhPwgcAhDWN4EcgF4fRuA/EDOAP8RgxYEg/gj/gGRRYCj/Av4TBCJaQBEAITBCJfn/xTB4IQLGwQgMAAf5DRAA=")) -} +}; var whiteImg = { width : 153, height : 145, bpp : 2, transparent : 0, palette : new Uint16Array([65535,65535,65504,65505]), buffer : require("heatshrink").decompress(atob("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")) -} +}; var blackImg = { width : 152, height : 144, bpp : 1, transparent : 1, palette : new Uint16Array([0,65535]), buffer : require("heatshrink").decompress(atob("/4AGwAKBg4LHAAoeEh4RL8AyNEhAABj4RJ/ASGgYSJ4BsPG5AABn4RH/gSIJhHwCRCaIQQxfLXIQAGgJwQORISvJY/4OJISH8CERwASJVQ3+gC9JjwmF/EAuASImByFEYN4JRB7Bv7hFCREHAQM/cIs8VRMAj4SDAwISLh5wEM4ISKOQYSCj4SKX4f8FgROBfBiWBNAL3JAAKFCXQUDXxISEcAZPCABCYCCQZyLTARHLTAwSPg7PDCSAROXwYSPXwJ/LCS7RBCSN/eoQShn/4CUcfCSXwCUcPCSXgCSEHCWZHDgfAC4bfMCQn//wRGgf//4SFg4IBDAY3DBIYSDn4IBTowJCJgMBwA3CAAJCCAAYJCDgISCgIICIQQACBIf4CQZTCAAIsCKggvCCQQIDL4pdCCQoIDKoQACj4ICR4ISCBAYSFOASiFCQiFEv5oHDYYSFBAYSJPYISUUAiOECSRUECQazECRLkDCVbaDCRDsECSo6BCRwQBCQyEDCRKqICQoHBHQMPew4SE/zEBCQLHEM4YSEFwISCEwigDCQngCQgTCJQarEPIUBCoIAPgYSSNQYADgUAsASHIooABgwSIg7JDCRyaBCR5/BCRFwBI0fW4gSMeYJyGhwSKL4wSJaoQSHvAIFgLoECQqhGfgY5FCRBwBAARzECRF/CQYmEjwSGVAIAEJoYSGGwgADwASHNwYSOGwwSGgISDXIISQCJASDnwSDJRISEgYSCgYSMn4SDZgYSKw4SR/4SQbQISVLxt4OKIABnASRAAQS/CRb2NCX4S/CTkACRsMCR9ACQUCCRtgCQUGCX4SGuASChwSN+ASVjwSPEYM+CRv4CQV+CSP+CSEHCR38gEP/4SggIOBCSUfCRvAgP/gE/CRvgCQOAAwUAAwIAI/EDAwgSL/4SFwAGFAAvnKQsHAwgA==")) +}; +if(g.theme.dark){ + var backgrounds = [blueImg, greenImg, whiteImg]; + var manaColors = ["#0000ff","#00ff00","#ffff00"]; +}else{ + var backgrounds = [blackImg, blueImg, redImg, greenImg, whiteImg]; + var manaColors = ["#000000","#0000ff","#ff0000","#00ff00","#ffff00"]; } -var backgrounds = [blackImg, blueImg, redImg, greenImg, whiteImg] -var manaColors = ["#000000","#0000ff","#ff0000","#00ff00","#ffff00"] var bgIndex = 0; g.width = g.getWidth(); g.height = g.getHeight(); @@ -43,24 +48,24 @@ Graphics.prototype.setFontTreasurehuntDOYwE_20 = function(scale) { // Actual height 20 (19 - 0) this.setFontCustom(atob("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C////4AP////gA/7/q+ADwB8A8APQLwDwAvS/APAC//8A4AP//0DAAv/f0sAAvg//gAAED/8AAAAL/gAAAAL4AAAAAEAAAAAAAAAAC//8AAAv//8AAL///8AA//7/wAH4AF/AAtAAB9ADwAAD4APAAALgA8AAAtADwAAC0APQAAPAAvAAA0AA8AALAADgAA0AAEAAAAAAAAAAAAHgAFbwAv///+AD////4AP////wA/v+q/AD8EAH8APwAAfgA+AAB+AD8AAD4APwAAPgA/gAA8AC/QAHgAP/kB9AAv/+/wAA///9AAB///QAAAf/0AAAAEQAAAAAAAAAAAH/4AAAD//8AAAv//8AAH///4AA/r//wAH0fB/AAsB4B+ADwHQD8APAcALwA8BwAeADwDACwAPAcAPAA+BwA8AD8LQHQALwtA8AAOBgCQAAQAAAAAHVURWQAv///+AC////4AP////gA//+q+AD4BwC8APQDADgA8AMANADwAwAAAPQHQAAA/AcAAAD8AQAAAPwAAAAA8AAAAABgAAAAAAAAAAAAAf//QAAD///QAAv///AAH/qv+AAvAAL8ADwAAPwANAAAvAB0BQA8ALQPADwAtA8APAC0DwA4ALwOAHgAPw9A8AA/D//gAC0P/8AABAr5AAAAAAAAAH/r5rgAf///9AB////0AL+///gAYAOQeAAAA0AUAAADQAAAAANAAAAAA4AAAKADwAQA+7/rvAD////4AL////gAv+v6+ACgAAAsAAAAAAQAAAAAAAB1BUAoAH//+/gAf///+AC////4APqv+vgAQAAAKAAAAAAEAAAAAAAKQAAAAP/AAAAb3//v+v+P/////w/////9D/+v/5AFAAEAAAAAAAAAAPVaVSgA////+AC////4AP////QA////+ACUf9V4AAD/4DgAAv/0EAAH9/wAAB/i/0AAP4D/wAD+AL/gAPgAL/AA8AAL+ACAAAH8AAAAAL4AAAAAPwAAAAAfAAAAAAsAAAAAAwAAAAADAAAAAAIAAAAAAADgFBQYAP7///wA////+AD////4APuvv/wAUAQAfAAAAAA4AAAAADgAAAAAOAAAAAA9AAAAAD8AAAAAeQAAAACwAAAAAFAAL///0AA/+lbwAP/AALQA/wAAIAD9AAAAAPwAAAAA/AAAAAD9AAAAAH9AAAAAD+AAAAAH/UBQAAH///gAB////AAf///4AD//6vQAv+oAEAD/QAAAAP8AAAAAvQAAAAC0AAAAAPgAABAAfgAAsAA/QALwAB/Qb/AAB///8AAB///gAAB//8AAABqUAAAAAAAAD+pZuq/v/////k/////kD/+AAAAO/8AAAAS/8AAAAD/wAAAAH/wAAAAP/AAAAAv9AAAABv4AAAQA/0AADAD/wAANAH/AAA0AL9AADkVf9AAP///9AA////8AD5ABWQAEAAAAAAAAAAAAAAH//8AAA///4AAP+r/wAB9AH/AALgAH8AB4AAH0ALAAALgA8AAAfADwAAA8APQAADgA/QAAMAC/QAGwAL/lC9AAf//vwAA///9AAB//+QAAB//wAAAAWQAAAAAAAAAIAAAAAH1WpVVB7//////P/////4/+///+DVRfVUAMAA8AAAwADwAADQAPAAAPAA4AAA/gHAAAD/74AAAP//AAAAf/8AAAAb+AAAAAFQAAAAAAAAAAAAD//8AAA///4AAL///wAA9QL/AALgAH9AB8AAH0ALQAALgAsAAAfADwAEA8APAA8DgA/AD4NAC/AL2wAH/UP9AAf//v0AA////wAB////AAB//x8AAAagA0AAAAADQAAAAAJAAAAAAACwAAAPAPVWqr4Av////AC////0AH///8AAf/7/AAB9QAQAAHAAAAAAcABgAAB8APAAAH4B/QAAf5v/QAA////AAD//f+gAD/0v/wAHpAv/wAAAAL/0AAAAL/gAAAAH/AAAAAH8AAAAALwAAAAAPAAAAAAcAAAAAA0AAAAABQAAAAAAAP/AHwAC/+AvQAP/8C9AC2/0D0APA/gPgA8C/AvADwL8B8APA/wHgA8D/QtADwP/rwAPgv//AAvA//wAB0D/+AAAAH/QAAAABUAAAAAAAAALgAAAAA8AAAAADwAAAAAPAAAAAA8AAAAADwAAAAAPQAARQA//7//AD////8AP////gA////+AD1WQD8APAAAHwA8AAAOADwAAAQAPAAAAAA+AAAAAD8AAAAAPwAAAAA9AAAAADgAAAAAEAAAAAA4L/5AAD2//8AAP///+AA////8AD/mv/wAPkAH/AAsAAH8AAAAAL4AAAAAPwAAAAA/AAAAAD8AAAAALQAAAAAtADAAADwAOQAJPAA////8AD////4AP////0A//v//w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"), 32, atob("BgYHDQsQEwQKCAgLBgkGDA4GCwoPCwwMCwwGBgwJCwoNGREQExEQEREICBcOHRUTERUaEBYXFh0TERIKCwcKCgYSDg0PDQ0NDgcGEg0XERAPEhINExERFw4ODwYFBgoK"), 20+(scale<<8)+(2<<16)); return this; -} +}; Graphics.prototype.setFontTreasurehuntDOYwE_40_N = function(scale) { // Actual height 40 (39 - 0) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAABwAAAAAHAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAB4AAAAAfwAAAAB/AAAAA/4AAAAH+AAAAB/gAAAAf4AAAAH+AAAAB/gAAAA/4AAAAP+AAAAB/wAAAA/8AAAAf/AAAAD/gAAAAf4AAAAA+AAAAADwAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AAAAAP/wAAAB//wAAAf//wAAD///AAAf8H+AAB+AH8AAPgAHwAB4AAPgAHAAAeAA4AAB4ADgAAHgAOAAAOAA4AAB4ADgAAHgAPAAAeAA+AADwAD8AAeAAH4AD4AAf4APgAA/8H8AAD///gAAD//8AAAH//gAAAH/4AAAAAYAAAAAAAAAAAAAAAAAAAAAAAAYAABAADgAAeAAf///4AD////gAP///+AA////4ABwAADgAAAAAGAAAAAAAAAAAAAAABwAAGAAPgAA4AB8AAHgAHwAA8AA8AAHwADgAA/AAMAAP8AB4AB/4AHgAf3gAPAH8eAA8B/B4AD4P4HgAP/+AeAA//wB4AB/8AHgAD/AAPAAPgAB8AAAAAPwAAAAB+AAAAAHgAAAAAAAAAAAAAAAAAAAAABgAAPwAOAMA/AB4DwAcAHAeAA4AOD4ADgA4/AAMADn8AAwAP/wADAA/3AAcAD8cADwAPh4AeAB4HgH4AHAfB/gAAA//8AAAB//gAAAD/8AAAAH/AAAAAEAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAeAAAAAD4AAAAAPgAAAAB+AAAAAP4AAAAB/gAAAAHOAAAAA44AAAAHjgAAAA8OAGAAPg4AYAD8Dh/gAP///+AB////wAH////gAP///+AA4BwA4AAAHAAgAAAMAAAAAB4AAAAAHgAAAAAeAAAAAB4AAAAAPAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAcAPAAADwAMAAA+AAwAAP4ADAAD/AAOAA/8AA4AD/wADAAP3AAMAA8cABwADhwAfAAOHgD4AA4fAfgADh//8AAfD//gAB8P/8AAHwf/gAAeB/4AABwD/AAAAAAAAAAAAAAAAAAAf/AAAAH//AAAA//+AAAP//4AAB/5/gAAP8A/AAB/wB8AAf3ADwAD8cAPAAfDwA4AB4PADgAPA8AeAB4DwDwAPAHgPAB4Afj4APAB//AB4AD/8APgAH/AA8AAP4ADgAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAPAAAAAB4AADgAPAAA+AB8AAP4AHwAD/gAfAAf4AB8AH+AADwA/gAAPAP4AAA8D+AAADw/wAAAPP8AAAA9/AAAAD/4AAAAP+AAAAA/gAAAAD4AAAAAfAAAAAAwAAAAACAAAAAAAAAAAAAAAAAAAAAAH8AAAAA/4AAAAP/wAADh//AAA/Pw+AAH+8B4AA//AHgAH/8AeAAcHgA4ADgeADgAOB4AOAB4HwB4AHg/gPgAfn/x8AB/9//gAD/3/8AAP+P/gAAPwf4AAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAACAAfgAAcAH/gADwA//AAeAD/8ADwAf/4AeADwHgDwAeAeAfABwA4D4APADgfAA4AOD4ADgA4/AAPADn4AA+AN/AAD4A/4AAP8f+AAA///wAAB//8AAAH//gAAAH/4AAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAHAeAAAA+D4AAAB4PwAAAHg/AAAAcB4AAAAAAAAAAAAAA"), 46, atob("CxgcCxcUHhUXFxYXCw=="), 40+(scale<<8)+(1<<16)); return this; -} +}; //Bluetooth on var btOnImg = { width : 15, height : 22, bpp : 3, transparent : 2, buffer : E.toArrayBuffer(atob("/////H//////A/////4A/////4BH////4JI////4JJH///4J5I///4BP5I///BJ/5A//BJPP5A/4JJ5P5H4J/PL/JHBJf/5JI4JJ/7JJIBP75/ZJAJZPJ/JIBJJ5/JJHBJP/JJA4JJ/JJI/4JPJJI//4BJJA////AAH//A==")) -} +}; var btOffImg = { width : 15, height : 22, bpp : 1, buffer : E.toArrayBuffer(atob("AAAAwAcAGgBEAQgEEBgQIDCAIQAiACgAUABgAMABgAKACQARAEGDAPgA")) -} +}; //Charging symbol var chrgOn = { @@ -68,7 +73,7 @@ var chrgOn = { transparent : 0, palette : new Uint16Array([65535,65504,63488,64928]), buffer : E.toArrayBuffer(atob("qterVKlcrVClcLVAlVfVVA1cDVANQA3ADQAPAAwA")) -} +}; function draw() { g.clear(); @@ -80,21 +85,21 @@ function draw() { drawBatteryStatus(); } function drawDate() { - days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"] - months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"]; + months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; var d = new Date(); var dateString = `${days[d.getDay()]} ${months[d.getMonth()]} ${d.getDate()}`; g.setFontTreasurehuntDOYwE_20(1); var sm = g.stringMetrics(dateString); - g.setColor(0,0,0).drawString(dateString, (g.width - sm.width) / 2, g.height - sm.height - 3); + g.setColor(g.theme.fg).drawString(dateString, (g.width - sm.width) / 2, g.height - sm.height - 3); } function drawTime(){ - var top = 100; pad = 6; bh=48; bw=62; linew=3 + var top = 100; pad = 6; bh=48; bw=62; linew=3; var boxH = {x:pad,y:top,x2:pad+bw,y2:top+bh}; var boxM = {x:g.width-pad-bw,y:top,x2:g.width-pad,y2:top+bh}; - var innerH = {x:boxH.x+linew, y:boxH.y+linew, x2:boxH.x2-linew, y2:boxH.y2-linew} - var innerM = {x:boxM.x+linew, y:boxM.y+linew, x2:boxM.x2-linew, y2:boxM.y2-linew} + var innerH = {x:boxH.x+linew, y:boxH.y+linew, x2:boxH.x2-linew, y2:boxH.y2-linew}; + var innerM = {x:boxM.x+linew, y:boxM.y+linew, x2:boxM.x2-linew, y2:boxM.y2-linew}; g.setColor(manaColors[bgIndex]).fillRect(boxH).fillRect(boxM).clearRect(innerH).clearRect(innerM); //Draw the hour and minute @@ -111,14 +116,14 @@ function drawTime(){ console.log `Hours: ${h}, x: ${xH}, y:${yH}`; var xM = (bw - mM.width)/2 + boxM.x+xOffset; var yM = (bh - mM.height)/2 +boxM.y+yOffset; - g.setColor(0,0,0).drawString (h, xH, yH).drawString(m, xM, yM); + g.setColor(g.theme.fg).drawString (h, xH, yH).drawString(m, xM, yM); } function drawBattery(){ var pad = 6; top=pad; bh=10; bw=40; linew=1; var box = {x:g.width-pad-bw,y:top,x2:g.width-pad,y2:top+bh}; - var innerB = {x:box.x+linew, y:box.y+linew, x2:box.x2-linew, y2:box.y2-linew} - var batteryFill={x:box.x+linew, y:box.y+linew, x2:(box.x-linew)+bw*E.getBattery()/100, y2:box.y2-linew} + var innerB = {x:box.x+linew, y:box.y+linew, x2:box.x2-linew, y2:box.y2-linew}; + var batteryFill={x:box.x+linew, y:box.y+linew, x2:(box.x-linew)+bw*E.getBattery()/100, y2:box.y2-linew}; g.setColor(manaColors[bgIndex]).fillRect(box).clearRect(innerB).setColor(0,1,0).fillRect(batteryFill); } diff --git a/apps/mtgwatchface/metadata.json b/apps/mtgwatchface/metadata.json index fd81ce10f..ac5fe9287 100644 --- a/apps/mtgwatchface/metadata.json +++ b/apps/mtgwatchface/metadata.json @@ -2,7 +2,7 @@ "id": "mtgwatchface", "name": "MTG Watchface", "shortName": "Magic the Gathering Watch Face", - "version": "1v03", + "version": "0.02", "description": "Magic the Gathering themed watch face. Embrace the inner wizzard. Dispay any of the different types of mana on your watch. Which color are you devoted to today? ", "icon": "icon.png", "screenshots": [ diff --git a/apps/openwind/ChangeLog b/apps/openwind/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/openwind/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/openwind/README.md b/apps/openwind/README.md new file mode 100644 index 000000000..1df7ea158 --- /dev/null +++ b/apps/openwind/README.md @@ -0,0 +1,22 @@ +# OpenWind + +Receive and display data from a wireless [OpenWind](https://www.openwind.de/) sailing wind instrument on the Bangle. + +## Usage + +Upon startup, the app will attempt to automatically connect to the wind instrument. This typically only takes a few seconds. + +## Features + +The app displays the apparent wind direction (via a green dot) and speed (green numbers, in knots) relative to the mounting direction of the wind vane. +If "True wind" is enabled in settings and a GPS fix is available, the true wind speed and direction (relative to the mounting direction of the vane) is +additionally displayed in red. In this mode, the speed over ground in knots is also shown at the bottom left of the screen. + +## Controls + +There are no controls in the main app, but there are two settings in the settings app that can be changed: + + * True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app + * Mounting angle: mounting relative to the boat of the wind instrument (in degrees) + +![](openwind_screenshot.png) diff --git a/apps/openwind/app-icon.js b/apps/openwind/app-icon.js new file mode 100644 index 000000000..b86738955 --- /dev/null +++ b/apps/openwind/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/AH4A/AH4AzhMJF94wtF+QwsF/4APnAACF54wZFoYxNF7guHGBQv0GCwuJGBIvFACov/AD4vvd6Yv/GCoumGIwtpAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHoA==")) diff --git a/apps/openwind/app.js b/apps/openwind/app.js new file mode 100644 index 000000000..b1c8fea4b --- /dev/null +++ b/apps/openwind/app.js @@ -0,0 +1,113 @@ +OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb'; +require("Font7x11Numeric7Seg").add(Graphics); +gatt = {}; +cx = g.getWidth()/2; +cy = 24+(g.getHeight()-24)/2; +w = (g.getWidth()-24)/2; + +gps_course = { spd: 0 }; + +var settings = require("Storage").readJSON('openwindsettings.json', 1) || {}; + +i = 0; +hullpoly = []; +for (y=-1; y<=1; y+=0.1) { + hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3; + hullpoly[i++] = cy - y*w*0.7; +} +for (y=1; y>=-1; y-=0.1) { + hullpoly[i++] = cx + (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3; + hullpoly[i++] = cy - y*w*0.7; +} + +function wind_updated(ev) { + if (ev.target.uuid == "0xcc91") { + awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1; + aws = ev.target.value.getInt16(3, true)*0.01; +// console.log(awa, aws); + if (gps_course.spd > 0) { + wv = { // wind vector (in fixed reference frame) + lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws, + lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws + }; + twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat }; + tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2)); + twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course; + if (twa<0) twa += 360; + if (twa>360) twa -=360; + } + else { + tws = -1; + twa = 0; + } + draw_compass(awa,aws,twa,tws); + } +} + +function draw_compass(awa, aws, twa, tws) { + g.clearRect(0, 24, g.getWidth()-1, g.getHeight()-1); + fh = w*0.15; + g.setColor(0, 0, 1).fillPoly(hullpoly); + g.setFontVector(fh).setColor(g.theme.fg); + g.setFontAlign(0, 0, 0).drawString("0", cx, 24+fh/2); + g.setFontAlign(0, 0, 1).drawString("90", g.getWidth()-12-fh, cy); + g.setFontAlign(0, 0, 2).drawString("180", cx, g.getHeight()-fh/2); + g.setFontAlign(0, 0, 3).drawString("270", 12+fh/2, cy); + for (i=0; i<4; ++i) { + a = i*Math.PI/2+Math.PI/4; + g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99); + } + g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1); + if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1); + g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06); + g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w); + if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w); + if (settings.truewind && typeof gps_course.spd!=='undefined') { + spd = gps_course.spd/1.852; + g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1); + } +} + +function parseDevice(d) { + device = d; + console.log("Found device"); + device.gatt.connect().then(function(ga) { + console.log("Connected"); + gatt = ga; + return ga.getPrimaryService("cc90"); +}).then(function(s) { + return s.getCharacteristic("cc91"); +}).then(function(c) { + c.on('characteristicvaluechanged', (event)=>wind_updated(event)); + return c.startNotifications(); +}).then(function() { + console.log("Done!"); +}).catch(function(e) { + console.log("ERROR"+e); +});} + +function connection_setup() { + NRF.setScan(); + NRF.setScan(parseDevice, { filters: [{services:["cc90"]}], timeout: 2000}); + console.log("Scanning for OW sensor"); +} + +if (settings.truewind) { + Bangle.on('GPS',function(fix) { + if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph + gps_course = + { lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852, + lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852, + spd: fix.speed, + course: fix.course + }; + } + else gps_course.spd = -1; + }); + Bangle.setGPSPower(1, "app"); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw_compass(0, 0, 0, 0); +connection_setup(); diff --git a/apps/openwind/app.png b/apps/openwind/app.png new file mode 100644 index 000000000..9fd64efba Binary files /dev/null and b/apps/openwind/app.png differ diff --git a/apps/openwind/metadata.json b/apps/openwind/metadata.json new file mode 100644 index 000000000..9229f7f25 --- /dev/null +++ b/apps/openwind/metadata.json @@ -0,0 +1,15 @@ +{ "id": "openwind", + "name": "OpenWind", + "shortName":"OpenWind", + "version":"0.01", + "description": "OpenWind", + "icon": "openwind.png", + "readme": "README.md", + "tags": "ble,outdoors,gps,sailing", + "supports" : ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"openwind.app.js","url":"app.js"}, + {"name":"openwind.img","url":"app-icon.js","evaluate":true}, + {"name":"openwind.settings.js", "url":"settings.js"} + ] +} diff --git a/apps/openwind/openwind.png b/apps/openwind/openwind.png new file mode 100644 index 000000000..9fd64efba Binary files /dev/null and b/apps/openwind/openwind.png differ diff --git a/apps/openwind/openwind_screenshot.png b/apps/openwind/openwind_screenshot.png new file mode 100644 index 000000000..05143e8a4 Binary files /dev/null and b/apps/openwind/openwind_screenshot.png differ diff --git a/apps/openwind/settings.js b/apps/openwind/settings.js new file mode 100644 index 000000000..a7e3a1abe --- /dev/null +++ b/apps/openwind/settings.js @@ -0,0 +1,44 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; +(function(back) { + const SETTINGS_FILE = 'openwindsettings.json' + // initialize with default settings... + let settings = { + 'truewind': false, + 'mount_angle': 0 + } + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const saved = storage.readJSON(SETTINGS_FILE, 1) || {} + for (const key in saved) { + settings[key] = saved[key]; + } + // creates a function to safe a specific setting, e.g. save('color')(1) + function save(key) { + return function (value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + } + const menu = { + '': { 'title': 'OpenWind' }, + '< Back': back, + 'True wind': { + value: settings.truewind, + format: boolFormat, + onchange: save('truewind'), + }, + 'Mounting angle': { + value: settings.mount_angle, + min: 0, + max: 355, + step: 5, + onchange: save('mount_angle'), + } + } + E.showMenu(menu); +}) diff --git a/apps/pebble/ChangeLog b/apps/pebble/ChangeLog index 01f653f48..274c34a34 100644 --- a/apps/pebble/ChangeLog +++ b/apps/pebble/ChangeLog @@ -1,8 +1,10 @@ -0.01: first release -0.02: included deployment of pebble.settings.js in apps.json +0.01: First release +0.02: Included deployment of pebble.settings.js in apps.json 0.03: Changed time+calendar font to LECO1976Regular, changed to slanting boot 0.04: Fix widget hiding code (fix #1046) 0.05: Fix typo in settings - Purple -0.06: Added dependancy on Pedometer Widget -0.07: Fixed icon and ong file to 48x48 -0.08: Added theme options and optional lock symbol +0.06: Add dependancy on Pedometer Widget +0.07: Fix icon and ong file to 48x48 +0.08: Add theme options and optional lock symbol +0.09: Add support for internationalization (LANG placeholders + "locale" module) + Get steps from built-in step counter (widpedom no more needed, fix #1697) diff --git a/apps/pebble/README.md b/apps/pebble/README.md index 953db0ba7..051f46e37 100644 --- a/apps/pebble/README.md +++ b/apps/pebble/README.md @@ -1,12 +1,10 @@ # Pebble - *a Pebble style clock with configurable background color, to keep the revolution going* +*A Pebble style clock with configurable background color, to keep the revolution going* * Designed specifically for Bangle 2 -* A choice of 6 different background colous through its setting menu. Goto Settings, App/Widget settings, Pebble. +* A choice of 6 different background colous through its settings menu. Goto *Settings* → *Apps* → *Pebble*. * Supports the Light and Dark themes (or set theme independently) -* Uses pedometer widget to get latest step count -* Dependant apps are installed when Pebble installs * Uses the whole screen, widgets are made invisible but still run in the background * When battery is less than 30% main screen goes Red * Optionally show a lock symbol when screen is locked (default off, enable in Settings) diff --git a/apps/pebble/metadata.json b/apps/pebble/metadata.json index eb049e78e..f3c1fcc12 100644 --- a/apps/pebble/metadata.json +++ b/apps/pebble/metadata.json @@ -2,9 +2,8 @@ "id": "pebble", "name": "Pebble Clock", "shortName": "Pebble", - "version": "0.08", + "version": "0.09", "description": "A pebble style clock to keep the rebellion going", - "dependencies": {"widpedom":"app"}, "readme": "README.md", "icon": "pebble.png", "screenshots": [{"url":"pebble_screenshot.png"}], diff --git a/apps/pebble/pebble.app.js b/apps/pebble/pebble.app.js index 062592e47..774b24c3f 100644 --- a/apps/pebble/pebble.app.js +++ b/apps/pebble/pebble.app.js @@ -27,18 +27,21 @@ const h3 = 7*h/8; let batteryWarning = false; function draw() { + let locale = require("locale"); let date = new Date(); - let da = date.toString().split(" "); - let timeStr = da[4].substr(0,5); + let dayOfWeek = locale.dow(date, 1).toUpperCase(); + let dayOfMonth = date.getDate(); + let time = locale.time(date, 1); + let steps = Bangle.getHealthStatus("day").steps; const t = 6; - // turn the warning on once we have dipped below 30% - if (E.getBattery() < 30) + if (E.getBattery() < 30) { + // turn the warning on once we have dipped below 30% batteryWarning = true; - - // turn the warning off once we have dipped above 40% - if (E.getBattery() > 40) + } else if (E.getBattery() > 40) { + // turn the warning off once we have dipped above 40% batteryWarning = false; + } g.reset(); g.setColor(settings.bg); @@ -49,15 +52,11 @@ function draw() { g.fillRect(0, h2 - t, w, h2); // day and steps - //if (settings.color == 'Blue' || settings.color == 'Red') - // g.setColor('#fff'); // white on blue or red best contrast - //else - // g.setColor('#000'); // otherwise black regardless of theme g.setColor(theme.day); g.setFontLECO1976Regular22(); g.setFontAlign(0, -1); - g.drawString(da[0].toUpperCase(), w/4, ha); // day of week - g.drawString(getSteps(), 3*w/4, ha); + g.drawString(dayOfWeek, w/4, ha); + g.drawString(steps, 3*w/4, ha); // time // white on red for battery warning @@ -67,7 +66,7 @@ function draw() { g.setFontLECO1976Regular42(); g.setFontAlign(0, -1); g.setColor(!batteryWarning ? theme.fg : '#fff'); - g.drawString(timeStr, w/2, h2 + 8); + g.drawString(time, w/2, h2 + 8); // contrast bar g.setColor(theme.fg); @@ -79,8 +78,8 @@ function draw() { g.setColor(settings.bg); g.drawImage(img, w/2 + ((w/2) - 64)/2, 1, { scale: 1 }); - drawCalendar(((w/2) - 42)/2, 14, 42, 4, da[2]); - + drawCalendar(((w/2) - 42)/2, 14, 42, 4, dayOfMonth); + drawLock(); } @@ -103,20 +102,12 @@ function drawCalendar(x,y,wi,th,str) { g.drawString(str, x + wi/2, y + wi/2 + th); } -function getSteps() { - if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return '????'; -} - function loadThemeColors() { theme = {fg: g.theme.fg, bg: g.theme.bg, day: g.toColor(0,0,0)}; if (settings.theme === "Dark") { theme.fg = g.toColor(1,1,1); theme.bg = g.toColor(0,0,0); - } - else if (settings.theme === "Light") { + } else if (settings.theme === "Light") { theme.fg = g.toColor(0,0,0); theme.bg = g.toColor(1,1,1); } @@ -144,14 +135,18 @@ Bangle.on('lock', function(on) { g.clear(); Bangle.loadWidgets(); -/* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ -for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + +// We are not drawing the widgets as we are taking over the whole screen +// so we will blank out the draw() functions of each widget and change the +// area to the top bar doesn't get cleared. +for (let wd of WIDGETS) { + wd.draw=()=>{}; + wd.area=""; +} + loadSettings(); loadThemeColors(); setInterval(draw, 15000); // refresh every 15s draw(); + Bangle.setUI("clock"); diff --git a/apps/pebble/pebble.settings.js b/apps/pebble/pebble.settings.js index 8a5fba63b..f1c065db4 100644 --- a/apps/pebble/pebble.settings.js +++ b/apps/pebble/pebble.settings.js @@ -1,21 +1,23 @@ (function(back) { const SETTINGS_FILE = "pebble.json"; - // initialize with default settings... + // TODO Only the color/theme indices should be written in the settings file so the labels can be translated + + // Initialize with default settings... let s = {'bg': '#0f0', 'color': 'Green', 'theme':'System', 'showlock':false} // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings - const storage = require('Storage') + const storage = require('Storage'); let settings = storage.readJSON(SETTINGS_FILE, 1) || s; - const saved = settings || {} + const saved = settings || {}; for (const key in saved) { s[key] = saved[key] } function save() { - settings = s - storage.write(SETTINGS_FILE, settings) + settings = s; + storage.write(SETTINGS_FILE, settings); } var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; @@ -24,8 +26,8 @@ E.showMenu({ '': { 'title': 'Pebble Clock' }, - '< Back': back, - 'Colour': { + /*LANG*/'< Back': back, + /*LANG*/'Colour': { value: 0 | color_options.indexOf(s.color), min: 0, max: 5, format: v => color_options[v], @@ -35,7 +37,7 @@ save(); } }, - 'Theme': { + /*LANG*/'Theme': { value: 0 | theme_options.indexOf(s.theme), min: 0, max: theme_options.length - 1, format: v => theme_options[v], @@ -44,13 +46,13 @@ save(); } }, - 'Show Lock': { + /*LANG*/'Show Lock': { value: settings.showlock, - format: () => (settings.showlock ? 'Yes' : 'No'), + format: () => (settings.showlock ? /*LANG*/'Yes' : /*LANG*/'No'), onchange: () => { settings.showlock = !settings.showlock; save(); } }, }); -}) +}); diff --git a/apps/qmsched/icon.js b/apps/qmsched/icon.js index c0f4e2b66..53aeb55af 100644 --- a/apps/qmsched/icon.js +++ b/apps/qmsched/icon.js @@ -1,2 +1 @@ -// https://icons8.com/icon/19324/no-reminders -require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AElksF1wwtF4YwO0WiGFguBGFovfGB3MAAgwnFooxfGBAuJGEguLGEV/F5owh0YvpGH4vhGCQvd0YwQF7vMGCAveGCAvfGB4vgGBwvhGBouhGFLkIGEouIGEwvKGBguiGEQuNGEHN5owa5ouQ53P5/O5wyOGA3NDAIbBLyAUCAAQzCNBQwF0gVDXiQoBGQgAEEIILE0iSJdiozCFQw1FGBJgSABSVIeg7wQGSDDMFyQ0VCQQwdAAWcAAwPHGD4vPGD+iAAwRJGEgRLGEQRNeTwARF1wA/AH4AX")) \ No newline at end of file +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AElksF1wwtF4YwO0WiGFguBGFovfGB3MAAgwnFooxfGBAuJGEguLGEV/F5owh0YvpGH4vhGCQvd0YwQF7vMGCAveGCAvfGB4vgGBwvhGBouhGFLkIGEouIGEwvKGBguiGEQuNGEHN5owa5ouQ53P5/O5wyOGA3NDAIbBLyAUCAAQzCNBQwF0gVDXiQoBGQgAEEIILE0iSJdiozCFQw1FGBJgSABSVIeg7wQGSDDMFyQ0VCQQwdAAWcAAwPHGD4vPGD+iAAwRJGEgRLGEQRNeTwARF1wA/AH4AX")) diff --git a/apps/red7game/ChangeLog b/apps/red7game/ChangeLog index 93dd81c5b..360b1a305 100644 --- a/apps/red7game/ChangeLog +++ b/apps/red7game/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial version of game 0.02: Fix mistake preventing game from ending in some cases. -0.03: Update help screen with more details. \ No newline at end of file +0.03: Update help screen with more details. +0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game. diff --git a/apps/red7game/metadata.json b/apps/red7game/metadata.json index c8f9e1aa1..15fec4d21 100644 --- a/apps/red7game/metadata.json +++ b/apps/red7game/metadata.json @@ -2,7 +2,7 @@ "name": "Red 7 Card Game", "shortName" : "Red 7", "icon": "icon.png", - "version":"0.03", + "version":"0.04", "description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!", "tags": "game", "supports":["BANGLEJS2"], diff --git a/apps/red7game/red7.js b/apps/red7game/red7.js index 901bc186f..2b22488b9 100644 --- a/apps/red7game/red7.js +++ b/apps/red7game/red7.js @@ -35,19 +35,27 @@ class Card { get clipRect() { return this.clippedRect; } + fillRect(x,y,x2,y2,r) { + //This function allows using new rounded corners on newer firmware + if(process.env.VERSION === "2v12" || process.env.VERSION === "2v11" || process.env.VERSION === "2v10") { + g.fillRect(x,y,x2,y2); + } else { + g.fillRect({x:x,y:y,x2:x2,y2:y2,r:r}); + } + } draw(x,y,outlined) { this.rect = {x1: x, x2: x+80, y1: y, y2: y+100}; this.clippedRect = {x1: x, x2: x+24, y1: y, y2: y+100}; var colorIndex = colors.indexOf(this.cardColor); var colorArr = colorsHex[colorIndex]; var colorRule = colorsRules[colorIndex]; - g.setColor(colorArr); - g.setBgColor(colorArr); - g.fillRect(x,y,x+80,y+100); if(outlined) { g.setColor(0,0,0); - g.drawRect(x,y,x+80,y+100); + this.fillRect(x-1,y-1,x+81,y+101,6); } + g.setColor(colorArr); + g.setBgColor(colorArr); + this.fillRect(x,y,x+80,y+100,6); g.setColor(255,255,255); g.setFont("Vector:40"); g.setFontAlign(0,0,0); @@ -61,21 +69,23 @@ class Card { drawBack(x,y,flipped) { this.rect = {x1: x, x2: x+80, y1: y, y2: y-100}; this.clippedRect = {x1: x, x2: x+24, y1: y, y2: y-100}; - g.setColor(255,255,255); g.setBgColor(0,0,0); if(flipped) { - g.fillRect(x,y,x+80,-100); g.setColor(0,0,0); - g.drawRect(x,y,x+80,-100); + this.fillRect(x-1,y+1,x+81,y-101,6); + g.setColor(255,255,255); + this.fillRect(x,y,x+80,y-100,6); g.setFontAlign(0,0,2); g.setColor(255,0,0); g.setBgColor(255,255,255); g.setFont("Vector:40"); //g.drawString(7,x+40,y-40,true); } else { - g.fillRect(x,y,x+80,y+100); g.setColor(0,0,0); - g.drawRect(x,y,x+80,y+100); + this.fillRect(x-1,y-1,x+81,y+101,6); + g.setColor(255,255,255); + this.fillRect(x,y,x+80,y+100,6); + g.setColor(0,0,0); g.setFontAlign(0,0,0); g.setColor(255,0,0); g.setBgColor(255,255,255); @@ -91,7 +101,7 @@ class Card { var colorRule = colorsRules[colorIndex]; g.setColor(colorArr); g.setBgColor(colorArr); - g.fillRect(x,y,x+110,y+45); + this.fillRect(x,y,x+110,y+45,6); g.setColor(255,255,255); g.setFontAlign(0,0,0); g.setFont("6x8:2"); @@ -104,7 +114,7 @@ class Card { var colorArr = colorsHex[colorIndex]; g.setColor(colorArr); g.setBgColor(colorArr); - g.fillRect(x,y,x+20,y+20); + this.fillRect(x,y,x+20,y+20,2); g.setFontAlign(0,0,0); g.setFont("6x8:2"); g.setColor(255,255,255); @@ -703,8 +713,8 @@ function drawScreenHelp() { } function drawGameOver(win) { + startedGame = false; E.showAlert(win ? "AI has given up. You Win!" : "You cannot play. AI wins.").then(function(){ - startedGame = false; undoStack = []; drawMainMenu(); }); diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index b90f7ebbc..7372f9c4a 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -1,3 +1,6 @@ 0.01: New App! 0.02: Fix scheduling of other alarms if there is a pending alarm from the past (fix #1667) 0.03: Fix `getTimeToAlarm` for a timer already used at same day, don't set `last` for timers. +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 diff --git a/apps/sched/README.md b/apps/sched/README.md index ed08139eb..47507fc14 100644 --- a/apps/sched/README.md +++ b/apps/sched/README.md @@ -8,8 +8,17 @@ Other apps can use this to provide alarm functionality. App --- -The Alarm app allows you to add/modify any running timers. +The **Alarms & Timers** app allows you to add/modify any running alarms and timers. +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 +- `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 Internals / Library ------------------- @@ -53,21 +62,27 @@ use too much RAM. It can be used as follows: ``` -// add/update an existing alarm +// Get a new alarm with default values +let alarm = require("sched").newDefaultAlarm(); + +// Get a new timer with default values +let timer = require("sched").newDefaultTimer(); + +// Add/update an existing alarm require("sched").setAlarm("mytimer", { msg : "Wake up", - timer : 10*60*1000, // 10 Minutes + timer : 10 * 60 * 1000 // 10 minutes }); // Ensure the widget and alarm timer updates to schedule the new alarm properly require("sched").reload(); // Get the time to the next alarm for us -var timeToNext = require("sched").getTimeToAlarm(require("sched").getAlarm("mytimer")); -// timeToNext===undefined if no alarm or alarm disabled +let timeToNext = require("sched").getTimeToAlarm(require("sched").getAlarm("mytimer")); +// timeToNext === undefined if no alarm or alarm disabled -// delete an alarm +// Delete an alarm require("sched").setAlarm("mytimer", undefined); -// reload after deleting... +// Reload after deleting require("sched").reload(); // Or add an alarm that runs your own code - in this case @@ -76,12 +91,15 @@ require("sched").reload(); require("sched").setAlarm("customrunner", { appid : "myapp", js : "load('setting.app.js')", - timer : 1*60*1000, // 1 Minute + timer : 1 * 60 * 1000 // 1 minute }); // If you have been specifying `appid` you can also find any alarms that // your app has created with the following: -require("sched").getAlarms().filter(a=>a.appid=="myapp"); +require("sched").getAlarms().filter(a => a.appid == "myapp"); + +// Get the scheduler settings +let settings = require("sched").getSettings(); ``` If your app requires alarms, you can specify that the alarms app needs to diff --git a/apps/sched/lib.js b/apps/sched/lib.js index d55a05475..58ba5daf0 100644 --- a/apps/sched/lib.js +++ b/apps/sched/lib.js @@ -37,9 +37,9 @@ exports.setAlarm = function(id, alarm) { exports.getTimeToAlarm = function(alarm, time) { if (!alarm) return undefined; if (!time) time = new Date(); - var active = alarm.on && (alarm.dow>>time.getDay())&1 && (!alarm.date || alarm.date==time.toISOString().substr(0,10)); - if (!active) return undefined; var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000); + var active = alarm.on && (alarm.dow>>((time.getDay()+(alarm.t { 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) }; +} + +// time in { hrs, mins } -> ms +exports.encodeTime = function(o) { + return o.hrs * 3600000 + o.mins * 60000; +} + +exports.formatTime = function(t) { + let o = exports.decodeTime(t); + return o.hrs + ":" + ("0" + o.mins).substr(-2); +} diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 1c0e8a1c8..5f61d7d04 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.03", + "version": "0.06", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", @@ -12,7 +12,8 @@ {"name":"sched.boot.js","url":"boot.js"}, {"name":"sched.js","url":"sched.js"}, {"name":"sched.img","url":"app-icon.js","evaluate":true}, - {"name":"sched","url":"lib.js"} + {"name":"sched","url":"lib.js"}, + {"name":"sched.settings.js","url":"settings.js"} ], - "data": [{"name":"sched.json"}] + "data": [{"name":"sched.json"}, {"name":"sched.settings.json"}] } diff --git a/apps/sched/sched.js b/apps/sched/sched.js index 9096fe4bf..7c97600d9 100644 --- a/apps/sched/sched.js +++ b/apps/sched/sched.js @@ -5,21 +5,11 @@ if (Bangle.SCHED) { delete Bangle.SCHED; } -// time in ms -> { hrs, mins } -function decodeTime(t) { - t = 0|t; // sanitise - var hrs = 0|(t/3600000); - return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) }; -} - -function formatTime(t) { - var o = decodeTime(t); - return o.hrs+":"+("0"+o.mins).substr(-2); -} - function showAlarm(alarm) { - var msg = ""; - msg += alarm.timer ? formatTime(alarm.timer) : formatTime(alarm.t); + const settings = require("sched").getSettings(); + + let msg = ""; + msg += alarm.timer ? require("sched").formatTime(alarm.timer) : require("sched").formatTime(alarm.t); if (alarm.msg) { msg += "\n"+alarm.msg; } else { @@ -28,9 +18,12 @@ function showAlarm(alarm) { else msg = atob("AC0swgF97///RcEpMlVVVVVVf9VVVVVVVVX/9VVf9VVf/1VVV///1Vf9VX///VVX///VWqqlV///1Vf//9aqqqqpf//9V///2qqqqqqn///V///6qqqqqqr///X//+qqoAAKqqv//3//6qoAAAAKqr//3//qqAAAAAAqq//3/+qoAADwAAKqv/3/+qgAADwAACqv/3/aqAAADwAAAqp/19qoAAADwAAAKqfV1qgAAADwAAACqXVWqgAAADwAAACqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAAOsAAAAKpVaoAAAAOsAAAAKpVaoAAAAL/AAAAKpVaoAAAAgPwAAAKpVaoAAACAD8AAAKpVWqAAAIAA/AAAqlVWqAAAgAAPwAAqlVWqAACAAADwAAqlVWqgAIAAAAAACqlVVqgAgAAAAAACqVVVqoAAAAAAAAKqVVVaqAAAAAAAAqpVVVWqgAAAAAACqlVVVWqoAAAAAAKqlVVVVqqAAAAAAqqVVVVVaqoAAAAKqpVVVVVeqqoAAKqqtVVVVV/6qqqqqqr/VVVVX/2qqqqqqn/1VVVf/VaqqqqpV/9VVVf9VVWqqlVVf9VVVf1VVVVVVVVX9VQ==")+" "+msg; } + Bangle.loadWidgets(); Bangle.drawWidgets(); - var buzzCount = 10; + + let buzzCount = settings.buzzCount; + E.showPrompt(msg,{ title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!", buttons : {/*LANG*/"Snooze":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins @@ -38,7 +31,7 @@ function showAlarm(alarm) { buzzCount = 0; if (sleep) { if(alarm.ot===undefined) alarm.ot = alarm.t; - alarm.t += 10*60*1000; // 10 minutes + alarm.t += settings.defaultSnoozeMillis; } else { if (!alarm.timer) alarm.last = (new Date()).getDate(); if (alarm.ot!==undefined) { @@ -51,24 +44,35 @@ function showAlarm(alarm) { require("sched").setAlarms(alarms); load(); }); + function buzz() { - require("buzz").pattern(alarm.vibrate===undefined?"..":alarm.vibrate).then(function() { - if (buzzCount--) - setTimeout(buzz, 3000); - else if(alarm.as) { // auto-snooze - buzzCount = 10; - setTimeout(buzz, 600000); + if (settings.unlockAtBuzz) { + Bangle.setLocked(false); + } + + require("buzz").pattern(alarm.vibrate === undefined ? ".." : alarm.vibrate).then(() => { + if (buzzCount--) { + setTimeout(buzz, settings.buzzIntervalMillis); + } else if (alarm.as) { // auto-snooze + buzzCount = settings.buzzCount; + setTimeout(buzz, settings.defaultSnoozeMillis); } }); } - if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; + + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) + return; + buzz(); } // Check for alarms -var alarms = require("sched").getAlarms(); -var active = require("sched").getActiveAlarms(alarms); -if (active.length) // if there's an alarm, show it +let alarms = require("sched").getAlarms(); +let active = require("sched").getActiveAlarms(alarms); +if (active.length) { + // if there's an alarm, show it showAlarm(active[0]); -else // otherwise just go back to default app +} else { + // otherwise just go back to default app setTimeout(load, 100); +} diff --git a/apps/sched/settings.js b/apps/sched/settings.js new file mode 100644 index 000000000..642e43b43 --- /dev/null +++ b/apps/sched/settings.js @@ -0,0 +1,72 @@ +(function (back) { + let settings = require("sched").getSettings(); + + E.showMenu({ + "": { "title": /*LANG*/"Scheduler" }, + + /*LANG*/"< Back": () => back(), + + /*LANG*/"Unlock at Buzz": { + value: settings.unlockAtBuzz, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.unlockAtBuzz = v; + require("sched").setSettings(settings); + } + }, + + /*LANG*/"Default Auto Snooze": { + value: settings.defaultAutoSnooze, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.defaultAutoSnooze = v; + require("sched").setSettings(settings); + } + }, + + /*LANG*/"Default Snooze": { + value: settings.defaultSnoozeMillis / 60000, + min: 5, + max: 30, + step: 5, + format: v => v + /*LANG*/" min", + onchange: v => { + settings.defaultSnoozeMillis = v * 60000; + require("sched").setSettings(settings); + } + }, + + /*LANG*/"Buzz Count": { + value: settings.buzzCount, + min: 5, + max: 15, + step: 1, + onchange: v => { + settings.buzzCount = v; + require("sched").setSettings(settings); + } + }, + + /*LANG*/"Buzz Interval": { + value: settings.buzzIntervalMillis / 1000, + min: 1, + max: 5, + step: 1, + format: v => v + /*LANG*/"s", + onchange: v => { + settings.buzzIntervalMillis = v * 1000; + require("sched").setSettings(settings); + } + }, + + /*LANG*/"Default Alarm Pattern": require("buzz_menu").pattern(settings.defaultAlarmPattern, v => { + settings.defaultAlarmPattern = v; + require("sched").setSettings(settings); + }), + + /*LANG*/"Default Timer Pattern": require("buzz_menu").pattern(settings.defaultTimerPattern, v => { + settings.defaultTimerPattern = v; + require("sched").setSettings(settings); + }) + }); +}); diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index dbc3a0b82..875b3c1da 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: Respect Quiet Mode 0.03: Add compatibility for Bangle.js 2 and new firmware, added "Alarm at " for the alarm time +0.04: Read alarms from new scheduling library, account for higher acceleration sensor noise on Bangle.js 2 +0.05: Refactor decodeTime() to scheduling library diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index e963f2c40..236b71c0b 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -1,5 +1,5 @@ const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2 -const alarms = require("Storage").readJSON("alarm.json",1)||[]; +const alarms = require("Storage").readJSON("sched.json",1)||[]; const active = alarms.filter(a=>a.on); // Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS): @@ -9,12 +9,12 @@ const active = alarms.filter(a=>a.on); // Function needs to be called for every measurement but returns a value at maximum once a second (see winwidth) // start of sleep marker is delayed by sleepthresh due to continous data reading const winwidth=13; -const nomothresh=0.006; +const nomothresh=0.03; // 0.006 was working on Bangle1, but Bangle2 has higher noise. const sleepthresh=600; var ess_values = []; var slsnds = 0; -function calc_ess(val) { - ess_values.push(val); +function calc_ess(acc_magn) { + ess_values.push(acc_magn); if (ess_values.length == winwidth) { // calculate standard deviation over ~1s @@ -42,9 +42,8 @@ function calc_ess(val) { var nextAlarm; active.forEach(alarm => { const now = new Date(); - const alarmHour = alarm.hr/1; - const alarmMinute = Math.round((alarm.hr%1)*60); - var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), alarmHour, alarmMinute); + const t = require("sched").decodeTime(alarm.t); + var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), t.hrs, t.mins); if (dateAlarm < now) { // dateAlarm in the past, add 24h dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000)); } @@ -118,9 +117,9 @@ if (nextAlarm !== undefined) { // minimum alert 30 minutes early minAlarm.setTime(nextAlarm.getTime() - (30*60*1000)); - setInterval(function() { + Bangle.on('accel', (accelData) => { // 12.5Hz const now = new Date(); - const acc = Bangle.getAccel().mag; + const acc = accelData.mag; const swest = calc_ess(acc); if (swest !== undefined) { @@ -136,7 +135,7 @@ if (nextAlarm !== undefined) { buzz(); measure = false; } - }, 80); // 12.5Hz + }); drawApp(); } else { E.showMessage('No Alarm'); diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json index ed0f21028..aecfa36e4 100644 --- a/apps/sleepphasealarm/metadata.json +++ b/apps/sleepphasealarm/metadata.json @@ -2,11 +2,12 @@ "id": "sleepphasealarm", "name": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm", - "version": "0.03", + "version": "0.05", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "icon": "app.png", "tags": "alarm", "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies": {"scheduler":"type"}, "storage": [ {"name":"sleepphasealarm.app.js","url":"app.js"}, {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} diff --git a/apps/thermomF/app.js b/apps/thermomF/app.js index 2961e1efc..ac754d448 100644 --- a/apps/thermomF/app.js +++ b/apps/thermomF/app.js @@ -23,6 +23,6 @@ setInterval(function() { drawTemperature(); }, 20000); drawTemperature(); -E.showMessage("Loading..."); +E.showMessage(/*LANG*/"Loading..."); Bangle.loadWidgets(); Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/timecal/ChangeLog b/apps/timecal/ChangeLog index e48145b4b..57e7a1758 100644 --- a/apps/timecal/ChangeLog +++ b/apps/timecal/ChangeLog @@ -7,4 +7,5 @@ -> added settings to render cal view begin day (-1: today, 0:sunday, 1:monday [default]) 0.03: a lot of more settings for outline, colors and highlights 0.04: finalized README, fixed settings cancel, fixed border-setting -0.05: bugfix: default settings \ No newline at end of file +0.05: bugfix: default settings +0.06: bugfix: Mrk.Color doesn't reflect the color selected, fixes #1706 diff --git a/apps/timecal/README.md b/apps/timecal/README.md index d26f9ba4d..8c5d619ad 100644 --- a/apps/timecal/README.md +++ b/apps/timecal/README.md @@ -8,15 +8,15 @@ Shows the ### The settings menu Calendar View can be customized -* < Save: Exist and save the current settings -* Show date: Choose if and how the date is displayed: none, locale (default), monthfull or monthshort.yearshort #weeknum with 0 prefixed -* Start wday: Set day of week start. Values: 0=Sunday, 1=Monday,...,6=Saturday or -1=Relative to today (default 0: Sunday) -* Su color: Set Sundays color. Values: none (default), red, green or blue -* Border: show or none (default) +* < Save: Exit and save the current settings +* Show date: Choose if and how the date is displayed: none, locale [default], monthfull or monthshort.yearshort #weeknum with 0 prefixed +* Start wday: Set day of week start. Values: 0=Sunday, 1=Monday,...,6=Saturday or -1=Relative to today [default 0: Sunday] +* Su color: Set Sundays color. Values: none [default], red, green or blue +* Border: show or none [default] * Submenu Today settings - choose how today is highlighted * < Back: - * Color: none, red (default), green or blue - * Marker: Outline today graphically. Values: none (default), circle, rect(angle) - * Mrk.Color: Circle/rectangle color: red (default), green or blue - * Mrk.Size: Circle/rectangle thickness in pixel: min:1, max: 10, default:3 -* < Cancel: Exit and no change. Nevertheless missing default settings and superflous settings will be removed and saved. + * Color: none, red [default], green or blue + * Marker: Highlight today graphically. Values: none [default], circle, rectangle or filled + * Mrk.Color: Circle/rectangle color: red, green [default] or blue + * Mrk.Size: Circle/rectangle thickness in pixel: min: 1 to 10:max [default:3] +* < Cancel: Exit and no change. (Nevertheless missing default settings will be added and superflous settings will be removed.) diff --git a/apps/timecal/metadata.json b/apps/timecal/metadata.json index f439f4e9c..287dce0ae 100644 --- a/apps/timecal/metadata.json +++ b/apps/timecal/metadata.json @@ -1,7 +1,7 @@ { "id": "timecal", "name": "TimeCal", "shortName":"TimeCal", - "version":"0.05", + "version":"0.06", "description": "TimeCal shows the date/time along with a 3 week calendar", "icon": "icon.png", "type": "clock", diff --git a/apps/timecal/timecal.settings.js b/apps/timecal/timecal.settings.js index e86f3d8b8..8a7867c0d 100644 --- a/apps/timecal/timecal.settings.js +++ b/apps/timecal/timecal.settings.js @@ -64,9 +64,7 @@ format: v => v ? /*LANG*/"show" : /*LANG*/"none", onchange: v => chngdSttngs.calBrdr = v }, - /*LANG*/"Today settings": () => { - showTodayMenu(); - }, + /*LANG*/"Today settings": () => showTodayMenu(), /*LANG*/"< Cancel": () => cancelExitSettings() }); }; @@ -75,9 +73,9 @@ E.showMenu({ "": { "title": /*LANG*/"Today settings" - }, - "< Back": () => showMainMenu(), - /*LANG*/"Color": { + }, + "< Back": () => showMainMenu(), + /*LANG*/"Color": { value: chngdSttngs.tdyNumClr, min: 0, max: 3, format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], @@ -91,8 +89,8 @@ }, /*LANG*/"Mrk.Color": { value: chngdSttngs.tdyMrkClr, - min: 0, max: 2, - format: v => [/*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + min: 1, max: 3, + format: v => [undefined, /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], onchange: v => chngdSttngs.tdyMrkClr = v }, /*LANG*/"Mrk.Size": { @@ -101,8 +99,8 @@ format: v => v+"px", onchange: v => chngdSttngs.tdyMrkPxl = v }, - /*LANG*/"< Cancel": () => cancelExitSettings() - }); + /*LANG*/"< Cancel": () => cancelExitSettings() + }); }; showMainMenu(); diff --git a/apps/toucher/app.js b/apps/toucher/app.js index aab50fbda..19310592e 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -255,7 +255,7 @@ function run(){ if (process.env.HWVERSION == 1) Bangle.setLCDMode(); g.clear(); g.flip(); - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); load(app.src); } diff --git a/apps/viewstl/viewstl.app.js b/apps/viewstl/viewstl.app.js index 0b2512176..34d018705 100644 --- a/apps/viewstl/viewstl.app.js +++ b/apps/viewstl/viewstl.app.js @@ -354,7 +354,7 @@ function loadFile(fn) { Bangle.setLCDMode("direct"); g.clear(); E.showMenu(); - E.showMessage("Loading...", fn); + E.showMessage(/*LANG*/"Loading...", fn); readSTL(fn); zDist = 5*largestExtent(points); g.clear(); diff --git a/apps/viewstl/viewstl.min.js b/apps/viewstl/viewstl.min.js index 77469042c..82975bbf9 100644 --- a/apps/viewstl/viewstl.min.js +++ b/apps/viewstl/viewstl.min.js @@ -216,7 +216,7 @@ function loadFile(fn) { Bangle.setLCDMode("direct"); g.clear(); E.showMenu(); - E.showMessage("Loading...", fn); + E.showMessage(/*LANG*/"Loading...", fn); readSTL(fn); zDist = 5*largestExtent(points); g.clear(); diff --git a/apps/widcal/ChangeLog b/apps/widcal/ChangeLog index a4bc24d1a..07b8f7424 100644 --- a/apps/widcal/ChangeLog +++ b/apps/widcal/ChangeLog @@ -1 +1,2 @@ -0.01: First version \ No newline at end of file +0.01: First version +0.02: Fix memory leak \ No newline at end of file diff --git a/apps/widcal/metadata.json b/apps/widcal/metadata.json index 74ab6d488..fc7d6dd1d 100644 --- a/apps/widcal/metadata.json +++ b/apps/widcal/metadata.json @@ -1,7 +1,7 @@ { "id": "widcal", "name": "Calendar Widget", - "version": "0.01", + "version": "0.02", "description": "Widget with the current date", "icon": "widget.png", "type": "widget", diff --git a/apps/widcal/widget.js b/apps/widcal/widget.js index 4214d280a..d4a4676a7 100644 --- a/apps/widcal/widget.js +++ b/apps/widcal/widget.js @@ -24,7 +24,8 @@ ]); } // redraw when date changes - setTimeout(()=>WIDGETS["cal"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); + if (WIDGETS["cal"].to) clearTimeout(WIDGETS["cal"].to); + WIDGETS["cal"].to = setTimeout(()=>WIDGETS["cal"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); } }; })(); diff --git a/apps/widstep/ChangeLog b/apps/widstep/ChangeLog new file mode 100644 index 000000000..55cda0f21 --- /dev/null +++ b/apps/widstep/ChangeLog @@ -0,0 +1 @@ +0.01: New widget diff --git a/apps/widstep/README.md b/apps/widstep/README.md new file mode 100644 index 000000000..c41b025cd --- /dev/null +++ b/apps/widstep/README.md @@ -0,0 +1,9 @@ +# Step counter widget +This is my step counter. There are many like it, but this one is mine. + +A pedometer widget designed to be as narrow as possible, but still easy to read, by sacrificing accuracy and only showing to the nearest 100 steps (0.1k). +Shows a subtle fill colour in the background for progress to the goal. The goal is picked up from the health tracker settings. + + +![](widstep-light.png) +![](widstep-dark.png) diff --git a/apps/widstep/icons8-winter-boots-48.png b/apps/widstep/icons8-winter-boots-48.png new file mode 100644 index 000000000..7dceceef0 Binary files /dev/null and b/apps/widstep/icons8-winter-boots-48.png differ diff --git a/apps/widstep/metadata.json b/apps/widstep/metadata.json new file mode 100644 index 000000000..ea108e0f1 --- /dev/null +++ b/apps/widstep/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "widstep", + "name": "Step counter widget", + "version": "0.01", + "description": "Step counter widget, narrow but clearly readable", + "readme": "README.md", + "icon": "icons8-winter-boots-48.png", + "screenshots": [{"url":"widstep-light.png"},{"url":"widstep-dark.png"}], + "type": "widget", + "tags": "widget,health", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : {"health":"app"}, + "allow_emulator":false, + "storage": [ + {"name":"widstep.wid.js","url":"widstep.wid.js"} + ] + } diff --git a/apps/widstep/widstep-dark.png b/apps/widstep/widstep-dark.png new file mode 100644 index 000000000..c8e1a8065 Binary files /dev/null and b/apps/widstep/widstep-dark.png differ diff --git a/apps/widstep/widstep-light.png b/apps/widstep/widstep-light.png new file mode 100644 index 000000000..9cce1e7c2 Binary files /dev/null and b/apps/widstep/widstep-light.png differ diff --git a/apps/widstep/widstep.wid.js b/apps/widstep/widstep.wid.js new file mode 100644 index 000000000..6ad971af7 --- /dev/null +++ b/apps/widstep/widstep.wid.js @@ -0,0 +1,23 @@ +let wsSettingsGoal = (require('Storage').readJSON("health.json", 1) || {}).stepGoal || 10000; + +Bangle.on('step', function(s) { WIDGETS["widstep"].draw(); }); +Bangle.on('lcdPower', function(on) { + if (on) WIDGETS["widstep"].draw(); +}); +WIDGETS["widstep"]={area:"tl", sortorder:-1, width:28, + draw:function() { + if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off + var steps = Bangle.getHealthStatus("day").steps; + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); + g.setColor(g.theme.dark ? '#00f' : '#0ff'); + var progress = this.width * Math.min(steps/wsSettingsGoal, 1); + g.fillRect(this.x+1, this.y+1, this.x + progress -1, this.y + 23); + g.setColor(g.theme.fg); + g.setFontAlign(0, -1); + var steps_k = (steps/1000).toFixed(1) + 'k'; + g.setFont('6x15').drawString(steps_k, this.x+this.width/2, this.y + 10); + g.setFont('4x6').drawString('steps', this.x+this.width/2, this.y + 2); + } +}; diff --git a/bin/language_scan.js b/bin/language_scan.js index 6385caf49..464d8f998 100755 --- a/bin/language_scan.js +++ b/bin/language_scan.js @@ -1,10 +1,74 @@ -#!/usr/bin/nodejs +#!/usr/bin/env node /* Scans for strings that may be in English in each app, and outputs a list of strings that have been found. See https://github.com/espruino/BangleApps/issues/1311 */ +var childProcess = require('child_process'); + +let refresh = false; + +function handleCliParameters () +{ + let usage = "USAGE: language_scan.js [options]"; + let die = function (message) { + console.log(usage); + console.log(message); + process.exit(3); + }; + let hadTURL = false, + hadDEEPL = false; + for(let i = 2; i < process.argv.length; i++) + { + const param = process.argv[i]; + switch(param) + { + case '-r': + case '--refresh': + refresh = true; + break; + case '--deepl': + i++; + let KEY = process.argv[i]; + if(KEY === '' || KEY === null || KEY === undefined) + { + die('--deepl requires a parameter: the API key to use'); + } + process.env.DEEPL = KEY; + hadDEEPL = true; + break; + case '--turl': + i++; + let URL = process.argv[i]; + if(URL === '' || URL === null || URL === undefined) + { + die('--turl requires a parameter: the URL to use'); + } + process.env.TURL = URL; + hadTURL = true; + break; + case '-h': + case '--help': + console.log(usage+"\n"); + console.log("Parameters:"); + console.log(" -h, --help Output this help text and exit"); + console.log(" -r, --refresh Auto-add new strings into lang/*.json"); + console.log(' --deepl KEY Enable DEEPL as auto-translation engine and'); + console.log(' use KEY as its API key. You also need to provide --turl'); + console.log(' --turl URL In combination with --deepl, use URL as the API base URL'); + process.exit(0); + default: + die("Unknown parameter: "+param); + } + } + if((hadTURL !== false || hadDEEPL !== false) && hadTURL !== hadDEEPL) + { + die("Use of deepl requires both a --deepl API key and --turl URL"); + } +} +handleCliParameters(); + let translate = false; if (process.env.DEEPL) { // Requires translate @@ -64,6 +128,14 @@ try { } catch (e) { ERROR("apps.json not found"); } +if (appsFile.indexOf("---") === 0 && fs.existsSync(BASEDIR+"bin/create_apps_json.sh")) +{ + console.log("apps.json has not been generated, running bin/create_apps_json.sh to build it..."); + childProcess.execFileSync(BASEDIR+'bin/create_apps_json.sh',[],{ + stdio: 'inherit' + }); + appsFile = fs.readFileSync(BASEDIR+"apps.json").toString(); +} try{ apps = JSON.parse(appsFile); } catch (e) { @@ -234,6 +306,11 @@ for (let language of languages) { translations.GLOBAL[translationItem.str] = translation; resolve() })) + } else if(refresh && !translate) { + translationPromises.push(new Promise(async (resolve) => { + translations.GLOBAL[translationItem.str] = translationItem.str; + resolve() + })) } } }); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 363e86922..8fdb5a4d2 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/node /* Checks for any obvious problems in apps.json */ @@ -197,10 +197,11 @@ apps.forEach((app,appIdx) => { // warn if JS icon is the wrong size if (file.name == app.id+".img") { let icon; - let match = fileContents.match(/E\.toArrayBuffer\(atob\(\"([^"]*)\"\)\)/); + let match = fileContents.match(/^\s*E\.toArrayBuffer\(atob\(\"([^"]*)\"\)\)\s*$/); + if (match==null) match = fileContents.match(/^\s*atob\(\"([^"]*)\"\)\s*$/); if (match) icon = Buffer.from(match[1], 'base64'); else { - match = fileContents.match(/require\(\"heatshrink\"\)\.decompress\(\s*atob\(\s*\"([^"]*)\"\s*\)\s*\)/); + match = fileContents.match(/^\s*require\(\"heatshrink\"\)\.decompress\(\s*atob\(\s*\"([^"]*)\"\s*\)\s*\)\s*$/); if (match) icon = heatshrink.decompress(Buffer.from(match[1], 'base64')); else ERROR(`JS icon ${file.name} does not match the pattern 'require("heatshrink").decompress(atob("..."))'`); } diff --git a/core b/core index e9097fa68..89049a5c7 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit e9097fa680182069a5814c3e566a0bcbcb5e72a1 +Subproject commit 89049a5c7c80d2b56dc235d135fc63b80789db96 diff --git a/css/main.css b/css/main.css index a986df22e..f4850babe 100644 --- a/css/main.css +++ b/css/main.css @@ -81,7 +81,7 @@ a.btn.btn-link.dropdown-toggle { min-height: 8em; } -.tile-content { position: relative; word-break: break-all; } +.tile-content { position: relative; } .link-github { position:absolute; top: 36px; diff --git a/lang/hr_HR.json b/lang/hr_HR.json new file mode 100644 index 000000000..1103a09a3 --- /dev/null +++ b/lang/hr_HR.json @@ -0,0 +1,162 @@ +{ + "//": "Croatian language translations", + "GLOBAL": { + "//": "Translations that apply for all apps", + "Alarm": "Alarm", + "(repeat)": "(ponovi)", + "New Timer": "Novi mjerač vremena", + "New Alarm": "Novi alarm", + "Timer": "Sat", + "Save": "Spremi", + "Connected": "Povezano", + "Keep Msgs": "Zadrži poruke", + "circle 4": "krug 4", + "circle 3": "krug 3", + "circle 1": "krug 1", + "week": "tjedan", + "circle 2": "krug 2", + "Sleep": "Spavanje", + "circle count": "broj krugova", + "steps": "koraci", + "show widgets": "prikaži widgete", + "heartrate": "brzina otkucaja srca", + "valid period": "valjano razdoblje", + "maximum": "maksimalan", + "battery warn": "upozorenje o bateriji", + "weather circle": "vremenski krug", + "data": "podaci", + "goal": "cilj", + "step length": "dužina koraka", + "minimum": "minimum", + "min. confidence": "min. samouvjerenost", + "Heartrate": "Brzina otkucaja srca", + "distance goal": "ciljna udaljenost", + "Steps": "Koraci", + "color": "boja", + "colorize icon": "bojanje ikone", + "App Source\nNot found": "Kod aplikacije\nNije pronađen", + "Show clocks": "Prikaži satove", + "TAP right top/bottom": "TAP desno gore/dolje", + "View Message": "Pogledaj poruku", + "Circle": "Krug", + "Launcher Settings": "Postavke pokretača", + "Vector font size": "Veličina vektorskog fonta", + "Font": "Font", + "Loading": "Učitavanje", + "Delete all messages": "Obriši sve poruke", + "No Messages": "Nema poruka", + "BTNs 1:startlap 2:exit 3:reset": "BTNs 1:startlap 2:exit 3:reset", + "Are you sure": "Jeste li sigurni", + "start&lap/reset, BTN1: EXIT": "start&lap/reset, BTN1: EXIT", + "Mark Unread": "Označi nepročitano", + "Utils": "Utils", + "Record Run": "Snimi trčanje", + "Delete All Messages": "Brisanje svih poruka", + "Unread timer": "Nepročitani timer", + "Music": "Glazba", + "LCD": "LCD", + "Apps": "Aplikacije", + "Bluetooth": "Bluetooth", + "Dark BW": "Tamna BW", + "Vibration": "Vibracija", + "Quiet Mode": "Tihi način", + "Beep": "Beep", + "Passkey BETA": "Passkey BETA", + "Piezo": "Piezo", + "HID": "HID", + "Make Connectable": "Povežite se", + "BLE": "BLE", + "Programmable": "Programabilno", + "Light BW": "Svjetla CB", + "Background": "Pozadina", + "Customize": "Prilagodite", + "Background 2": "Pozadina 2", + "LCD Brightness": "Svjetlost LCD-a", + "Wake on BTN1": "Probudi se na BTN1", + "Wake on BTN2": "Probudi se na BTN2", + "Wake on BTN3": "Probudi se na BTN3", + "LCD Timeout": "LCD timeout", + "Twist Max Y": "Twist Max Y", + "Wake on Twist": "Probudi se na Twist", + "Twist Threshold": "Prag Twista", + "Foreground 2": "Prvi plan 2", + "Foreground": "Prvi plan", + "Remove": "Odstranite", + "Time Zone": "Vremenska zona", + "Highlight FG": "Naznači FG", + "Wake on Touch": "Probudi se na dodir", + "Clock Style": "Stil sata", + "Highlight BG": "Naznači BG", + "Add Device": "Dodaj uređaj", + "Connect device\nto add to\nwhitelist": "Spojite uređaj\nza dodavanje\nna sigurnu listu", + "Utilities": "Usluge", + "Wake on FaceUp": "Prebudi se na FaceUp", + "Compact Storage": "Kompaktna pohrana", + "Log": "Zapis", + "Debug Info": "Debug informacije", + "Turn Off": "Ugasite", + "Flatten Battery": "Prazna baterija", + "Reset to Defaults": "Postavite na početne postavke", + "Reset Settings": "Poništite postavke", + "Twist Timeout": "Twist Timeout", + "This will remove everything": "Ovo će obrisati sve", + "Rewrite Settings": "Ponovno napiši postavke", + "Storage": "Pohrana", + "Compacting...\nTakes approx\n1 minute": "Komprimiranje...\nPribližno\n1 minuta", + "No Clocks Found": "Satovi nisu pronađeni", + "Stay Connectable": "Ostanite povezani", + "Minute": "Minuta", + "Connectable": "Povezivost", + "Hour": "sat", + "Flattening battery - this can take hours.\nLong-press button to cancel": "Pražnjenje baterije - može potrajati satima.\nDugi pritisak da odustanete", + "Second": "Drugi", + "Month": "Mjesec", + "Date": "Datum", + "ALARM": "ALARM", + "Reset all widgets": "Poništite sve widgete", + "Reset All": "Poništite sve widgete", + "TIMER": "TIMER", + "Widgets": "widgeti", + "Hours": "Sati", + "Minutes": "Minute", + "Year": "Godina", + "No app has settings": "Nema aplikacije s postavkama", + "App Settings": "Postavke aplikacije", + "Repeat": "Ponovite", + "Invalid settings": "Neispravna postavka", + "Enabled": "Omogućeno", + "Reset": "Resetiraj", + "off": "Isključeno", + "Side": "Strana", + "Sort Order": "Poredak", + "Left": "Lijevo", + "Right": "Desno", + "on": "uključeno", + "Theme": "Tema", + "Locale": "Lokacija", + "Alerts": "Upozorenja", + "Select Clock": "Odaberite sat", + "System": "Sustav", + "Settings": "Postavke", + "Set Time": "Postavi vrijeme", + "Whitelist": "Sigurna lista", + "Message": "Poruka", + "Vibrate": "Vibriranje", + "Delete": "Izbriši", + "Error in settings": "Pogreška u postavkama", + "Messages": "Poruke", + "Disable": "Onemogućite", + "Show": "Prikaži", + "Ok": "Ok", + "On": "Uključeno", + "Hide": "Sakrij", + "Factory Reset": "Postavljanje na tvorničke postavke", + "Yes": "Da", + "No": "Ne", + "Off": "Isključeno", + "Back": "Natrag" + }, + "alarm": { + "//": "App-specific overrides" + } +} \ No newline at end of file diff --git a/lang/index.json b/lang/index.json index 2a9ecfd42..20ceaab92 100644 --- a/lang/index.json +++ b/lang/index.json @@ -21,5 +21,7 @@ {"code":"pl_PL","name":"Polish","url":"pl_PL.json"}, {"code":"ro_RO","name":"Romanian","url":"ro_RO.json"}, {"code":"sk_SK","name":"Slovak","url":"sk_SK.json"}, - {"code":"sl_SL","name":"Slovenian","url":"sl_SL.json"} + {"code":"sl_SL","name":"Slovenian","url":"sl_SL.json"}, + {"code":"nn_NO","name":"Norwegian (Nynorsk)","url":"nn_NO.json"}, + {"code":"hr_HR","name":"Croatian","url":"hr_HR.json"} ] diff --git a/lang/nn_NO.json b/lang/nn_NO.json new file mode 100644 index 000000000..176d82b48 --- /dev/null +++ b/lang/nn_NO.json @@ -0,0 +1,244 @@ +{ + "//": "Norwegian nynorsk language translations", + "GLOBAL": { + "//": "Translations that apply for all apps", + "Alarms": "Alarmar", + "Hours": "Timar", + "Minutes": "Minutt", + "Enabled": "Slått på", + "New Alarm": "Ny alarm", + "Save": "Lagre", + "Back": "Tilbake", + "Repeat": "Gjentaking", + "Delete": "Slett", + "ALARM!": "ALARM!", + "Sleep": "Søvn", + "circle 3": "sirkel 3", + "circle 1": "sirkel 1", + "music": "musikk", + "week": "veke", + "Keep Msgs": "Behald meldingar", + "Auto snooze": "Automatisk slumring", + "step length": "steglengde", + "Circle": "Sirkel", + "data": "data", + "colorize icon": "fargelegg ikon", + "min. confidence": "min. tillit", + "show widgets": "vis widget", + "valid period": "gyldi periode", + "Heartrate": "Puls", + "distance goal": "mål for distanse", + "circle 4": "sirkel 4", + "circle count": "antall sirklar", + "minimum": "minimum", + "maximum": "maksimum", + "New Timer": "Ny nedteljing", + "battery warn": "batteriåtvaring", + "heartrate": "puls", + "circle 2": "sirkel 2", + "(repeat)": "(gjenta)", + "weather circle": "værsirkel", + "Delete All Messages": "Slett alle meldingar", + "No Messages": "Ingen meldingar", + "Show clocks": "Visa klokker", + "STEPS": "STEG", + "TAP right top/bottom": "TRYKK oppe/nede til høgre", + "View Message": "Vis melding", + "Mark Unread": "Marker ulest", + "Are you sure": "Er du sikker", + "Delete all messages": "Slett alle meldingar", + "Record Run": "Rekordlaup", + "Unread timer": "Ulest nedteljing", + "Vibration": "Vibrering", + "Utils": "Verkty", + "Quiet Mode": "Stille modus", + "Passkey BETA": "Passord BETA", + "Dark BW": "Mørk BW", + "BTNs 1:startlap 2:exit 3:reset": "BTN 1:start 2:avslutt 3:nullstill", + "start&lap/reset, BTN1: EXIT": "start&runde/nullstill, BTN1: AVSLUTT", + "BLE": "BLE", + "Programmable": "Programmerbar", + "Launcher Settings": "Innstillingar for oppstartsprogram", + "Vector font size": "Storleik for vektorskrifttype", + "Font": "Skrifttype", + "Yes\ndefinitely": "Ja\ndefinitivt", + "App Source\nNot found": "App-kjelde\nikkje funnet", + "Make Connectable": "Gjer mogleg å kople til", + "HID": "HID", + "Bluetooth": "Bluetooth", + "Apps": "Appar", + "Piezo": "Piezo", + "LCD": "LCD", + "Foreground 2": "Forgrunn 2", + "Light BW": "Lys BW", + "Background": "Bakgrunn", + "Remove": "Fjern", + "Highlight BG": "Marker BG", + "Customize": "Tilpass", + "Highlight FG": "Marker FG", + "Background 2": "Bakgrunn 2", + "LCD Brightness": "Lyusstyrke på LCD-skjermen", + "Add Device": "Legg til eining", + "Wake on BTN1": "Vakne ved KNAPP1", + "Wake on BTN2": "Vakne ved KNAPP2", + "Twist Timeout": "Tidsavbrot for vridning", + "Wake on Touch": "Vakne ved berøring", + "LCD Timeout": "LCD tidsavbrot", + "Foreground": "Forgrunn", + "Connect device\nto add to\nwhitelist": "Kople til eining\nfor å leggje til\ni lista", + "Wake on FaceUp": "Vakne på FaceUp", + "Twist Threshold": "Terskel for vridning", + "Wake on BTN3": "Vakne på BTN3", + "Clock Style": "Klokkestil", + "Time Zone": "Tidssone", + "Twist Max Y": "Vridning Max Y", + "Stay Connectable": "Opne for tilkopling", + "This will remove everything": "Dette vil fjerne alt", + "Turn Off": "Slå av", + "Connectable": "Kan koplast til", + "Flattening battery - this can take hours.\nLong-press button to cancel": "Flatar ut batteriet, dette kan ta fleire timar.\nHald inne knappen for å avbryte", + "Reset to Defaults": "Nullstill", + "Utilities": "Verkty", + "Flatten Battery": "Flat ut batteriet", + "Debug Info": "Feilsøkjingsinfo.", + "Reset Settings": "Nullstill innstillingar", + "Wake on Twist": "Vakne ved vridning", + "Compact Storage": "Trykk saman lagring", + "Log": "Logg", + "Rewrite Settings": "Omskriving av innstillingar", + "Compacting...\nTakes approx\n1 minute": "Trykkar saman lagring...\nTek ca.\n1 minutt", + "Storage": "Lagring", + "Second": "Sekund", + "App Settings": "App-innstillingar", + "Invalid settings": "Ugyldige innstillingar", + "Minute": "Minutt", + "Sleep Phase Alarm": "Søvnfase-alarm", + "No app has settings": "Ingen appar har innstillingar", + "Hour": "Time", + "No Clocks Found": "Fant inga klokke", + "Date": "Dato", + "Month": "Månad", + "Alarm": "Alarm", + "Reset": "Nullstill", + "Reset all widgets": "Nullstill alle widget", + "TIMER": "TIMAR", + "on": "på", + "OFF": "AV", + "Side": "Side", + "Sort Order": "Sortering", + "Left": "Venstre", + "Right": "Høgre", + "Reset All": "Nullstill alle", + "Widgets": "Widget", + "goal": "mål", + "Vibrate": "Vibrer", + "Message": "Melding", + "Beep": "Lag lyd", + "Disable": "Slå av", + "Select Clock": "Vel klokke", + "Locale": "Språk", + "Alerts": "Varslingar", + "System": "System", + "Set Time": "Still tid", + "Factory Reset": "Nullstill til fabrikkinnstillingar", + "Messages": "Meldingar", + "Timer": "Nedteljing", + "BACK": "TILLBAKE", + "Error in settings": "Feil i innstillingar", + "Whitelist": "Tillatelsesliste", + "ALARM": "ALARM", + "Hide": "Skjul", + "Connected": "Kopla til", + "Show": "Vis", + "On": "På", + "Ok": "Ok", + "No": "Nei", + "Settings": "Innstillingar", + "steps": "steg", + "back": "tilbake", + "Steps": "Steg", + "Year": "År", + "Yes": "Ja", + "Loading": "Lastar", + "Music": "Musikk", + "color": "farge", + "off": "av", + "Off": "Av", + "Theme": "Drakt", + "Select App": "Vel App", + "No Apps Found": "Fant inga appar", + "Days of Week": "Vekedagar", + "Days": "Dagar", + "ALTITUDE (m)": "HØGDE (m)", + "ZERO": "NULL", + "No tokens": "Ingea token", + "Not supported": "Ikkje støtta", + "weather data": "vêrdata", + "Uncalibrated\nturn 360° around": "Ikkje kalibrert\nsnu 360° grader", + "RESET": "NULLSTILL", + "Mark all read": "Marker alle som lest", + "Min Font": "Minste skriftstorleik", + "Small": "Liten", + "Medium": "Medium", + "Auto-Open Music": "Opne musikk automatisk", + "Unlock Watch": "Lås opp klokke", + "Flash Icon": "Blink Ikon", + "Silent": "Stille", + "Exit": "Avslutt", + "Current Mode": "Gjeldande modus", + "Switch Theme": "Byt drakt", + "Edit Schedule": "Rediger tidsplan", + "Switch to": "Byt til", + "No apps": "Ingen appar", + "Recorder": "Ta opp", + "RECORD": "TA OPP", + "File #": "Fil #", + "View Tracks": "Sjå spor", + "Time Period": "Tidsperiode", + "Tracks": "Spor", + "No Tracks found": "Fant inga spor", + "Erase": "Slett", + "Delete Track": "Slett spor", + "Drawing": "Teikning", + "Altitude (m)": "Høgd (m)", + "Speed (m/s)": "Hastigheit (m/s)", + "Notifications": "Varsel", + "Snooze": "Slumre", + "settings": "innstillingar", + "Show date": "vis dato", + "locale": "språk", + "M": "M", + "m.Y #W": "m.Y #W", + "today": "i dag", + "Border": "Omriss", + "show": "vis", + "Color": "Farge", + "Marker": "Marker", + "circle": "sirkel", + "rectangle": "rektangel", + "Connection\nlost": "Mista\ntilkopling", + "Calculating": "Reknar ut", + "Add Schedule": "Legg til tidsskjema", + "LCD Settings": "LCD-innstillingar", + "Today settings": "Innstillingar for i dag", + "Cancel": "Avbryt", + "red": "raud", + "green": "grøn", + "blue": "blå", + "Track": "Spor", + "none": "inga", + "Plot Map": "Plott Kart", + "Plot OpenStMap": "Plott OpenStMap", + "Plot Alt": "Plott Høgde", + "Plot Speed": "Plott Hastigheit", + "Dist Pattern": "Avstandsmønster", + "Step Pattern": "Stegmønster", + "Time Pattern": "Tidsmønster", + "Boxes": "Bokstar", + "Start wday": "Start vdag", + "Su color": "Su farge", + "filled": "fylt", + "Mrk.Color": "Mrk.Farge", + "Mrk.Size": "Mrk.Storleik" + } +} diff --git a/loader.js b/loader.js index c4553940b..ee7b584a2 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v12"; +var RECOMMENDED_VERSION = "2v13"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles diff --git a/modules/buzz_menu.js b/modules/buzz_menu.js index 64b225343..c5b41a997 100644 --- a/modules/buzz_menu.js +++ b/modules/buzz_menu.js @@ -4,9 +4,10 @@ exports.pattern = function(value, callback) { var vibPatterns = ["", ".", "..", "-", "--", "-.-", "---"]; return { value: Math.max(0,vibPatterns.indexOf(value)), - min: 0, max: vibPatterns.length, + min: 0, max: vibPatterns.length-1, format: v => vibPatterns[v]||/*LANG*/"Off", onchange: v => { + require("buzz").pattern(vibPatterns[v]); callback(vibPatterns[v]); } };