diff --git a/apps.json b/apps.json index abd65cbc5..d554b0354 100644 --- a/apps.json +++ b/apps.json @@ -42,7 +42,7 @@ "name": "Launcher (Default)", "shortName":"Launcher", "icon": "app.png", - "version":"0.06", + "version":"0.07", "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "tags": "tool,system,launcher,b2", "type":"launch", @@ -107,7 +107,7 @@ "name": "Fullscreen Notifications", "shortName":"Notifications", "icon": "notify.png", - "version":"0.11", + "version":"0.12", "description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.", "tags": "widget,b2", "type": "notify", @@ -199,11 +199,11 @@ "sortorder" : -2 }, { "id": "alarm", - "name": "Default Alarm", + "name": "Default Alarm & Timer", "shortName":"Alarms", "icon": "app.png", - "version":"0.12", - "description": "Set and respond to alarms", + "version":"0.13", + "description": "Set and respond to alarms and timers", "tags": "tool,alarm,widget,b2", "storage": [ {"name":"alarm.app.js","url":"app.js"}, @@ -585,7 +585,7 @@ { "id": "weather", "name": "Weather", "icon": "icon.png", - "version":"0.08", + "version":"0.10", "description": "Show Gadgetbridge weather report", "readme": "readme.md", "tags": "widget,outdoors", @@ -1187,7 +1187,7 @@ { "id": "widclk", "name": "Digital clock widget", "icon": "widget.png", - "version":"0.05", + "version":"0.06", "description": "A simple digital clock widget", "tags": "widget,clock", "type":"widget", @@ -1236,9 +1236,9 @@ { "id": "demoapp", "name": "Demo Loop", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", - "tags": "", + "tags": "bno2", "type":"app", "allow_emulator":true, "storage": [ @@ -1328,14 +1328,13 @@ "id": "grocery", "name": "Grocery", "icon": "grocery.png", - "version":"0.01", + "version":"0.02", "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.", "tags": "tool,outdoors,shopping,list", "type": "app", "custom":"grocery.html", "storage": [ - {"name":"grocery"}, - {"name":"grocery.app.js"}, + {"name":"grocery.app.js", "url":"app.js"}, {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} ] }, @@ -1381,7 +1380,7 @@ { "id": "barclock", "name": "Bar Clock", "icon": "clock-bar.png", - "version":"0.07", + "version":"0.08", "description": "A simple digital clock showing seconds as a bar", "tags": "clock", "type":"clock", @@ -2058,7 +2057,7 @@ "name": "Find Phone", "shortName":"Find Phone", "icon": "app.png", - "version":"0.02", + "version":"0.03", "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.", "tags": "tool,android", "readme": "README.md", @@ -3520,10 +3519,11 @@ { "id": "antonclk", "name": "Anton Clock", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "A simple clock using the bold Anton font.", "tags":"clock,b2", "type":"clock", + "allow_emulator":true, "storage": [ {"name":"antonclk.app.js","url":"app.js"}, {"name":"antonclk.img","url":"app-icon.js","evaluate":true} @@ -3532,10 +3532,11 @@ { "id": "waveclk", "name": "Wave Clock", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**", "tags":"clock,b2", "type":"clock", + "allow_emulator":true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, {"name":"waveclk.img","url":"app-icon.js","evaluate":true} diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 4084a7d3f..a151fd07e 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -10,3 +10,5 @@ 0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506) 0.11: Respect Quiet Mode 0.12: Fix widget for bangle 2, now uses theme + Widgets now shown on Alarm screen + Alarm widget state now updates when setting/resetting an alarm diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js index 26345e887..bb5722106 100644 --- a/apps/alarm/alarm.js +++ b/apps/alarm/alarm.js @@ -18,8 +18,10 @@ function showAlarm(alarm) { var buzzCount = 10; if (alarm.msg) msg += "\n"+alarm.msg; + Bangle.loadWidgets(); + Bangle.drawWidgets(); E.showPrompt(msg,{ - title:"ALARM!", + title:alarm.timer ? "TIMER!" : "ALARM!", buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins }).then(function(sleep) { buzzCount = 0; diff --git a/apps/alarm/app.js b/apps/alarm/app.js index b6019ca08..fdb7784f7 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[]; last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! rp : true, // repeat as : false, // auto snooze + timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes } ];*/ @@ -18,6 +19,12 @@ function formatTime(t) { return hrs+":"+("0"+mins).substr(-2); } +function formatMins(t) { + mins = (0|t)%60; + hrs = 0|(t/60); + return hrs+":"+("0"+mins).substr(-2); +} + function getCurrentHr() { var time = new Date(); return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); @@ -25,17 +32,24 @@ function getCurrentHr() { function showMainMenu() { const menu = { - '': { 'title': 'Alarms' }, - 'New Alarm': ()=>editAlarm(-1) + '': { 'title': 'Alarm/Timer' }, + 'New Alarm': ()=>editAlarm(-1), + 'New Timer': ()=>editTimer(-1) }; alarms.forEach((alarm,idx)=>{ - txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr); - if (alarm.rp) txt += " (repeat)"; + if (alarm.timer) { + txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer); + } else { + txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr); + if (alarm.rp) txt += " (repeat)"; + } menu[txt] = function() { - editAlarm(idx); + if (alarm.timer) editTimer(idx); + else editAlarm(idx); }; }); menu['< Back'] = ()=>{load();}; + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); return E.showMenu(menu); } @@ -55,7 +69,7 @@ function editAlarm(alarmIndex) { as = a.as; } const menu = { - '': { 'title': 'Alarms' }, + '': { 'title': 'Alarm' }, 'Hours': { value: hrs, onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' @@ -109,4 +123,59 @@ function editAlarm(alarmIndex) { return E.showMenu(menu); } +function editTimer(alarmIndex) { + var newAlarm = alarmIndex<0; + var hrs = 0; + var mins = 5; + var en = true; + if (!newAlarm) { + var a = alarms[alarmIndex]; + mins = (0|a.timer)%60; + hrs = 0|(a.timer/60); + en = a.on; + } + const menu = { + '': { 'title': 'Timer' }, + 'Hours': { + value: hrs, + onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Minutes': { + value: mins, + onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Enabled': { + value: en, + format: v=>v?"On":"Off", + onchange: v=>en=v + } + }; + function getTimer() { + var d = new Date(Date.now() + ((hrs*60)+mins)*60000); + var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600); + // Save alarm + return { + on : en, + timer : (hrs*60)+mins, + hr : hr, + rp : false, as: false + }; + } + menu["> Save"] = function() { + if (newAlarm) alarms.push(getTimer()); + else alarms[alarmIndex] = getTimer(); + require("Storage").write("alarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function() { + alarms.splice(alarmIndex,1); + require("Storage").write("alarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + } + menu['< Back'] = showMainMenu; + return E.showMenu(menu); +} + showMainMenu(); diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js index 7b9a88bb9..e8bb79fc7 100644 --- a/apps/alarm/widget.js +++ b/apps/alarm/widget.js @@ -1,11 +1,7 @@ -(() => { - var alarms = require('Storage').readJSON('alarm.json',1)||[]; - alarms = alarms.filter(alarm=>alarm.on); - if (!alarms.length) return; // no alarms, no widget! - delete alarms; - // add the widget - WIDGETS["alarm"]={area:"tl",width:24,draw:function() { - g.setColor(g.theme.fg); - g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); - }}; -})() +WIDGETS["alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + } +}; +WIDGETS["alarm"].reload(); diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 5560f00bc..8c2a33143 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index 1b92d4a8b..f6fcf1708 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -19,7 +19,7 @@ function queueDraw() { function draw() { var x = g.getWidth()/2; var y = g.getHeight()/2; - g.reset(); + g.reset(); var date = new Date(); var timeStr = require("locale").time(date,1); var dateStr = require("locale").date(date).toUpperCase(); @@ -33,7 +33,7 @@ function draw() { g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background g.drawString(dateStr,x,y); // queue draw in one minute - queueDraw(); + queueDraw(); } // Clear the screen once, at startup @@ -49,9 +49,8 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); - diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index 9589a1902..c56967d3d 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -5,3 +5,4 @@ 0.05: Clock does not start if app Languages is not installed 0.06: Improve accuracy 0.07: Update to use Bangle.setUI instead of setWatch +0.08: Use theme colors, Layout library \ No newline at end of file diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index 5069faa39..c2b4bde12 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -3,167 +3,102 @@ * A simple digital clock showing seconds as a bar **/ // Check settings for what type our clock should be -const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour'] -let locale = require('locale') +const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; +let locale = require("locale"); { // add some more info to locale - let date = new Date() - date.setFullYear(1111) - date.setMonth(1, 3) // februari: months are zero-indexed - const localized = locale.date(date, true) - locale.dayFirst = /3.*2/.test(localized) + let date = new Date(); + date.setFullYear(1111); + date.setMonth(1, 3); // februari: months are zero-indexed + const localized = locale.date(date, true); + locale.dayFirst = /3.*2/.test(localized); - locale.hasMeridian = false - if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed - locale.hasMeridian = (locale.meridian(date) !== '') + locale.hasMeridian = false; + if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed + locale.hasMeridian = (locale.meridian(date)!==""); } - } -const screen = { - width: g.getWidth(), - height: g.getWidth(), - middle: g.getWidth() / 2, - center: g.getHeight() / 2, +Bangle.loadWidgets(); +function renderBar(l) { + if (!this.fraction) { + // zero-size fillRect stills draws one line of pixels, we don't want that + return; + } + const width = this.fraction*l.w; + g.fillRect(l.x, l.y, width-1, l.y+l.height-1); } -// hardcoded "settings" -const settings = { - time: { - color: -1, - font: '6x8', - size: (is12Hour && locale.hasMeridian) ? 6 : 8, - middle: screen.middle, - center: screen.center, - ampm: { - color: -1, - font: '6x8', - size: 2, +const Layout = require("Layout"); +const layout = new Layout({ + type: "v", c: [ + { + type: "h", c: [ + {id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below + {id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg}, + ], }, - }, - date: { - color: -1, - font: 'Vector', - size: 20, - middle: screen.height - 20, // at bottom of screen - center: screen.center, - }, - bar: { - color: -1, - top: 155, // just below time - thickness: 6, // matches 24h time "pixel" size - }, + {id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, + {height: 40}, + {id: "date", type: "txt", font: "10%", valign: 1}, + ], +}, false, {lazy: true}); +// adjustments based on screen size and whether we display am/pm +let thickness; // bar thickness, same as time font "pixel block" size +if (is12Hour) { + // Maximum font size = ( - ) / (5chars * 6px) + thickness = Math.floor((g.getWidth()-24)/(5*6)); +} else { + layout.ampm.label = ""; + thickness = Math.floor(g.getWidth()/(5*6)); } +layout.bar.height = thickness+1; +layout.time.font = "6x8:"+thickness; +layout.update(); -const SECONDS_PER_MINUTE = 60 - -const timeText = function (date) { +function timeText(date) { if (!is12Hour) { - return locale.time(date, true) + return locale.time(date, true); } - const date12 = new Date(date.getTime()) - const hours = date12.getHours() - if (hours === 0) { - date12.setHours(12) - } else if (hours > 12) { - date12.setHours(hours - 12) + const date12 = new Date(date.getTime()); + const hours = date12.getHours(); + if (hours===0) { + date12.setHours(12); + } else if (hours>12) { + date12.setHours(hours-12); } - return locale.time(date12, true) + return locale.time(date12, true); } -const ampmText = function (date) { - return is12Hour ? locale.meridian(date) : '' +function ampmText(date) { + return (is12Hour && locale.hasMeridian)? locale.meridian(date) : ""; } - -const dateText = function (date) { +function dateText(date) { const dayName = locale.dow(date, true), month = locale.month(date, true), - day = date.getDate() - const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}` - return `${dayName} ${dayMonth}` + day = date.getDate(); + const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`; + return `${dayName} ${dayMonth}`; } -const drawDateTime = function (date) { - const t = settings.time - g.setColor(t.color) - g.setFont(t.font, t.size) - g.setFontAlign(0, 0) // centered - g.drawString(timeText(date), t.center, t.middle, true) - if (is12Hour && locale.hasMeridian) { - const a = settings.time.ampm - g.setColor(a.color) - g.setFont(a.font, a.size) - g.setFontAlign(1, -1) // right top - // at right edge of screen, aligned with time bottom - const left = screen.width - a.size * 2, - top = t.middle + t.size - a.size - g.drawString(ampmText(date), left, top, true) - } +draw = function draw() { + if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled + const date = new Date(); + layout.time.label = timeText(date); + layout.ampm.label = ampmText(date); + layout.date.label = dateText(date); + const SECONDS_PER_MINUTE = 60; + layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE; + layout.render(); + // schedule update at start of next second + const millis = date.getMilliseconds(); + setTimeout(draw, 1000-millis); +}; - const d = settings.date - g.setColor(d.color) - g.setFont(d.font, d.size) - g.setFontAlign(0, 0) // centered - g.drawString(dateText(date), d.center, d.middle, true) -} - -const drawBar = function (date) { - const b = settings.bar - const seconds = date.getSeconds() - if (seconds === 0) { - // zero-size rect stills draws one line of pixels, we don't want that - return - } - const fraction = seconds / SECONDS_PER_MINUTE, - width = fraction * screen.width - g.setColor(b.color) - g.fillRect(0, b.top, width, b.top + b.thickness) -} - -const clearScreen = function () { - g.setColor(0) - const timeTop = settings.time.middle - (settings.time.size * 4) - g.fillRect(0, timeTop, screen.width, screen.height) -} - -let lastSeconds, tTick -const tick = function () { - g.reset() - const date = new Date() - const seconds = date.getSeconds() - if (lastSeconds > seconds) { - // new minute - clearScreen() - drawDateTime(date) - } - // the bar only gets larger, so drawing on top of the previous one is fine - drawBar(date) - lastSeconds = seconds - // schedule next update - const millis = date.getMilliseconds() - tTick = setTimeout(tick, 1000-millis) -} - -const start = function () { - lastSeconds = 99 // force redraw - tick() -} -const stop = function () { - if (tTick) { - clearTimeout(tTick) - tTick = undefined - } -} - -// clean app screen -g.clear() -Bangle.loadWidgets() -Bangle.drawWidgets() // Show launcher when button pressed Bangle.setUI("clock"); - -Bangle.on('lcdPower', function (on) { +Bangle.on("lcdPower", function(on) { if (on) { - start() - } else { - stop() + draw(); } -}) -start() +}); +g.reset().clear(); +Bangle.drawWidgets(); +draw(); diff --git a/apps/barclock/screenshot.png b/apps/barclock/screenshot.png index d37ee9cae..9c2b7a50f 100644 Binary files a/apps/barclock/screenshot.png and b/apps/barclock/screenshot.png differ diff --git a/apps/barclock/screenshot_pm.png b/apps/barclock/screenshot_pm.png index a2a3f63fb..983f17aaa 100644 Binary files a/apps/barclock/screenshot_pm.png and b/apps/barclock/screenshot_pm.png differ diff --git a/apps/demoapp/ChangeLog b/apps/demoapp/ChangeLog index 5560f00bc..53e9cf268 100644 --- a/apps/demoapp/ChangeLog +++ b/apps/demoapp/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Minor adjustment to fix out of memory errors diff --git a/apps/demoapp/app.js b/apps/demoapp/app.js index 13c043587..f1cf5af07 100644 --- a/apps/demoapp/app.js +++ b/apps/demoapp/app.js @@ -60,8 +60,8 @@ var scenes = [ }; }, function() { + Bangle.setLCDMode("120x120"); var img = require("heatshrink").decompress(atob("oNBxH+5wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHGpAAoQKv4ADCBQAeqsrAAejBw9/B4oABqt/IGepHw5CEQspALH5hBC5pAvv4/MAALFkIBWpPI6IHqpAu0Z3GfYOpRYdPQEhALYIp2FBYNVI4JAvvL4LH0yBYAFJAQQQ5Ay1JAFftBAQBYxCDv+qIGiCHIQiGnIBfOv5BJIQRAyIJkrvKEkIBrFBB4qEGIGRCNYsZAQIQV/IZDEiICRCDQVJAUIQVPC4lVIF6yJQYpAZ5t/FYvNIBepqtVIJGjIDoqBDY2pdYo3DfAhBIQLmpvIcDvIrC5oJEIAhTCGQmj5qgEC4t5e7YrBqt5BI6UFBg15v4XHbQwAQb4oAKv7NKABdVRoYATUAwnICqjZFIMdVE4+jXI4XGYCxBFFZN/M5OpCxUrvJ/ZFYmjvNVAAY+KCwpDBC6YAV5vNC9oA/AH4A/AHYA==")); - g.clear(); y = 0; var step = 4; @@ -70,8 +70,7 @@ var scenes = [ g.clear(); g.drawImage(img,60,60,{rotate:Math.sin(y*0.03)*0.5}); g.flip(); - }, 20); - Bangle.setLCDMode("120x120"); + }, 20); return function() { if (i) clearInterval(i); }; diff --git a/apps/findphone/ChangeLog b/apps/findphone/ChangeLog index 86558abf5..29100f3c1 100644 --- a/apps/findphone/ChangeLog +++ b/apps/findphone/ChangeLog @@ -1,2 +1,3 @@ 0.01: First Version 0.02: Remove HID requirement, update screen +0.03: Fix for Bangle 2, toggle find with top half of screen, exit touch bottom half of screen diff --git a/apps/findphone/README.md b/apps/findphone/README.md index c655457a2..64e719d6a 100644 --- a/apps/findphone/README.md +++ b/apps/findphone/README.md @@ -6,3 +6,8 @@ Ring your phone via GadgetBridge if you lost it somewhere. 2. Lose phone 3. Open app 4. Click any button or screen + +## On a Bangle 2 + +- You can touch the top half of the screen to toggle Find / Stop +- You can touch the bottom half of the screen to exit the app. diff --git a/apps/findphone/app.js b/apps/findphone/app.js index 34f729bc7..e5e32739a 100644 --- a/apps/findphone/app.js +++ b/apps/findphone/app.js @@ -1,13 +1,15 @@ //notify your phone +const fontSize = g.getWidth() / 8; var finding = false; function draw() { // show message - g.clear(1); - require("Font8x12").add(Graphics); - g.setFont("8x12",3); + g.clear(g.theme.bg); + g.setColor(g.theme.fg); + g.setFont("Vector", fontSize); g.setFontAlign(0,0); + if (finding) { g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20); g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20); @@ -17,17 +19,37 @@ function draw() { g.flip(); } +function findPhone(v) { + Bluetooth.println(JSON.stringify({t:"findPhone", n:v})); +} + function find(){ finding = !finding; draw(); - Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding})); + findPhone(finding); } draw(); //register all buttons and screen to find phone setWatch(find, BTN1, {repeat:true}); -setWatch(find, BTN2, {repeat:true}); -setWatch(find, BTN3, {repeat:true}); -setWatch(find, BTN4, {repeat:true}); -setWatch(find, BTN5, {repeat:true}); + +if (process.env.HWVERSION == 1) { + setWatch(find, BTN2, {repeat:true}); + setWatch(find, BTN3, {repeat:true}); + setWatch(find, BTN4, {repeat:true}); + setWatch(find, BTN5, {repeat:true}); +} + +if (process.env.HWVERSION == 2) { + Bangle.on('touch', function(button, xy) { + + // click top part of the screen to stop start + if (xy.y < g.getHeight() / 2) { + find(); + } else { + findPhone(false); + setTimeout(load, 100); // exit in 100ms + } + }); +} diff --git a/apps/gbridge/README.md b/apps/gbridge/README.md index 867b736ab..03bf883d7 100644 --- a/apps/gbridge/README.md +++ b/apps/gbridge/README.md @@ -52,3 +52,62 @@ Activity reporting You'll need a Gadgetbridge release *after* version 0.50.0 for Actvity Reporting to be enabled. By default heart rate isn't reported, but it can be enabled from `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Record HRM` + + +## Troubleshooting + +1. Switch to using one of the stock watch faces like s7clk or wave on a Bangle 2. + +2. Check that the battery charge level is being seen by the Gadgetbridge App on the phone. This proves that data is getting to your phone. + +### You can test the notifications on the Bangle + +First disconnect from Gadgetbridge on your phone. Then connect +through the IDE and enter the following code. You should get a pop +up screen on your Bangle. This proves that the watch is correctly +setup for notifications. + + + GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) + + +NOTE: On a Bangle 2, this will fail if you have not installed 'Notifications Fullscreen'. + +### Check that notifications are getting through to your Bangle + +* Disconnect your Bangle from Gadgetbridge on your phone. +* Connect through the IDE +* Run the following bit of code + + var log = []; + function GB(d) { + log.push(JSON.stringify(d)); + } + +* Disconnect from the IDE +* Connect your Bangle to Gadgetbridge +* Call your phone to get a missed call +* Disonnect your Bangle to Gadgetbridge +* Connect through the IDE +* Run the following bit of code + + log; + +If notifications are getting through then you should see something like. + + + >log + =[ + "{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}", + "{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}" + ] + + +IMPORTANT: Now reset your Bangle using a BTN3 long press so that the GB() function is restored. + +## References + +[Bangle Gadgetbridge Page](https://www.espruino.com/Gadgetbridge) + +[Gadgetbridge Project Home](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Home) + diff --git a/apps/grocery/ChangeLog b/apps/grocery/ChangeLog index 5560f00bc..906046782 100644 --- a/apps/grocery/ChangeLog +++ b/apps/grocery/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Refactor code to store grocery list in separate file diff --git a/apps/grocery/app.js b/apps/grocery/app.js new file mode 100644 index 000000000..481efc3d9 --- /dev/null +++ b/apps/grocery/app.js @@ -0,0 +1,29 @@ +var filename = 'grocery_list.json'; +var settings = require("Storage").readJSON(filename,1)|| { products: [] }; + +function updateSettings() { + require("Storage").writeJSON(filename, settings); + Bangle.buzz(); +} + +function twoChat(n){ + if(n<10) return '0'+n; + return ''+n; +} + +const mainMenu = settings.products.reduce(function(m, p, i){ + const name = twoChat(p.quantity)+' '+p.name; + m[name] = { + value: p.ok, + format: v => v?'[x]':'[ ]', + onchange: v => { + settings.products[i].ok = v; + updateSettings(); + } + }; + return m; +}, { + '': { 'title': 'Grocery list' } +}); +mainMenu['< Back'] = ()=>{load();}; +E.showMenu(mainMenu); diff --git a/apps/grocery/grocery.html b/apps/grocery/grocery.html index 14c406d75..e717dee2e 100644 --- a/apps/grocery/grocery.html +++ b/apps/grocery/grocery.html @@ -105,56 +105,9 @@ } document.getElementById("upload").addEventListener("click", function() { - - - var app = ` -var newTime = ${Date.now()} -var products = ${JSON.stringify(products)} -var newTime = newTime; -var filename = 'grocery'; -var settings = require("Storage").readJSON(filename,1)|| null; -function getSettings(){ - return { - products : products, - date: newTime - }; -} -if(!settings || !settings.date || settings.date < newTime){ - settings = getSettings(); - Bangle.buzz(500); -} -function updateSettings() { - require("Storage").writeJSON(filename, settings); - Bangle.buzz(); -} -function twoChat(n){ - if(n<10) return '0'+n; - return ''+n; -} -const mainMenu = settings.products.reduce(function(m, p, i){ - const name = twoChat(p.quantity)+' '+p.name; - m[name] = { - value: p.ok, - format: v => v?'[x]':'[ ]', - onchange: v => { - settings.products[i].ok = v; - updateSettings(); - } - }; - return m; -}, { - '': { 'title': 'Grocery list' } -}); -mainMenu['< Back'] = ()=>{load();}; -E.showMenu(mainMenu); -`; - - var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQ0QACF1nGAAIxpFoYwqFwwwnRggwGB4eFAggACLzwHCMAeF1WGAgOGw2x2IGCLzYGEF4YpBwotCFwJfWFwo1GSAYtBAIIABRq4vFMhAwBzoAFdzIuKAAOc4IAGGC4qEMZOiF44wXFxovleBYvIGCwmB0WjE4V/AgfG1IvCzujFQOjwoECF6WFwovBDYOFEwN/AgIwCAgOFBwYrBBAQEBzodCF6AAHww1CBpIODAAYvRDAWG2IEBAYYJFBxICCF6Ox1WxAAQfBAYQlCAAIOJAQIvUADQvn1WGR4RfbP4gAFBwgFCF7a5EdwQADF46/cL9wAQF94AGF85bB1TvmF47vdJ4bvFF8qPRFgLv/L7jPCaQq/fYYrvgJgoAGd/7v/F/4v/F5oAdF54weFyAA/AH4A3A="))`; sendCustomizedApp({ storage:[ - {name:"grocery.app.js", url:"app.js", content:app}, - {name:"grocery.img", content:icon, evaluate:true}, - {name:"grocery"} + { name:"grocery_list.json", content: JSON.stringify({products: products}) } ] }); }); diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index b56c9f6bb..09569d8da 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -4,3 +4,4 @@ 0.04: Now displays widgets 0.05: Use g.theme for colours 0.06: Use Bangle.setUI for buttons +0.07: Theme colours fix \ No newline at end of file diff --git a/apps/launch/app.js b/apps/launch/app.js index ab1a89fc0..449e16e62 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -33,8 +33,10 @@ function drawMenu() { if (i+menuScroll==selected) { g.setColor(g.theme.bgH).fillRect(0,y,w-1,y+63); g.setColor(g.theme.fgH).drawRect(0,y,w-1,y+63); - } else - g.clearRect(0,y,w-1,y+63); + } else { + g.clearRect(0, y, w-1, y+63); + g.setColor(g.theme.fg); + } g.drawString(app.name,64,y+32); var icon=undefined; if (app.icon) icon = s.read(app.icon); diff --git a/apps/notifyfs/ChangeLog b/apps/notifyfs/ChangeLog index 1c39bcbd5..cf0a13866 100644 --- a/apps/notifyfs/ChangeLog +++ b/apps/notifyfs/ChangeLog @@ -9,3 +9,4 @@ 0.09: Add onHide callback 0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode 0.11: Improvements to help notifications work with themes, Bangle.js 2 support +0.12: More use of themes, title now uses theme highlight colors, font adjusts diff --git a/apps/notifyfs/notify.js b/apps/notifyfs/notify.js index a45d889f0..9cadbb124 100644 --- a/apps/notifyfs/notify.js +++ b/apps/notifyfs/notify.js @@ -1,7 +1,7 @@ let oldg; let id = null; let hideCallback = null; - +const titleFont = g.getWidth() / 8; /** * See notify/notify.js */ @@ -63,8 +63,8 @@ exports.show = function(options) { // top bar if (options.title||options.src) { const title = options.title || options.src; - g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30); - g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3); + g.setColor(options.titleBgColor||g.theme.bgH).fillRect(x, y, x+w-1, y+30); + g.setColor(g.theme.fgH).setFontAlign(-1, -1, 0).setFont("Vector", titleFont); g.drawString(title.trim().substring(0, 13), x+5, y+3); if (options.title && options.src) { g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2); diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 5dcf6b477..49915ee21 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -32,3 +32,4 @@ 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) 0.29: Add Customize to Theme menu +0.30: Move '< Back' to the top of menus \ No newline at end of file diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 656164e53..540ecef2a 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -75,6 +75,7 @@ function showMainMenu() { var beepN = ["Off", "Piezo", "Vibrate"]; const mainmenu = { '': { 'title': 'Settings' }, + '< Back': ()=>load(), 'Make Connectable': ()=>makeConnectable(), 'App/Widget Settings': ()=>showAppSettingsMenu(), 'BLE': ()=>showBLEMenu(), @@ -117,7 +118,6 @@ function showMainMenu() { 'Theme': ()=>showThemeMenu(), 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, - '< Back': ()=>load() }; return E.showMenu(mainmenu); } @@ -126,6 +126,7 @@ function showBLEMenu() { var hidV = [false, "kbmedia", "kb", "joy"]; var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; E.showMenu({ + '< Back': ()=>showMainMenu(), 'BLE': { value: settings.ble, format: boolFormat, @@ -158,8 +159,7 @@ function showBLEMenu() { 'Whitelist': { value: settings.whitelist?(settings.whitelist.length+" devs"):"off", onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call - }, - '< Back': ()=>showMainMenu() + } }); } @@ -180,6 +180,7 @@ function showThemeMenu() { function cl(x) { return g.setColor(x).getColor(); } m = E.showMenu({ '':{title:'Theme'}, + '< Back': ()=>showMainMenu(), 'Dark BW': ()=>{ updT({ fg:cl("#fff"), bg:cl("#000"), @@ -197,7 +198,6 @@ function showThemeMenu() { }); }, 'Customize': ()=>showCustomThemeMenu(), - '< Back': ()=>{delete m;showMainMenu();}, }); } function showCustomThemeMenu() { @@ -255,6 +255,7 @@ function showCustomThemeMenu() { function showPasskeyMenu() { var menu = { + "< Back" : ()=>showBLEMenu(), "Disable" : () => { settings.passkey = undefined; updateSettings(); @@ -275,12 +276,12 @@ function showPasskeyMenu() { } }; })(i); - menu['< Back']=()=>showBLEMenu(); E.showMenu(menu); } function showWhitelistMenu() { var menu = { + "< Back" : ()=>showBLEMenu(), "Disable" : () => { settings.whitelist = undefined; updateSettings(); @@ -290,7 +291,7 @@ function showWhitelistMenu() { if (settings.whitelist) settings.whitelist.forEach(function(d){ menu[d.substr(0,17)] = function() { E.showPrompt('Remove\n'+d).then((v) => { - if (v) { + if (v) settings.whitelist.splice(settings.whitelist.indexOf(d),1); updateSettings(); } @@ -312,7 +313,6 @@ function showWhitelistMenu() { showWhitelistMenu(); }); }; - menu['< Back']=()=>showBLEMenu(); E.showMenu(menu); } diff --git a/apps/slidingtext/slidingtext.locale.es.js b/apps/slidingtext/slidingtext.locale.es.js index e1f3bc18b..1b6f6d11b 100644 --- a/apps/slidingtext/slidingtext.locale.es.js +++ b/apps/slidingtext/slidingtext.locale.es.js @@ -7,7 +7,7 @@ const spanishNumberStr = [ ["ZERO"], // 0 ["CUATRO",''], //4 ["CINCO",''], //5 ["SEIS",''], //6 - ["SEITO",''], //7 + ["SIETE",''], //7 ["OCHO",''], //8 ["NUEVE",''], // 9, ["DIEZ",''], // 10 @@ -20,7 +20,7 @@ const spanishNumberStr = [ ["ZERO"], // 0 ["DIECI",'SIETE'], // 17 ["DIECI",'OCHO'], // 18 ["DIECI",'NEUVE'], // 19 - ["VEINTA",''], // 20 + ["VEINTE",''], // 20 ["VEINTI",'UNO'], // 21 ["VEINTI",'DOS'], // 22 ["VEINTI",'TRES'], // 23 @@ -74,4 +74,4 @@ class SpanishDateFormatter extends DateFormatter { } } -module.exports = SpanishDateFormatter; \ No newline at end of file +module.exports = SpanishDateFormatter; diff --git a/apps/waveclk/ChangeLog b/apps/waveclk/ChangeLog index 5560f00bc..8c2a33143 100644 --- a/apps/waveclk/ChangeLog +++ b/apps/waveclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide diff --git a/apps/waveclk/app.js b/apps/waveclk/app.js index 7e1870aa7..f1c67ce2f 100644 --- a/apps/waveclk/app.js +++ b/apps/waveclk/app.js @@ -28,7 +28,7 @@ function queueDraw() { function draw() { var x = g.getWidth()/2; var y = 24+20; - + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); if (g.getWidth() == IMAGEWIDTH) g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); @@ -65,8 +65,8 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); \ No newline at end of file diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index fd5d4d146..8f997a83e 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -4,4 +4,6 @@ 0.05: Add wind direction. 0.06: Use setUI for launcher. 0.07: Add theme support and unknown icon. -0.08: Refactor and reduce widget ram usage. \ No newline at end of file +0.08: Refactor and reduce widget ram usage. +0.09: Fix crash when weather.json is absent. +0.10: Use new Layout library \ No newline at end of file diff --git a/apps/weather/app.js b/apps/weather/app.js index 9d64583e9..6dba14143 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -1,96 +1,106 @@ -(() => { - const weather = require('weather'); - let current = weather.get(); +const Layout = require('Layout'); +const locale = require('locale'); +const weather = require('weather'); +let current = weather.get(); - function formatDuration(millis) { - let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); - if (millis < 60000) return "< 1 minute"; - if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); - if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); - return pluralize(Math.floor(millis/86400000), "day"); - } +Bangle.loadWidgets(); - function draw() { - g.reset(); - g.clearRect(0, 24, 239, 239); +var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ + {filly: 1}, + {type: "h", filly: 0, c: [ + {type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon", + render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)}, + {type: "v", fillx: 1, c: [ + {type: "h", pad: 2, c: [ + {type: "txt", font: "18%", id: "temp", label: "000"}, + {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, + ]}, + {filly: 1}, + {type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"}, + {type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"}, + {filly: 1}, + {type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"}, + {type: "h", halign: -1, c: [ + {type: "txt", font: "9%", pad: 2, id: "wind", label: "00"}, + {type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"}, + ]}, + ]}, + ]}, + {filly: 1}, + {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, + {filly: 1}, + {type: "h", c: [ + {type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"}, + {fillx: 1}, + {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, + ]}, + {filly: 1}, +]}, null, {lazy: true}); - weather.drawIcon(current.txt, 65, 90, 55); - const locale = require("locale"); +function formatDuration(millis) { + let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); + if (millis < 60000) return "< 1 minute"; + if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); + if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); + return pluralize(Math.floor(millis/86400000), "day"); +} - g.reset(); +function draw() { + layout.icon.txt = current.txt; + const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); + layout.temp.label = temp[1]; + layout.tempUnit.label = temp[2]; + layout.hum.label = current.hum+"%"; + const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/); + layout.wind.label = wind[1]; + layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase(); + layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1); + layout.loc.label = current.loc; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.update(); + layout.render(); +} - const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); - let width = g.setFont("Vector", 40).stringWidth(temp[1]); - width += g.setFont("Vector", 20).stringWidth(temp[2]); - g.setFont("Vector", 40).setFontAlign(-1, -1, 0); - g.drawString(temp[1], 180-width/2, 70); - g.setFont("Vector", 20).setFontAlign(1, -1, 0); - g.drawString(temp[2], 180+width/2, 70); +function drawUpdateTime() { + if (!current || !current.time) return; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.update(); + layout.render(); +} - g.setFont("6x8", 1); - g.setFontAlign(-1, 0, 0); - g.drawString("Humidity", 135, 130); - g.setFontAlign(1, 0, 0); - g.drawString(current.hum+"%", 225, 130); - if ('wind' in current) { - g.setFontAlign(-1, 0, 0); - g.drawString("Wind", 135, 142); - g.setFontAlign(1, 0, 0); - g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142); - } - - g.setFont("6x8", 2).setFontAlign(0, 0, 0); - g.drawString(current.loc, 120, 170); - - g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190); - - drawUpdateTime(); - - g.flip(); - } - - function drawUpdateTime() { - if (!current || !current.time) return; - let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`; - g.reset(); - g.clearRect(0, 202, 239, 210); - g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(text, 120, 206); - } - - function update() { - current = weather.get(); - NRF.removeListener("connect", update); - if (current) { - draw(); - } else if (NRF.getSecurityStatus().connected) { - E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); +function update() { + current = weather.get(); + NRF.removeListener("connect", update); + if (current) { + draw(); + } else { + layout.forgetLazyState(); + if (NRF.getSecurityStatus().connected) { + E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); } else { - E.showMessage("Weather unknown\n\nGadgetbridge\nnot connected"); + E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected"); NRF.on("connect", update); } } +} - let interval = setInterval(drawUpdateTime, 60000); - Bangle.on('lcdPower', (on) => { - if (interval) { - clearInterval(interval); - interval = undefined; - } - if (on) { - drawUpdateTime(); - interval = setInterval(drawUpdateTime, 60000); - } - }); +let interval = setInterval(drawUpdateTime, 60000); +Bangle.on('lcdPower', (on) => { + if (interval) { + clearInterval(interval); + interval = undefined; + } + if (on) { + drawUpdateTime(); + interval = setInterval(drawUpdateTime, 60000); + } +}); - weather.on("update", update); +weather.on("update", update); - update(); +update(); - // Show launcher when middle button pressed - Bangle.setUI("clock"); +// Show launcher when middle button pressed +Bangle.setUI("clock"); - Bangle.loadWidgets(); - Bangle.drawWidgets(); -})() +Bangle.drawWidgets(); diff --git a/apps/weather/lib.js b/apps/weather/lib.js index f08df4a4a..299009e74 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -47,7 +47,7 @@ global.GB = (event) => { }; exports.get = function() { - return storage.readJSON('weather.json').weather; + return (storage.readJSON('weather.json')||{}).weather; } scheduleExpiry(storage.readJSON('weather.json')||{}); diff --git a/apps/widclk/ChangeLog b/apps/widclk/ChangeLog index 655679515..c74857ab4 100644 --- a/apps/widclk/ChangeLog +++ b/apps/widclk/ChangeLog @@ -2,3 +2,4 @@ 0.03: Ensure redrawing works with variable size widget system 0.04: Fix regression stopping correct widget updates 0.05: Don't show clock widget if already showing clock app +0.06: Use 7 segment font, update *on* the minute, use less memory diff --git a/apps/widclk/widget.js b/apps/widclk/widget.js index cd4b29367..da3f60ce8 100644 --- a/apps/widclk/widget.js +++ b/apps/widclk/widget.js @@ -1,30 +1,16 @@ -(function() { - // don't show widget if we know we have a clock app running - if (Bangle.CLOCK) return; +/* Simple clock that appears in the widget bar if no other clock +is running. We update once per minute, but don't bother stopping +if the */ - let intervalRef = null; - var width = 5 * 6*2 - - function draw() { - g.reset().setFont("6x8", 2).setFontAlign(-1, 0); - var time = require("locale").time(new Date(),1); - g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60 - } - function clearTimers(){ - if(intervalRef) { - clearInterval(intervalRef); - intervalRef = null; - } - } - function startTimers(){ - intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000); - WIDGETS["wdclk"].draw(); - } - Bangle.on('lcdPower', (on) => { - clearTimers(); - if (on) startTimers(); - }); - - WIDGETS["wdclk"]={area:"tr",width:width,draw:draw}; - if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000); -})() +// don't show widget if we know we have a clock app running +if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") */,draw:function() { + g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9); + var time = require("locale").time(new Date(),1); + g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60 + // queue draw in one minute + if (drawTimeout) clearTimeout(drawTimeout); + this.drawTimeout = setTimeout(()=>{ + this.drawTimeout = undefined; + this.draw(); + }, 60000 - (Date.now() % 60000)); +}}; diff --git a/bin/build_bangle2_c.js b/bin/firmwaremaker_c.js similarity index 86% rename from bin/build_bangle2_c.js rename to bin/firmwaremaker_c.js index 5b4464691..2cb993d00 100755 --- a/bin/build_bangle2_c.js +++ b/bin/firmwaremaker_c.js @@ -11,16 +11,35 @@ var SETTINGS = { pretokenise : true }; +var DEVICE = process.argv[2]; + var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; -var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); -var APPS = [ // IDs of apps to install - "boot","launchb2","s7clk","setting", - "about","alarm","widlock","widbat","widbt" -]; var MINIFY = true; +var OUTFILE, APPS; + +if (DEVICE=="BANGLEJS") { + var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs1_storage_default.c'); + var APPS = [ // IDs of apps to install + "boot","launch","mclock","setting", + "about","alarm","widbat","widbt","welcome" + ]; +} else if (DEVICE=="BANGLEJS2") { + var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); + var APPS = [ // IDs of apps to install + "boot","launchb2","s7clk","setting", + "about","alarm","widlock","widbat","widbt" + ]; +} else { + console.log("USAGE:"); + console.log(" bin/firmwaremaker_c.js BANGLEJS"); + console.log(" bin/firmwaremaker_c.js BANGLEJS2"); + process.exit(1); +} +console.log("Device = ",DEVICE); + var fs = require("fs"); global.Const = { diff --git a/modules/Layout.js b/modules/Layout.js index 5ac0cab16..09e2a3d8c 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -36,6 +36,8 @@ layoutObject has: * A `id` field. If specified the object is added with this name to the returned `layout` object, so can be referenced as `layout.foo` * A `font` field, eg `6x8` or `30%` to use a percentage of screen height +* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` + and `fillx`/`filly` to be set. Not compatible with text rotation. * A `col` field, eg `#f00` for red * A `bgCol` field for background color (will automatically fill on render) * A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center @@ -71,6 +73,7 @@ Other functions: * `layout.update()` - update positions of everything if contents have changed * `layout.debug(obj)` - draw outlines for objects on screen * `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render) +* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render */ @@ -138,7 +141,7 @@ function Layout(layout, buttons, options) { if (l.c) l.c.forEach(idRecurser); } idRecurser(layout); - this.update(); + this.updateNeeded = true; } Layout.prototype.remove = function (l) { @@ -152,6 +155,24 @@ Layout.prototype.remove = function (l) { } }; +function wrappedLines(str, maxWidth) { + var lines = []; + for (var unwrappedLine of str.split("\n")) { + var words = unwrappedLine.split(" "); + var line = words.shift(); + for (var word of words) { + if (g.stringWidth(line + " " + word) > maxWidth) { + lines.push(line); + line = word; + } else { + line += " " + word; + } + } + lines.push(line); + } + return lines; +} + function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol); if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { @@ -176,6 +197,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { Layout.prototype.render = function (l) { if (!l) l = this._l; + if (this.updateNeeded) this.update(); function render(l) {"ram" g.reset(); @@ -187,7 +209,14 @@ Layout.prototype.render = function (l) { var cb = { "":function(){}, "txt":function(l){ - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + if (l.wrap) { + g.setFont(l.font,l.fsz).setFontAlign(0,-1); + var lines = wrappedLines(l.label, l.w); + var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); + lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); + } else { + g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + } }, "btn":function(l){ var x = l.x+(0|l.pad); var y = l.y+(0|l.pad); @@ -230,6 +259,10 @@ Layout.prototype.render = function (l) { } }; +Layout.prototype.forgetLazyState = function () { + this.rects = {}; +} + Layout.prototype.layout = function (l) { // l = current layout element // exw,exh = extra width/height available @@ -282,6 +315,7 @@ Layout.prototype.debug = function(l,c) { if (l.c) l.c.forEach(n => this.debug(n,c)); }; Layout.prototype.update = function() { + delete this.updateNeeded; var l = this._l; var w = g.getWidth(); var y = this.yOffset; @@ -305,9 +339,13 @@ Layout.prototype.update = function() { l.font = f[0]; l.fsz = f[1]; } - g.setFont(l.font,l.fsz); - l._h = g.getFontHeight(); - l._w = g.stringWidth(l.label); + if (l.wrap) { + l._h = l._w = 0; + } else { + g.setFont(l.font,l.fsz); + l._h = g.getFontHeight(); + l._w = g.stringWidth(l.label); + } }, "btn": function(l) { l._h = 24; l._w = 14 + l.label.length*8; diff --git a/tests/Layout/tests/wrapping.bmp b/tests/Layout/tests/wrapping.bmp new file mode 100644 index 000000000..a0d80cc5b Binary files /dev/null and b/tests/Layout/tests/wrapping.bmp differ diff --git a/tests/Layout/tests/wrapping.js b/tests/Layout/tests/wrapping.js new file mode 100644 index 000000000..652530f9c --- /dev/null +++ b/tests/Layout/tests/wrapping.js @@ -0,0 +1,7 @@ +var layout = new Layout({type:"v", c: [ + {type:"h", c: [ + {type:"txt", font:"10%", wrap: true, fillx: true, filly: true, label:"This is wrapping text that fills remaining space"}, + {type:"txt", font:"6x8", wrap: true, width: 60, filly: true, label:"This is wrapping text in a narrow column"}, + ]}, + {type:"txt", font:"6x8", wrap: true, fillx: true, height: 20, label:"This doesn't need to wrap"}, +]}); \ No newline at end of file