diff --git a/README.md b/README.md index ac80b8270..20ae8afb2 100644 --- a/README.md +++ b/README.md @@ -377,40 +377,37 @@ that handles configuring the app. When the app settings are opened, this function is called with one argument, `back`: a callback to return to the settings menu. -Usually it will save any information in `app.json` where `app` is the name +Usually it will save any information in `myappid.json` where `myappid` is the name of your app - so you should change the example accordingly. Example `settings.js` ```js // make sure to enclose the function in parentheses (function(back) { - let settings = require('Storage').readJSON('app.json',1)||{}; - function save(key, value) { - settings[key] = value; - require('Storage').write('app.json',settings); - } + function get(key, def) { return require('Settings').get('myappid', key, def); } + function set(key, value) { require('Settings').set('myappid', key, value); } const appMenu = { '': {'title': 'App Settings'}, '< Back': back, 'Monkeys': { - value: settings.monkeys||12, - onchange: (m) => {save('monkeys', m)} + value: get('monkeys', 12), + onchange: (m) => set('monkeys', m) } }; E.showMenu(appMenu) }) ``` -In this example the app needs to add `app.settings.js` to `storage` in `apps.json`. -It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled. +In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`. +It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled. ```json - { "id": "app", + { "id": "myappid", ... "storage": [ ... - {"name":"app.settings.js","url":"settings.js"}, + {"name":"myappid.settings.js","url":"settings.js"} ], "data": [ - {"name":"app.json"} + {"name":"myappid.json"} ] }, ``` diff --git a/apps.json b/apps.json index bc1f8402f..bf81d9d7d 100644 --- a/apps.json +++ b/apps.json @@ -82,7 +82,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.07", + "version": "0.08", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system,health", @@ -543,11 +543,11 @@ { "id": "cubescramble", "name": "Cube Scramble", - "version":"0.02", + "version":"0.03", "description": "A random scramble generator for the 3x3 Rubik's cube", "icon": "cube-scramble.png", "tags": "", - "supports" : ["BANGLEJS","BANGLEJS2"], + "supports" : ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "storage": [ @@ -599,7 +599,7 @@ { "id": "compass", "name": "Compass", - "version": "0.04", + "version": "0.05", "description": "Simple compass that points North", "icon": "compass.png", "screenshots": [{"url":"screenshot_compass.png"}], @@ -749,12 +749,12 @@ { "id": "weather", "name": "Weather", - "version": "0.10", + "version": "0.11", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], "tags": "widget,outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "readme.md", "storage": [ {"name":"weather.app.js","url":"app.js"}, @@ -768,11 +768,11 @@ { "id": "chargeanim", "name": "Charge Animation", - "version": "0.01", + "version": "0.02", "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", "icon": "icon.png", "tags": "battery", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"chargeanim.app.js","url":"app.js"}, @@ -892,7 +892,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widchime.wid.js","url":"widget.js"}, {"name":"widchime.settings.js","url":"settings.js"} @@ -908,7 +908,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widram.wid.js","url":"widget.js"} ] @@ -2639,14 +2639,16 @@ { "id": "magnav", "name": "Navigation Compass", - "version": "0.04", + "version": "0.05", "description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.", + "screenshots": [{"url":"screenshot-b2.png"},{"url":"screenshot-light-b2.png"}], "icon": "magnav.png", "tags": "tool,outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ - {"name":"magnav.app.js","url":"magnav.min.js"}, + {"name":"magnav.app.js","url":"magnav_b1.js","supports":["BANGLEJS"]}, + {"name":"magnav.app.js","url":"magnav_b2.js","supports":["BANGLEJS2"]}, {"name":"magnav.img","url":"magnav-icon.js","evaluate":true} ], "data": [{"name":"magnav.json"}] @@ -2730,8 +2732,9 @@ { "id": "multiclock", "name": "Multi Clock", - "version": "0.08", - "description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 or swipe left-right. For best display set theme Background 2 to cyan or some other bright colour in settings.", + "version": "0.09", + "description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 (Bangle 2 touch top-right, bottom right). For best display set theme Background 2 to cyan or some other bright colour in settings.", + "screenshots": [{"url":"screen-ana.png"},{"url":"screen-big.png"},{"url":"screen-td.png"},{"url":"screen-nifty.png"},{"url":"screen-word.png"},{"url":"screen-sec.png"}], "icon": "multiclock.png", "type": "clock", "tags": "clock", @@ -3127,15 +3130,17 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.04", - "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", + "version": "0.05", + "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", "type": "launch", "tags": "tool,system,launcher", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ - {"name":"dtlaunch.app.js","url":"app.js"}, + {"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]}, + {"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]}, {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} ] }, @@ -3644,15 +3649,15 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.05", + "version": "0.06", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}], + "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}], "type": "app", "tags": "tools,bluetooth,gadgetbridge,music", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", - "allow_emulator": false, + "allow_emulator": true, "storage": [ {"name":"gbmusic.app.js","url":"app.js"}, {"name":"gbmusic.settings.js","url":"settings.js"}, @@ -3718,12 +3723,12 @@ "id": "qmsched", "name": "Quiet Mode Schedule and Widget", "shortName": "Quiet Mode", - "version": "0.02", + "version": "0.03", "description": "Automatically turn Quiet Mode on or off at set times", "icon": "app.png", "screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}], "tags": "tool,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"qmsched","url":"lib.js"}, @@ -4040,7 +4045,7 @@ { "id": "antonclk", "name": "Anton Clock", - "version": "0.02", + "version": "0.03", "description": "A simple clock using the bold Anton font.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], @@ -4201,7 +4206,7 @@ { "id": "swiperclocklaunch", "name": "Swiper Clock Launch", - "version": "0.01", + "version": "0.02", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", "type": "bootloader", @@ -4264,30 +4269,31 @@ ] }, { - "id": "a_battery_widget", + "id": "wid_a_battery_widget", "name": "A Battery Widget (with percentage)", "shortName":"A Battery Widget", "icon": "widget.png", - "version":"1.0", + "version":"1.01", "type": "widget", "supports": ["BANGLEJS2"], "readme": "README.md", "description": "Simple and slim battery widget with charge status and percentage", "tags": "widget,battery", "storage": [ - {"name":"a_battery_widget.wid.js","url":"widget.js"} + {"name":"wid_a_battery_widget.wid.js","url":"widget.js"} ] }, - { + { "id": "lcars", "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.01", + "version":"0.02", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", "type": "clock", "tags": "clock", + "screenshots": [{"url":"screenshot.png"}], "storage": [ {"name":"lcars.app.js","url":"lcars.app.js"}, {"name":"lcars.img","url":"lcars.icon.js","evaluate":true} @@ -4297,15 +4303,50 @@ "name": "Binary Watch", "shortName":"BinWatch", "icon": "app.png", - "version":"0.02", - "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"}], + "version":"0.03", + "supports": ["BANGLEJS2"], + "readme": "README.md", "allow_emulator":true, "description": "Famous binary watch", "tags": "clock", "type": "clock", - "storage": [ + "storage": [ {"name":"binwatch.app.js","url":"app.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] + }, + { + "id": "hidmsicswipe", + "name": "Bluetooth Music Swipe Controls", + "shortName": "Swipe Control", + "version": "0.01", + "description": "Based on the original Bluetooth Music Controls. Swipe up/down for volume, left/right for previous and next, tap for play/pause and btn1 to lock and unlock the controls. Enable HID in settings, pair with your phone, then use this app to control music from your watch!", + "icon": "hidmsicswipe.png", + "tags": "bluetooth", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"hidmsicswipe.app.js","url":"hidmsicswipe.js"}, + {"name":"hidmsicswipe.img","url":"hidmsicswipe-icon.js","evaluate":true} + ] + }, + { + "id": "authentiwatch", + "name": "2FA Authenticator", + "shortName": "AuthWatch", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "version": "0.01", + "description": "Google Authenticator compatible tool.", + "tags": "tool", + "interface": "interface.html", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"authentiwatch.app.js","url":"app.js"}, + {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"authentiwatch.json"}] } ] diff --git a/apps/.eslintrc.json b/apps/.eslintrc.json index 9d4f8a4aa..d656c2555 100644 --- a/apps/.eslintrc.json +++ b/apps/.eslintrc.json @@ -152,6 +152,8 @@ "no-prototype-builtins": "off", "no-redeclare": "off", "no-unreachable": "warn", + "no-cond-assign": "warn", + "no-useless-catch": "warn", // TODO: "no-undef": "warn", "no-undef": "off", "no-unused-vars": "off", diff --git a/apps/a_battery_widget/ChangeLog b/apps/a_battery_widget/ChangeLog deleted file mode 100644 index 0dbd5f758..000000000 --- a/apps/a_battery_widget/ChangeLog +++ /dev/null @@ -1 +0,0 @@ -2021/11/18 | 1.0: Release for Bangle 2 diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 8c2a33143..f88276a90 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Load widgets after setUI so widclk knows when to hide +0.03: Clock now shows day of week under date. diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index f6fcf1708..7912dfc0f 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -23,6 +23,7 @@ function draw() { var date = new Date(); var timeStr = require("locale").time(date,1); var dateStr = require("locale").date(date).toUpperCase(); + var dowStr = require("locale").dow(date).toUpperCase(); // draw time g.setFontAlign(0,0).setFont("Anton"); g.clearRect(0,y-40,g.getWidth(),y+35); // clear the background @@ -32,6 +33,10 @@ function draw() { g.setFontAlign(0,0).setFont("6x8",2); g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background g.drawString(dateStr,x,y); + //draw day of week + y += 16; + g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background + g.drawString(dowStr,x,y); // queue draw in one minute queueDraw(); } diff --git a/apps/authentiwatch/ChangeLog b/apps/authentiwatch/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/authentiwatch/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/authentiwatch/README.md b/apps/authentiwatch/README.md new file mode 100644 index 000000000..403770c2b --- /dev/null +++ b/apps/authentiwatch/README.md @@ -0,0 +1,26 @@ +# Authentiwatch - 2FA Authenticator + +## Supports + +* Google Authenticator compatible 2-factor authentication +* Hash calculations: + * Bangle 1: SHA1 only (same as Google Authenticator) + * Bangle 2: All (SHA1, SHA256, SHA512) +* Timed (TOTP) and Counter (HOTP) modes +* Custom periods +* Between 6 and 10 digits +* Phone/PC configuration web page: + * Add/edit/delete/arrange tokens + * Scan QR codes + * Produce scannable QR codes + +## Usage + +* Use the Phone/PC web page interface to manage the tokens stored on the watch. +* Tokens are stored *ONLY* on the watch. +* Swipe right to exit to the app launcher. +* Swipe left on selected counter token to advance the counter to the next value. + +## Creator + +Andrew Gregory (andrew.gregory at gmail) diff --git a/apps/authentiwatch/app-icon.js b/apps/authentiwatch/app-icon.js new file mode 100644 index 000000000..27ced695e --- /dev/null +++ b/apps/authentiwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN")) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js new file mode 100644 index 000000000..43eff4709 --- /dev/null +++ b/apps/authentiwatch/app.js @@ -0,0 +1,300 @@ +const tokenentryheight = 46; +// Hash functions +const crypto = require("crypto"); +const algos = { + "SHA512":{sha:crypto.SHA512,retsz:64,blksz:128}, + "SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 }, + "SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 }, +}; + +var tokens = require("Storage").readJSON("authentiwatch.json", true) || []; + +// QR Code Text +// +// Example: +// +// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret} +// +// ${algorithm} : one of SHA1 / SHA256 / SHA512 +// ${digits} : one of 6 / 8 +// ${period} : one of 30 / 60 +// ${url} : a domain name "example.com" +// ${secret} : the seed code + +function b32decode(seedstr) { + // RFC4648 + var i, buf = 0, bitcount = 0, retstr = ""; + for (i in seedstr) { + var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0); + if (c != -1) { + buf <<= 5; + buf |= c; + bitcount += 5; + if (bitcount >= 8) { + retstr += String.fromCharCode(buf >> (bitcount - 8)); + buf &= (0xFF >> (16 - bitcount)); + bitcount -= 8; + } + } + } + if (bitcount > 0) { + retstr += String.fromCharCode(buf << (8 - bitcount)); + } + var retbuf = new Uint8Array(retstr.length); + for (i in retstr) { + retbuf[i] = retstr.charCodeAt(i); + } + return retbuf; +} +function do_hmac(key, message, algo) { + var a = algos[algo]; + // RFC2104 + if (key.length > a.blksz) { + key = a.sha(key); + } + var istr = new Uint8Array(a.blksz + message.length); + var ostr = new Uint8Array(a.blksz + a.retsz); + for (var i = 0; i < a.blksz; ++i) { + var c = (i < key.length) ? key[i] : 0; + istr[i] = c ^ 0x36; + ostr[i] = c ^ 0x5C; + } + istr.set(message, a.blksz); + ostr.set(a.sha(istr), a.blksz); + var ret = a.sha(ostr); + // RFC4226 dynamic truncation + var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); + return v.getUint32(0) & 0x7FFFFFFF; +} +function hotp(token) { + var tick; + var d = new Date(); + if (token.period > 0) { + // RFC6238 - timed + var seconds = Math.floor(d.getTime() / 1000); + tick = Math.floor(seconds / token.period); + } else { + // RFC4226 - counter + tick = -token.period; + } + var msg = new Uint8Array(8); + var v = new DataView(msg.buffer); + v.setUint32(0, tick >> 16 >> 16); + v.setUint32(4, tick & 0xFFFFFFFF); + var ret = ""; + try { + var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase()); + ret = "" + hash % Math.pow(10, token.digits); + while (ret.length < token.digits) { + ret = "0" + ret; + } + } catch(err) { + ret = "Not supported"; + } + return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)}; +} + +var state = { + listy: 0, + prevcur:0, + curtoken:-1, + nextTime:0, + otp:"", + rem:0, + hide:0 +}; + +function drawToken(id, r) { + var x1 = r.x; + var y1 = r.y; + var x2 = r.x + r.w - 1; + var y2 = r.y + r.h - 1; + var adj; + g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ), + Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2)); + if (id == state.curtoken) { + // current token + g.setColor(g.theme.fgH); + g.setBgColor(g.theme.bgH); + g.setFont("Vector", 16); + // center just below top line + g.setFontAlign(0, -1, 0); + adj = y1; + } else { + g.setColor(g.theme.fg); + g.setBgColor(g.theme.bg); + g.setFont("Vector", 30); + // center in box + g.setFontAlign(0, 0, 0); + adj = (y1 + y2) / 2; + } + g.clearRect(x1, y1, x2, y2); + g.drawString(tokens[id].label, (x1 + x2) / 2, adj, false); + if (id == state.curtoken) { + if (tokens[id].period > 0) { + // timed - draw progress bar + let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period); + g.fillRect(x1, y2 - 4, xr, y2 - 1); + adj = 0; + } else { + // counter - draw triangle as swipe hint + let yc = (y1 + y2) / 2; + g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]); + adj = 5; + } + // digits just below label + g.setFont("Vector", (state.otp.length > 8) ? 26 : 30); + g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false); + } + // shaded lines top and bottom + g.setColor(0.5, 0.5, 0.5); + g.drawLine(x1, y1, x2, y1); + g.drawLine(x1, y2, x2, y2); + g.setClipRect(0, 0, g.getWidth(), g.getHeight()); +} + +function draw() { + var d = new Date(); + if (state.curtoken != -1) { + var t = tokens[state.curtoken]; + if (d.getTime() > state.nextTime) { + if (state.hide == 0) { + // auto-hide the current token + if (state.curtoken != -1) { + state.prevcur = state.curtoken; + state.curtoken = -1; + } + state.nextTime = 0; + } else { + // time to generate a new token + var r = hotp(t); + state.nextTime = r.next; + state.otp = r.hotp; + if (t.period <= 0) { + state.hide = 1; + } + state.hide--; + } + } + state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000)); + } + if (tokens.length > 0) { + var drewcur = false; + var id = Math.floor(state.listy / tokenentryheight); + var y = id * tokenentryheight + Bangle.appRect.y - state.listy; + while (id < tokens.length && y < Bangle.appRect.y2) { + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight}); + if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) { + drewcur = true; + } + id += 1; + y += tokenentryheight; + } + if (drewcur) { + // the current token has been drawn - draw it again in 1sec + if (state.drawtimer) { + clearTimeout(state.drawtimer); + } + state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime()); + if (tokens[state.curtoken].period <= 0) { + state.hide = 0; + } + } else { + // de-select the current token if it is scrolled out of view + if (state.curtoken != -1) { + state.prevcur = state.curtoken; + state.curtoken = -1; + } + state.nexttime = 0; + } + } else { + g.setFont("Vector", 30); + g.setFontAlign(0, 0, 0); + g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false); + } +} + +function onTouch(zone, e) { + if (e) { + var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight); + if (id == state.curtoken || tokens.length == 0) { + id = -1; + } + if (state.curtoken != id) { + if (id != -1) { + var y = id * tokenentryheight - state.listy; + if (y < 0) { + state.listy += y; + y = 0; + } + y += tokenentryheight; + if (y > Bangle.appRect.h) { + state.listy += (y - Bangle.appRect.h); + } + } + state.nextTime = 0; + state.curtoken = id; + state.hide = 2; + draw(); + } + } +} + +function onDrag(e) { + if (e.x > g.getWidth() || e.y > g.getHeight()) return; + if (e.dx == 0 && e.dy == 0) return; + var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h); + newy = Math.max(0, newy); + if (newy != state.listy) { + state.listy = newy; + draw(); + } +} + +function onSwipe(e) { + if (e == 1) { + Bangle.showLauncher(); + } + if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) { + tokens[state.curtoken].period--; + require("Storage").writeJSON("authentiwatch.json", tokens); + state.nextTime = 0; + state.hide = 2; + draw(); + } +} + +function bangle1Btn(e) { + if (tokens.length > 0) { + if (state.curtoken == -1) { + state.curtoken = state.prevcur; + } else { + switch (e) { + case -1: state.curtoken--; break; + case 1: state.curtoken++; break; + } + } + state.curtoken = Math.max(state.curtoken, 0); + state.curtoken = Math.min(state.curtoken, tokens.length - 1); + var fakee = {}; + fakee.y = state.curtoken * tokenentryheight - state.listy + Bangle.appRect.y; + state.curtoken = -1; + state.nextTime = 0; + onTouch(0, fakee); + } +} + +Bangle.on('touch', onTouch); +Bangle.on('drag' , onDrag ); +Bangle.on('swipe', onSwipe); +if (typeof BTN2 == 'number') { + setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true}); + setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true}); + setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true}); +} +Bangle.loadWidgets(); + +// Clear the screen once, at startup +g.clear(); +draw(); +Bangle.drawWidgets(); diff --git a/apps/authentiwatch/app.png b/apps/authentiwatch/app.png new file mode 100644 index 000000000..208fb63b3 Binary files /dev/null and b/apps/authentiwatch/app.png differ diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html new file mode 100644 index 000000000..12c0c1d8d --- /dev/null +++ b/apps/authentiwatch/interface.html @@ -0,0 +1,373 @@ + + + + + + + + + + + + + + + + + +

Authentiwatch

+
+

No watch comms.

+
+
+ + + +
+
+
+
+
+
+ +
+
+ + + + diff --git a/apps/authentiwatch/qr_packed.js b/apps/authentiwatch/qr_packed.js new file mode 100644 index 000000000..28b1bddd0 --- /dev/null +++ b/apps/authentiwatch/qr_packed.js @@ -0,0 +1,107 @@ +/* Packed with Google Closure +* +* Ported to JavaScript by Lazar Laszlo 2011 +* lazarsoft@gmail.com, www.lazarsoft.info +* +* Copyright 2007 ZXing authors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +var qrcode=function(){"use strict";function a(h,b){this.count=h;this.dataCodewords=b;this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("DataCodewords",function(){return this.dataCodewords})}function f(h,b,e){this.ecCodewordsPerBlock=h;this.ecBlocks=e?[b,e]:Array(b);this.__defineGetter__("ECCodewordsPerBlock",function(){return this.ecCodewordsPerBlock});this.__defineGetter__("TotalECCodewords",function(){return this.ecCodewordsPerBlock*this.NumBlocks});this.__defineGetter__("NumBlocks", +function(){for(var d=0,c=0;cMath.abs(d-b);if(h){var a=b;b=e;e=a;a=d;d=c;c=a}for(var m=Math.abs(d-b),f=Math.abs(c-e),q=-m>>1,k=ed?(h=b/(b-d),d=0):d>=g.width&&(h=(g.width-1-b)/(d-b),d=g.width-1);c=Math.floor(e-(c-e)*h);h=1;0>c?(h=e/(e-c),c=0):c>=g.height&&(h=(g.height-1-e)/(c-e),c=g.height-1);d=Math.floor(b+(d-b)*h);a+=this.sizeOfBlackWhiteBlackRun(b,e,d,c);return a-1};this.calculateModuleSizeOneWay=function(b,e){var d=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(b.X), +Math.floor(b.Y),Math.floor(e.X),Math.floor(e.Y)),c=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.X),Math.floor(e.Y),Math.floor(b.X),Math.floor(b.Y));return isNaN(d)?c/7:isNaN(c)?d/7:(d+c)/14};this.calculateModuleSize=function(b,e,d){return(this.calculateModuleSizeOneWay(b,e)+this.calculateModuleSizeOneWay(b,d))/2};this.distance=function(b,e){var d=b.X-e.X,c=b.Y-e.Y;return Math.sqrt(d*d+c*c)};this.computeDimension=function(b,e,d,c){e=Math.round(this.distance(b,e)/c);b=Math.round(this.distance(b, +d)/c);b=(e+b>>1)+7;switch(b&3){case 0:b++;break;case 2:b--;break;case 3:throw"Error";}return b};this.findAlignmentInRegion=function(b,e,d,c){c=Math.floor(c*b);var h=Math.max(0,e-c);e=Math.min(g.width-1,e+c);if(e-h<3*b)throw"Error";var a=Math.max(0,d-c);return(new R(this.image,h,a,e-h,Math.min(g.height-1,d+c)-a,b,this.resultPointCallback)).find()};this.createTransform=function(b,e,d,c,h){h-=3.5;var a;if(null!=c){var p=c.X;c=c.Y;var f=a=h-3}else p=e.X-b.X+d.X,c=e.Y-b.Y+d.Y,f=a=h;return z.quadrilateralToQuadrilateral(3.5, +3.5,h,3.5,f,a,3.5,h,b.X,b.Y,e.X,e.Y,p,c,d.X,d.Y)};this.sampleGrid=function(b,e,d){return F.sampleGrid3(b,d,e)};this.processFinderPatternInfo=function(b){var e=b.TopLeft,d=b.TopRight;b=b.BottomLeft;var c=this.calculateModuleSize(e,d,b);if(1>c)throw"Error";var h=this.computeDimension(e,d,b,c),a=k.getProvisionalVersionForDimension(h),m=a.DimensionForVersion-7,f=null;if(0>3&3);this.dataMask=h&7;this.__defineGetter__("ErrorCorrectionLevel",function(){return this.errorCorrectionLevel});this.__defineGetter__("DataMask",function(){return this.dataMask});this.GetHashCode=function(){return this.errorCorrectionLevel.ordinal()<< +3|this.dataMask};this.Equals=function(b){return this.errorCorrectionLevel==b.errorCorrectionLevel&&this.dataMask==b.dataMask}}function C(h,b,e){this.ordinal_Renamed_Field=h;this.bits=b;this.name=e;this.__defineGetter__("Bits",function(){return this.bits});this.__defineGetter__("Name",function(){return this.name});this.ordinal=function(){return this.ordinal_Renamed_Field}}function I(h,b){b||(b=h);if(1>h||1>b)throw"Both dimensions must be greater than 0";this.width=h;this.height=b;var e=h>>5;0!=(h& +31)&&e++;this.rowSize=e;this.bits=Array(e*b);for(e=0;e>5)],d&31)&1)};this.set_Renamed=function(d,c){this.bits[c*this.rowSize+ +(d>>5)]|=1<<(d&31)};this.flip=function(d,c){this.bits[c*this.rowSize+(d>>5)]^=1<<(d&31)};this.clear=function(){for(var d=this.bits.length,c=0;cc||0>d)throw"Left and top must be nonnegative";if(1>b||1>e)throw"Height and width must be at least 1";e=d+e;b=c+b;if(b>this.height||e>this.width)throw"The region must fit inside the matrix";for(;c>5)]|=1<<(a&31)}}function G(a,b){this.numDataCodewords= +a;this.codewords=b;this.__defineGetter__("NumDataCodewords",function(){return this.numDataCodewords});this.__defineGetter__("Codewords",function(){return this.codewords})}function T(a){var b=a.Dimension;if(21>b||1!=(b&3))throw"Error BitMatrixParser";this.bitMatrix=a;this.parsedFormatInfo=this.parsedVersion=null;this.copyBit=function(e,d,c){return this.bitMatrix.get_Renamed(e,d)?c<<1|1:c<<1};this.readFormatInformation=function(){if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var e= +0,d=0;6>d;d++)e=this.copyBit(d,8,e);e=this.copyBit(7,8,e);e=this.copyBit(8,8,e);e=this.copyBit(8,7,e);for(d=5;0<=d;d--)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var c=this.bitMatrix.Dimension,e=0,b=c-8,d=c-1;d>=b;d--)e=this.copyBit(d,8,e);for(d=c-7;d>2;if(6>=d)return k.getVersionForNumber(d);for(var d=0,c=e-11,b=5;0<=b;b--)for(var a=e-9;a>=c;a--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;d=0;for(a=5;0<=a;a--)for(b=e-9;b>=c;b--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!= +this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;throw"Error readVersion";};this.readCodewords=function(){var b=this.readFormatInformation(),d=this.readVersion(),c=H.forReference(b.DataMask),b=this.bitMatrix.Dimension;c.unmaskBitMatrix(this.bitMatrix,b);for(var c=d.buildFunctionPattern(),a=!0,h=Array(d.TotalCodewords),m=0,f=0,g=0,k=b-1;0t;t++)c.get_Renamed(k-t,v)||(g++,f<<=1,this.bitMatrix.get_Renamed(k- +t,v)&&(f|=1),8==g&&(h[m++]=f,f=g=0));a^=1}if(m!=d.TotalCodewords)throw"Error readCodewords";return h}}function w(a,b){if(null==b||0==b.length)throw"System.ArgumentException";this.field=a;var e=b.length;if(1c.length){var b=d,d=c;c=b}for(var b=Array(c.length),e=c.length-d.length,h=0;hc)throw"System.ArgumentException";if(0==d)return this.field.Zero;for(var b=this.coefficients.length,e=Array(b+c),a=0;a=c.Degree&&!b.Zero;)var a=b.Degree-c.Degree, +h=this.field.multiply(b.getCoefficient(b.Degree),e),f=c.multiplyByMonomial(a,h),a=this.field.buildMonomial(a,h),d=d.addOrSubtract(a),b=b.addOrSubtract(f);return[d,b]}}function n(a){this.expTable=Array(256);this.logTable=Array(256);for(var b=1,e=0;256>e;e++)this.expTable[e]=b,b<<=1,256<=b&&(b^=a);for(e=0;255>e;e++)this.logTable[this.expTable[e]]=e;a=Array(1);a[0]=0;this.zero=new w(this,Array(a));a=Array(1);a[0]=1;this.one=new w(this,Array(a));this.__defineGetter__("Zero",function(){return this.zero}); +this.__defineGetter__("One",function(){return this.one});this.buildMonomial=function(d,c){if(0>d)throw"System.ArgumentException";if(0==c)return this.zero;for(var b=Array(d+1),e=0;e>b:(a>>b)+(2<<~b)}function U(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return this.x});this.__defineGetter__("Y",function(){return this.y});this.incrementCount=function(){this.count++}; +this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function V(a){this.bottomLeft=a[0];this.topLeft=a[1];this.topRight=a[2];this.__defineGetter__("BottomLeft",function(){return this.bottomLeft});this.__defineGetter__("TopLeft",function(){return this.topLeft});this.__defineGetter__("TopRight",function(){return this.topRight})}function S(){this.image=null;this.possibleCenters=[];this.hasSkipped= +!1;this.crossCheckStateCount=[0,0,0,0,0];this.resultPointCallback=null;this.__defineGetter__("CrossCheckStateCount",function(){this.crossCheckStateCount[0]=0;this.crossCheckStateCount[1]=0;this.crossCheckStateCount[2]=0;this.crossCheckStateCount[3]=0;this.crossCheckStateCount[4]=0;return this.crossCheckStateCount});this.foundPatternCross=function(a){for(var b=0,e=0;5>e;e++){var d=a[e];if(0==d)return!1;b+=d}if(7>b)return!1;b=Math.floor((b<m)return NaN;for(;0<=m&&!c[b+m*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[b+m*g.width]&&l[0]<=e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m=e)return NaN;for(;m=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=2*d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.crossCheckHorizontal=function(a,b,e,d){for(var c=this.image,h=g.width,l=this.CrossCheckStateCount,m=a;0<=m&&c[m+b*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[m+b*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[m+b*g.width]&&l[0]<= +e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m=e)return NaN;for(;m=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.handlePossibleCenter=function(a,b,e){var d=a[0]+a[1]+a[2]+a[3]+a[4];e=this.centerFromEnd(a,e);b=this.crossCheckVertical(b,Math.floor(e),a[2],d);if(!isNaN(b)&&(e=this.crossCheckHorizontal(Math.floor(e), +Math.floor(b),a[2],d),!isNaN(e))){a=d/7;for(var d=!1,c=this.possibleCenters.length,h=0;ha)throw"Couldn't find enough finder patterns (found "+a+")";if(3a&&this.possibleCenters.splice(d,1)}3d.count?-1:c.count=a)return 0;for(var b=null,e=0;e=K)if(null==b)b=d;else return this.hasSkipped=!0,Math.floor((Math.abs(b.X-d.X)-Math.abs(b.Y-d.Y))/2)}return 0};this.haveMultiplyConfirmedCenters=function(){for(var a,b=0,e=0,d=this.possibleCenters.length,c=0;c=K&&(b++,e+=a.EstimatedModuleSize); +if(3>b)return!1;for(var b=e/d,p=0,c=0;cl[2]&&(m+=b-l[2]-c,f=d-1));else{do f++;while(f=d||1>=d/this.estimatedModuleSize):!1}}function R(a,b,e,d,c,f,l){this.image=a;this.possibleCenters= +[];this.startX=b;this.startY=e;this.width=d;this.height=c;this.moduleSize=f;this.crossCheckStateCount=[0,0,0];this.resultPointCallback=l;this.centerFromEnd=function(c,d){return d-c[2]-c[1]/2};this.foundPatternCross=function(c){for(var d=this.moduleSize,b=d/2,a=0;3>a;a++)if(Math.abs(d-c[a])>=b)return!1;return!0};this.crossCheckVertical=function(c,d,b,a){var e=this.image,h=g.height,f=this.crossCheckStateCount;f[0]=0;f[1]=0;f[2]=0;for(var l=c;0<=l&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l--;if(0>l||f[1]>b)return NaN; +for(;0<=l&&!e[d+l*g.width]&&f[0]<=b;)f[0]++,l--;if(f[0]>b)return NaN;for(l=c+1;lb)return NaN;for(;lb||5*Math.abs(f[0]+f[1]+f[2]-a)>=2*a?NaN:this.foundPatternCross(f)?this.centerFromEnd(f,l):NaN};this.handlePossibleCenter=function(c,d,b){var a=c[0]+c[1]+c[2];b=this.centerFromEnd(c,b);d=this.crossCheckVertical(d,Math.floor(b),2*c[1],a);if(!isNaN(d)){c=(c[0]+c[1]+c[2])/3;for(var a=this.possibleCenters.length, +e=0;e>1),p=[0,0,0],k=0;k>1:-(k+1>>1));p[0]=0;p[1]=0;p[2]=0;for(var A=b;A=b?this.dataLengthMode=0:10<=b&&26>=b?this.dataLengthMode=1:27<= +b&&40>=b&&(this.dataLengthMode=2);this.getNextBits=function(b){var c,d;if(b>this.bitPointer-b+1;this.bitPointer-=b;return d}if(b>8-(b-(this.bitPointer+1));this.bitPointer-=b%8;0>this.bitPointer&&(this.bitPointer= +8+this.bitPointer);return d}if(b>8-(b-(this.bitPointer+1+8));this.bitPointer-=(b-8)%8;0>this.bitPointer&&(this.bitPointer=8+this.bitPointer);return c+e+d}return 0}; +this.NextMode=function(){return this.blockPointer>this.blocks.length-this.numErrorCorrectionCode-2?0:this.getNextBits(4)};this.getDataLength=function(b){for(var c=0;1!=b>>c;)c++;return this.getNextBits(g.sizeOfDataLengthInfo[this.dataLengthMode][c])};this.getRomanAndFigureString=function(b){var c="",d="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");do if(1c&&(d+="0"),10>c&&(d+="0"),b-=3):2==b?(c=this.getNextBits(7),10>c&&(d+="0"),b-=2):1==b&&(c=this.getNextBits(4),--b),d+=c;while(0=d+33088?d+33088:d+49472);b--}while(0< +b);return c};this.parseECIValue=function(){var b=0,c=this.getNextBits(8);0==(c&128)&&(b=c&127);128==(c&192)&&(b=this.getNextBits(8),b|=(c&63)<<8);192==(c&224)&&(b=this.getNextBits(8),b|=(c&31)<<16);return b};this.__defineGetter__("DataByte",function(){var b=[];do{var c=this.NextMode();if(0==c)if(0a)throw"Invalid data length: "+a;switch(c){case 1:c=this.getFigureString(a);for(var a=Array(c.length),e=0;ed||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}f=!0;for(m=b.length-2;0<=m&&f;m-=2){d=Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}},sampleGrid3:function(a,b,e){for(var d=new I(b),c=Array(b<<1),h=0;h>1)+.5,c[k+1]=m;e.transformPoints1(c);F.checkAndNudgePoints(a,c);try{for(k=0;k>1,h)}catch(q){throw"Error.checkAndNudgePoints";}}return d},sampleGridx:function(a,b,e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w){e=z.quadrilateralToQuadrilateral(e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w);return F.sampleGrid3(a,b,e)}};k.VERSION_DECODE_INFO=[31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154, +84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017];k.VERSIONS=[new k(1,[],new f(7,new a(1,19)),new f(10,new a(1,16)),new f(13,new a(1,13)),new f(17,new a(1,9))),new k(2,[6,18],new f(10,new a(1,34)),new f(16,new a(1,28)),new f(22,new a(1,22)),new f(28,new a(1,16))),new k(3,[6,22],new f(15,new a(1,55)),new f(26,new a(1,44)),new f(18,new a(2,17)),new f(22,new a(2,13))),new k(4,[6,26],new f(20,new a(1,80)),new f(18, +new a(2,32)),new f(26,new a(2,24)),new f(16,new a(4,9))),new k(5,[6,30],new f(26,new a(1,108)),new f(24,new a(2,43)),new f(18,new a(2,15),new a(2,16)),new f(22,new a(2,11),new a(2,12))),new k(6,[6,34],new f(18,new a(2,68)),new f(16,new a(4,27)),new f(24,new a(4,19)),new f(28,new a(4,15))),new k(7,[6,22,38],new f(20,new a(2,78)),new f(18,new a(4,31)),new f(18,new a(2,14),new a(4,15)),new f(26,new a(4,13),new a(1,14))),new k(8,[6,24,42],new f(24,new a(2,97)),new f(22,new a(2,38),new a(2,39)),new f(22, +new a(4,18),new a(2,19)),new f(26,new a(4,14),new a(2,15))),new k(9,[6,26,46],new f(30,new a(2,116)),new f(22,new a(3,36),new a(2,37)),new f(20,new a(4,16),new a(4,17)),new f(24,new a(4,12),new a(4,13))),new k(10,[6,28,50],new f(18,new a(2,68),new a(2,69)),new f(26,new a(4,43),new a(1,44)),new f(24,new a(6,19),new a(2,20)),new f(28,new a(6,15),new a(2,16))),new k(11,[6,30,54],new f(20,new a(4,81)),new f(30,new a(1,50),new a(4,51)),new f(28,new a(4,22),new a(4,23)),new f(24,new a(3,12),new a(8,13))), +new k(12,[6,32,58],new f(24,new a(2,92),new a(2,93)),new f(22,new a(6,36),new a(2,37)),new f(26,new a(4,20),new a(6,21)),new f(28,new a(7,14),new a(4,15))),new k(13,[6,34,62],new f(26,new a(4,107)),new f(22,new a(8,37),new a(1,38)),new f(24,new a(8,20),new a(4,21)),new f(22,new a(12,11),new a(4,12))),new k(14,[6,26,46,66],new f(30,new a(3,115),new a(1,116)),new f(24,new a(4,40),new a(5,41)),new f(20,new a(11,16),new a(5,17)),new f(24,new a(11,12),new a(5,13))),new k(15,[6,26,48,70],new f(22,new a(5, +87),new a(1,88)),new f(24,new a(5,41),new a(5,42)),new f(30,new a(5,24),new a(7,25)),new f(24,new a(11,12),new a(7,13))),new k(16,[6,26,50,74],new f(24,new a(5,98),new a(1,99)),new f(28,new a(7,45),new a(3,46)),new f(24,new a(15,19),new a(2,20)),new f(30,new a(3,15),new a(13,16))),new k(17,[6,30,54,78],new f(28,new a(1,107),new a(5,108)),new f(28,new a(10,46),new a(1,47)),new f(28,new a(1,22),new a(15,23)),new f(28,new a(2,14),new a(17,15))),new k(18,[6,30,56,82],new f(30,new a(5,120),new a(1,121)), +new f(26,new a(9,43),new a(4,44)),new f(28,new a(17,22),new a(1,23)),new f(28,new a(2,14),new a(19,15))),new k(19,[6,30,58,86],new f(28,new a(3,113),new a(4,114)),new f(26,new a(3,44),new a(11,45)),new f(26,new a(17,21),new a(4,22)),new f(26,new a(9,13),new a(16,14))),new k(20,[6,34,62,90],new f(28,new a(3,107),new a(5,108)),new f(26,new a(3,41),new a(13,42)),new f(30,new a(15,24),new a(5,25)),new f(28,new a(15,15),new a(10,16))),new k(21,[6,28,50,72,94],new f(28,new a(4,116),new a(4,117)),new f(26, +new a(17,42)),new f(28,new a(17,22),new a(6,23)),new f(30,new a(19,16),new a(6,17))),new k(22,[6,26,50,74,98],new f(28,new a(2,111),new a(7,112)),new f(28,new a(17,46)),new f(30,new a(7,24),new a(16,25)),new f(24,new a(34,13))),new k(23,[6,30,54,74,102],new f(30,new a(4,121),new a(5,122)),new f(28,new a(4,47),new a(14,48)),new f(30,new a(11,24),new a(14,25)),new f(30,new a(16,15),new a(14,16))),new k(24,[6,28,54,80,106],new f(30,new a(6,117),new a(4,118)),new f(28,new a(6,45),new a(14,46)),new f(30, +new a(11,24),new a(16,25)),new f(30,new a(30,16),new a(2,17))),new k(25,[6,32,58,84,110],new f(26,new a(8,106),new a(4,107)),new f(28,new a(8,47),new a(13,48)),new f(30,new a(7,24),new a(22,25)),new f(30,new a(22,15),new a(13,16))),new k(26,[6,30,58,86,114],new f(28,new a(10,114),new a(2,115)),new f(28,new a(19,46),new a(4,47)),new f(28,new a(28,22),new a(6,23)),new f(30,new a(33,16),new a(4,17))),new k(27,[6,34,62,90,118],new f(30,new a(8,122),new a(4,123)),new f(28,new a(22,45),new a(3,46)),new f(30, +new a(8,23),new a(26,24)),new f(30,new a(12,15),new a(28,16))),new k(28,[6,26,50,74,98,122],new f(30,new a(3,117),new a(10,118)),new f(28,new a(3,45),new a(23,46)),new f(30,new a(4,24),new a(31,25)),new f(30,new a(11,15),new a(31,16))),new k(29,[6,30,54,78,102,126],new f(30,new a(7,116),new a(7,117)),new f(28,new a(21,45),new a(7,46)),new f(30,new a(1,23),new a(37,24)),new f(30,new a(19,15),new a(26,16))),new k(30,[6,26,52,78,104,130],new f(30,new a(5,115),new a(10,116)),new f(28,new a(19,47),new a(10, +48)),new f(30,new a(15,24),new a(25,25)),new f(30,new a(23,15),new a(25,16))),new k(31,[6,30,56,82,108,134],new f(30,new a(13,115),new a(3,116)),new f(28,new a(2,46),new a(29,47)),new f(30,new a(42,24),new a(1,25)),new f(30,new a(23,15),new a(28,16))),new k(32,[6,34,60,86,112,138],new f(30,new a(17,115)),new f(28,new a(10,46),new a(23,47)),new f(30,new a(10,24),new a(35,25)),new f(30,new a(19,15),new a(35,16))),new k(33,[6,30,58,86,114,142],new f(30,new a(17,115),new a(1,116)),new f(28,new a(14,46), +new a(21,47)),new f(30,new a(29,24),new a(19,25)),new f(30,new a(11,15),new a(46,16))),new k(34,[6,34,62,90,118,146],new f(30,new a(13,115),new a(6,116)),new f(28,new a(14,46),new a(23,47)),new f(30,new a(44,24),new a(7,25)),new f(30,new a(59,16),new a(1,17))),new k(35,[6,30,54,78,102,126,150],new f(30,new a(12,121),new a(7,122)),new f(28,new a(12,47),new a(26,48)),new f(30,new a(39,24),new a(14,25)),new f(30,new a(22,15),new a(41,16))),new k(36,[6,24,50,76,102,128,154],new f(30,new a(6,121),new a(14, +122)),new f(28,new a(6,47),new a(34,48)),new f(30,new a(46,24),new a(10,25)),new f(30,new a(2,15),new a(64,16))),new k(37,[6,28,54,80,106,132,158],new f(30,new a(17,122),new a(4,123)),new f(28,new a(29,46),new a(14,47)),new f(30,new a(49,24),new a(10,25)),new f(30,new a(24,15),new a(46,16))),new k(38,[6,32,58,84,110,136,162],new f(30,new a(4,122),new a(18,123)),new f(28,new a(13,46),new a(32,47)),new f(30,new a(48,24),new a(14,25)),new f(30,new a(42,15),new a(32,16))),new k(39,[6,26,54,82,110,138, +166],new f(30,new a(20,117),new a(4,118)),new f(28,new a(40,47),new a(7,48)),new f(30,new a(43,24),new a(22,25)),new f(30,new a(10,15),new a(67,16))),new k(40,[6,30,58,86,114,142,170],new f(30,new a(19,118),new a(6,119)),new f(28,new a(18,47),new a(31,48)),new f(30,new a(34,24),new a(34,25)),new f(30,new a(20,15),new a(61,16)))];k.getVersionForNumber=function(a){if(1>a||40>2)}catch(b){throw"Error getVersionForNumber";}};k.decodeVersionInformation=function(a){for(var b=4294967295,e=0,d=0;d=b?this.getVersionForNumber(e):null};z.quadrilateralToQuadrilateral=function(a,b,e,d,c,f,g,m,k,q,n,x,v,t,r,u){a=this.quadrilateralToSquare(a,b,e,d,c,f,g,m);return this.squareToQuadrilateral(k, +q,n,x,v,t,r,u).times(a)};z.squareToQuadrilateral=function(a,b,e,d,c,f,g,m){var h=m-f,l=b-d+f-m;if(0==h&&0==l)return new z(e-a,c-e,a,d-b,f-d,b,0,0,1);var p=e-c,k=g-c;c=a-e+c-g;f=d-f;var n=p*h-k*f,h=(c*h-k*l)/n,l=(p*l-c*f)/n;return new z(e-a+h*e,g-a+l*g,a,d-b+h*d,m-b+l*m,b,h,l,1)};z.quadrilateralToSquare=function(a,b,e,d,c,f,g,m){return this.squareToQuadrilateral(a,b,e,d,c,f,g,m).buildAdjoint()};var N=[[21522,0],[20773,1],[24188,2],[23371,3],[17913,4],[16590,5],[20375,6],[19104,7],[30660,8],[29427, +9],[32170,10],[30877,11],[26159,12],[25368,13],[27713,14],[26998,15],[5769,16],[5054,17],[7399,18],[6608,19],[1890,20],[597,21],[3340,22],[2107,23],[13663,24],[12392,25],[16177,26],[14854,27],[9396,28],[8579,29],[11994,30],[11245,31]],B=[0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4];r.numBitsDiffering=function(a,b){a^=b;return B[a&15]+B[u(a,4)&15]+B[u(a,8)&15]+B[u(a,12)&15]+B[u(a,16)&15]+B[u(a,20)&15]+B[u(a,24)&15]+B[u(a,28)&15]};r.decodeFormatInformation=function(a){var b=r.doDecodeFormatInformation(a);return null!= +b?b:r.doDecodeFormatInformation(a^21522)};r.doDecodeFormatInformation=function(a){for(var b=4294967295,e=0,d=0;d=b?new r(e):null};C.forBits=function(a){if(0>a||a>=O.length)throw"ArgumentException";return O[a]};var Y=new C(0,1,"L"),Z=new C(1,0,"M"),aa=new C(2,3,"Q"),ba=new C(3,2,"H"),O=[Z,Y,ba,aa];G.getDataBlocks=function(a,b,e){if(a.length!=b.TotalCodewords)throw"ArgumentException"; +var d=b.getECBlocksForLevel(e);e=0;var c=d.getECBlocks();for(b=0;ba||7h)throw"ReedSolomonException Bad error location";a[h]=n.addOrSubtract(a[h],c[f])}};this.runEuclideanAlgorithm=function(a,e,d){if(a.Degree=Math.floor(d/2);){var k=a,q=b,n=h;a=e;b=f;h=g;if(a.Zero)throw"r_{i-1} was zero";e=k;g=this.field.Zero;f=a.getCoefficient(a.Degree); +for(f=this.field.inverse(f);e.Degree>=a.Degree&&!e.Zero;){var k=e.Degree-a.Degree,r=this.field.multiply(e.getCoefficient(e.Degree),f),g=g.addOrSubtract(this.field.buildMonomial(k,r));e=e.addOrSubtract(a.multiplyByMonomial(k,r))}f=g.multiply1(b).addOrSubtract(q);g=g.multiply1(h).addOrSubtract(n)}d=g.getCoefficient(0);if(0==d)throw"ReedSolomonException sigmaTilde(0) was zero";d=this.field.inverse(d);a=g.multiply2(d);d=e.multiply2(d);return[a,d]};this.findErrorLocations=function(a){var b=a.Degree;if(1== +b)return Array(a.getCoefficient(1));for(var d=Array(b),c=0,f=1;256>f&&cg.maxImgSize&&(f=d.width/d.height,e=Math.sqrt(g.maxImgSize/f),f*=e);a.width=f;a.height=e;b.drawImage(d,0,0,a.width,a.height);g.width=a.width;g.height=a.height;try{g.imagedata= +b.getImageData(0,0,a.width,a.height)}catch(y){g.result=Error("Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!");null!=g.callback&&g.callback(g.result);return}try{g.result=g.process(b)}catch(y){console.log(y),g.result=Error("error decoding QR Code")}null!=g.callback&&g.callback(g.result)};d.onerror=function(){null!=g.callback&&g.callback(Error("Failed to load the image"))};d.src=a},isUrl:function(a){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(a)}, +decode_url:function(a){var b="";try{b=escape(a)}catch(e){console.log(e),b=a}a="";try{a=decodeURIComponent(b)}catch(e){console.log(e),a=b}return a},decode_utf8:function(a){return g.isUrl(a)?g.decode_url(a):a},process:function(a){var b=(new Date).getTime(),e=g.grayScaleToBitmap(g.grayscale());if(g.debug){for(var d=0;dc;c++){d[c]=Array(4);for(var f=0;4>f;f++)d[c][f]=[0,0]}for(c=0;4>c;c++)for(f= +0;4>f;f++){d[f][c][0]=255;for(var h=0;hd[f][c][1]&&(d[f][c][1]=k)}}a=Array(4);for(b=0;4>b;b++)a[b]=Array(4);for(c=0;4>c;c++)for(f=0;4>f;f++)a[f][c]=Math.floor((d[f][c][0]+d[f][c][1])/2);return a},grayScaleToBitmap:function(a){for(var b=g.getMiddleBrightnessPerArea(a),e=b.length,d=Math.floor(g.width/e),c=Math.floor(g.height/e),f=new ArrayBuffer(g.width*g.height),f=new Uint8Array(f),h=0;h=e&&d>=c?(d=a[0],e=a[1],c=a[2]):c>=d&&c>=e?(d=a[1], +e=a[0],c=a[2]):(d=a[2],e=a[0],c=a[1]);if(0>function(a,b,c){var d=b.x;b=b.y;return(c.x-d)*(a.y-b)-(c.y-b)*(a.x-d)}(e,d,c))var f=e,e=c,c=f;a[0]=e;a[1]=d;a[2]=c};return g}(); diff --git a/apps/authentiwatch/screenshot.png b/apps/authentiwatch/screenshot.png new file mode 100644 index 000000000..2a7bcbd9a Binary files /dev/null and b/apps/authentiwatch/screenshot.png differ diff --git a/apps/binwatch/ChangeLog b/apps/binwatch/ChangeLog index f916cd6cf..bf4f5075a 100644 --- a/apps/binwatch/ChangeLog +++ b/apps/binwatch/ChangeLog @@ -1,2 +1,3 @@ 0.01: start of development 0.02: first running version for BangleJs2 +0.03: corrected icon, added screen shot, extended description diff --git a/apps/binwatch/README.md b/apps/binwatch/README.md index d9da4968b..52e868e21 100644 --- a/apps/binwatch/README.md +++ b/apps/binwatch/README.md @@ -1,10 +1,47 @@ # TheBinWatch Binary watch to train Your brain -Inspired by the 80's LCD wrist watch from RALtec +Inspired by the LCD wrist watch from TecRAL from 1989 + +![](screenshot.png) +![](screenshot2.png) + ## Usage - swipe to left or right to change displayed text (date, time, ...) - currently only available for BangeJs2 - Widgets will not be shown -- If bluetooth connection is not established an icon will show up \ No newline at end of file +- If bluetooth connection is not established an icon will show up + +## How it works +Binary means that every digit can represent 2 states: 0 or 1, displayed by a black bar. + +The principle is the same like in out well known and daily used decimal system with values from 0 to 9: + +We start from the most right position with the least significant bit (binary digit) which can have the value 0 or 1 +The 2nd bit from the right can have the value 0 or 2 (sum of all bits to the right set to 1 plus 1). +This principle is valid for all the remaining bits. + +Mathematically spoken: the value of a digit is the base number of the system (10 for decimal or 2 for binary) +to the power of the position (from the right, starting with 0). +That means in numbers: 2^5 = 32, 2^4 = 16, 2^3 = 8, 2^2 = 4, 2^1 = 2, 2^0 = 1 + +The upper row represents the hours with 4 bit (2^4 = 16 possible values in total, 12 are used: 1 to 12), + the 2nd row represents the minutes with 6 bit (2^6 = 64 possible values in total, 60 are used: 0 to 59). +Same holds for the thrid row: 0-59 seconds + +To read the values of a row we summ up the vaules of set bits (black bars). +E.g. the picture above, 3rd row (seconds): +101001 +is 1 * 32 + 0 * 16 + 1 * 8 + 0 * 4 + 0 * 2 + 1 * 1 +is (only the '1' bit): 32 + 8 + 1 = 41 + +for the minutes we do the same: 32 + 1 = 33 +and the hours: 8 + 2 = 10 + +So the time is 10:33:41 (that's all) + +## TRAIN YOUR BRAIN + +Remark: more infos about the original watch including manual can be found here: +https://timeartpiece.com/watches/tech-ral-binary diff --git a/apps/binwatch/app-icon.js b/apps/binwatch/app-icon.js index 31d0a4cca..10d7e84e8 100644 --- a/apps/binwatch/app-icon.js +++ b/apps/binwatch/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("2GwwcCBQ0JkmSpICuoBMNIP4ABH14CCpBAMgRB/IOlJIJkSIOcgIP5BNH2ICDIJ3/AFvkIJsEIOuAIJKSCk4jQ7dt2wCJt4dP/hBc4EAgIOCIJl8EgPyv5BPyBAIgJBCn4GBg4cG/wKBhJBC/ZBLsATByRBM/5BCyRBM/5BMvMkIJ3gDoOSp5BM+RBLhJBCXIIABj4bF+AJBDoOfA4JBLCQMEMoRBPoBBHBZCnFwEAgfJIIftIJPfDYM8IJ3+ILkCCIOTIJnYDYKnCn5BPpBAGF4WSuEHCgRBF/gRBjxBE/pBJcYMBIJ//U4IRBILDjDBYJBJ25BBh4vCk5BXiRxC+BBJ8ARBDQIdBp4JBQZISBhJBQ/IRCkBBFF4WfIJkHII32II++EgN5F4UkHg/8JohBYCAMEIIdJIJWwCYIRDEwJBGkkn/1k85BDkhAEF4f/IJP4CIM8II3+II/wgEDeoZBH/MJQIPJyV/8hBZFgYCBn5BJwEAgSDEyZBF5DDB+f5yUfIIYZBAAQaCKQJBJ4EAgJBH/5BGtgkBiRBK/1CGAPyvhBB/gRCyBBUh5BFCgJBHvgkB+RBEXIJBEJwKDB8mE55BHOIZuBIJH+CIMJIIraB//7IItgCYIRFIIvyiVP8//kkk5//CIZBCF4YVBIJd5IJH/IIvggEHII1PIIfwAwX5kMkzJKBIKnwCIIsFAQOfII4SBghBGFIRBDAwPJGwJBFoAvELIRBIwEAgfJIJPtIIffEgM8IJ0mpAMBIIP+CIVIIKUCQY+TII3YgEB8hBHn5BFmVOIJIvDXgZBG/hSBiRBK/pBD4BBBCIxBIGQKoBIILLBCIQvEIJrdDAQoOBIIe3IIMPIJEnIIlMyfz/JBDAgJBFNYRBI8BBBFg7dEQYYSBhJBIkgmC/0SsmH+V/knPIIsgCgWfIJkHIJn2IIO+IIN5IJsTkknQYRBC/4UGIJYtBghBJpJBE2ATBCJIsD/nJkLMB5MmfYRBHBIRBH/AtBnhBM/xBBwEAgRBPhMn/1JmY2D8hBSgIUGAQk/IIVvIIMeIJWTE4RBC+VJXIZBGSIJBJ4BBBFhJBD//btiWBiRBOHAMnyVkA4aOBIJQnBAARBCh5BLDQXbvgWBOAIUKfwf/LggIFIJt+AQMJIJbgC/dgCYIRLyVPHQoAGIJP+IAcDDhgAkTwhBEAG5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5Bs+EAgFJAoP27dt2wCJB4P8z4DBCJdt34PB5ApBh5BTwEAgfJAoP+IJffIIvtIJYpC5PAIK8CpM/IKHkyZBQ/1JQYMBIKX8CwMSIIX/IJYWCkhBC/pBKt41DnBBXBwILCIJW3IIeSv5BMBoI1CkArBNYRBP8AVBBYMkA4P7IJn5EANPAoJBNGQQrBg5BTg5BE/5BJ35BH+xBJEAQmCFgRBRKwMEDQWfIJ3JEARBO/gmCwAtBIKH4CYM8IIvtIJAWCIIv+IJHfBgPkEwWcIKoLCkmTIJv+EAc/IJQpCIIeQFoMfIJ/AgEBIIeSv///pBHt4gGIIP/IJZoEYwJBSh5BG/5BHBQQgHII+3II2SQYMDIJ3+CQMJDQlPIJgRDAQIHB/ZBJ/ImEoAvBDwRBL+ARBvJBH+xBGCwRBH/5BG35BHpxBTFggCBIJf8IIufIJfJEwlIF4MPIJuAa4IaFIIX+IIvfBIPkIJHtIIocCNA3AIKMCDQ0/II4VCII2TII9vIJKDBgJBM/gQBiQaGBwRBICIpBD/pBHGQgCCnBBRDQ4OC/ZBD25BJyV/IIwHBIJEgGIKtBIJXgB4IsGAQJBJ/JBHp4LBII4mIGIMHIJYOCIJX/IIe/IJv2IIYaCExB0BIJ0EDRGfIJHJII9JIJH8ExGAGYJBK/ANBFhBBD9pBCAoP+CI5BD/xBC74GB8gmIzhBOgIaJyZBEt5BMn5BEGIQmJyBBBj5BJ4BBBQZOSv///pBEDogCFEYRBFExOTYwMDIJcPIJn/IIIECIJv7tu3IJmSQYJBJ/wMBhIaKp5BGCJICBII35ExVAGoIkBII3wBYN5IJv27ZvCIJv/tu/AYPJExVOIJosKAQJBF/hBLz5BCIoRBLpA1Bh5BHwEAgRBO/3fAYPkIJ3tCwQmM4BBZn5tCIJ2TB4PvIJ6DBgJBHAHRB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5BmwEAgO27dtARIZCkASBCJfbt4SB+YCB/omM4AjBIJMDDRe3IIUngEAjZBLv5uCAYJBM25BBh5BH+AuBmxBN/MkCQMDIJ2Sp4DBQZgiBIJH+BYN2DRW/IIfggEHIJaWCIIf2ExW+GoJXBIJMGIJvJkjZBgBBN/gpBIJuwIJX/aIMADRQPB/wXBzgSB9pBJ74TB8hBD/wmKMYMDCAJBJgJBPyBBBhpBJYgRBCn5BLt5BBj5BJ/AuBjYaJC4oSBgJBMFIRBB/5BJtggBIJs7DRDcBC4lwUgJBI25BFFIRBJvgzBCoRBH/4NBgZBLCgIXBoATBmxBK/IpCkgGB/YmIsBBN8EAg4aIBwRBDpwhBuxBH35BI/4mIDwMHIJsAIJX8IIdICQMGIJXJIIefIJPfIJ38B4MNDQ4NB8hBDpISBhxBHCQP+CIZBD9omG7AeBn5BOjpBPnATBII1vII+TIJP4gEBG4RBJ/+ACAIaGBgQsDAQMgIIMbIJApEAQN///9Ew3AIJ/wgEDDQu3IJEnIIM7IIo3BIJP/EwxBBh5BPgE2II/5IIskCQMDIJARFyVPII+2DgJBO/wRBuwaE34LB5JBG8EAg5BFCQP8IJP2EwmwF4JXCIJ0GIJ+ACYJBE75BJpJBHWYRBO/7XBgAaEJgQsGkmQCQPtII3kIJP+EwhdBgY2EIJkBDQdvIJWTEwMNIIYdCIJE/IItvDQMfIJ/4OAMbIIoUEAQgSBgJBGCI4sDIIdsDQJBQ/4TBnYaCbgRBJuCqBIIW3IJ37EwV8FoI1FIJsDIIosHAQNACYM2IIn5IJEkIItgIKfgCgIaCA4P8IJNOCQN2IIO/CYPJIJf/EwQYBg5BU9pBOpASBgxBBDYRBKz5BD74YBn5BR/gVBhoaBA4PkIJNJCQMAIIf+CJJBDNAPYIK8d2wHCIJc4gEB7dvIJuTIIf4C4I0FIJn/wAWBIIYsJAQMgKoMbIIQmEAQ9///923AIKvwgED25BOk5BBnYxBIJ//2wWBh40GEwpBIgE3AoP5IJckCQMDGIQRLyVPB4O+IKwAz/hWFIP5B88hBFz4SK8EAn4HE4EAgJBjHwYCCyYSKjkAg///xEB/0AAAJKFABXwCYMPIKUSIJsDwEAFII7CQYnxSol/ILP5IIUgIIWSEZAABgFwgEfwBBBwIKC45KECIIdG4H8HwQREIJ0CIJ1+gCGBn/8QATIBF4LRB//4ILfJIISYBIIVPIJV/4BBp/w7CpBBR/BBwhKJCIJYDBINHyIIVAIIoXJIOcBIKAmBIIYyBIIgREIKw4BHYJABIIknChHjAwuPc4ovDCIxBS/hBGgBBMADfwFYJECIJuQIIcEBASDPABUfILHkHAWAIKD1DVQP8gK2DBAMHAoRBHYqJBIgAICz5BLwBBE/0AIL7+CkhAEIIeTIK4FBIMcSILT7BDI5BQ/JBCkBBFgRBByQ4CIKmAZARBW5JBCIApBb/kAGRBBbgBBCp5BM/+BBQXHIIXgJQRBW/w1CpBBKpJBKEwfAgA7CIIMAGoRBjhJBJgYbDEwLCBAAIFBQYTdHAAfwCYJQJ//yIIVAIJbgKOIiDFCZhBagJBRVAoUTAA4yBGoJAHAAJBCk4saACf8IIWQIP5BLggOCIN3kGQWAIJufIPkABwQCyIBUAiRBzkBB/IJsCIOZALIP4ADIOVIIJsJIONAHQwA==")) +require("heatshrink").decompress(atob("mEwwcCgEBkmSpICKCwQRRhMn/4AK+VACIU4A4PAz+27dt20ECI1IgEDCIOT+wRB2EkCIX+BwMCpE/8f+gmSvwRB2Mkz///v/5IRBpwRHwIRC5PzCIMSCIXwMQNP7dshMkyf/p+G/MgiV+CIPxCJFM8gRByf+CIIvBRIP7sCMCv/h8//C4P+g6ABCIdiCIVP/M///kFIPAj6iLCIYAOCPH4ibUC2zABdgW/8ARFUgILB2/8fwf/kB3BPobUD3/kz4pCTwMDCIrCBCIWTCINv/IREfAVJDoYpCv/JkmAv4RCYQYRM+ARCn4vCHYX+bQOQh4RBfAYRJyUBCI3/F4IFB/4RGdP4RHwDmC7/gmzaC//tbQWBR4UbfAWQgzIDfwVsR4QRCfAIRM/0DCIWSgDaDz4RBsDXDCIIdByVAfAb+CCIf/4AREjYRFgZ9D/D4DpEDfAT+Cj4REhoRJ7ARE/8PfAVJgbmDp/YWZHgv6zIkkSBYWB44sB/4CB/AREkESp4EBx4RBx0/CIPACAf5kECCIQAHPQIAB5MAgVJEYs4AwIjECIMACI0ACIv+pARCn5rDvwFDGoQRDhILDABHyoARBgKeCARQQBCKIA==")) \ No newline at end of file diff --git a/apps/binwatch/app.png b/apps/binwatch/app.png index ebab4670e..e1a0c88ff 100644 Binary files a/apps/binwatch/app.png and b/apps/binwatch/app.png differ diff --git a/apps/binwatch/screenshot.png b/apps/binwatch/screenshot.png new file mode 100644 index 000000000..ebab4670e Binary files /dev/null and b/apps/binwatch/screenshot.png differ diff --git a/apps/binwatch/screenshot2.png b/apps/binwatch/screenshot2.png new file mode 100644 index 000000000..fa171d253 Binary files /dev/null and b/apps/binwatch/screenshot2.png differ diff --git a/apps/chargeanim/ChangeLog b/apps/chargeanim/ChangeLog index 5560f00bc..a7262b0c9 100644 --- a/apps/chargeanim/ChangeLog +++ b/apps/chargeanim/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Bangle.js 2 compatibility diff --git a/apps/chargeanim/app.js b/apps/chargeanim/app.js index c2702337a..68d0cdff5 100644 --- a/apps/chargeanim/app.js +++ b/apps/chargeanim/app.js @@ -1,8 +1,12 @@ +g.setBgColor(0, 0, 0); g.clear().flip(); -var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA==")); -var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI")); +var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa")); +var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA==")); - var W=240,H=240; +var W=g.getWidth(),H=g.getHeight(); +var b2v = (W != 240)?-1:1; +var b2rot = (W != 240)?Math.PI:0; +var b2scale = W/240.0; var bubbles = []; for (var i=0;i<10;i++) { bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()}); @@ -12,12 +16,16 @@ function anim() { /* we don't use any kind of buffering here. Just draw one image at a time (image contains a background) too, and there is minimal flicker. */ - var mx = 120, my = 120; + var mx = W/2.0, my = H/2.0; bubbles.forEach(f=>{ - f.y-=f.v;if (f.y<-24) f.y=H+8; - g.drawImage(imgbubble,f.y,f.x,{scale:f.s}); + f.y-=f.v * b2v; + if (f.y<-24) + f.y=H+8; + else if (f.y > (H+8)) + f.y=0; + g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot}); }); - g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2}); + g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot}); g.flip(); } diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog index d2bfbd4fa..4bb7838ac 100644 --- a/apps/compass/ChangeLog +++ b/apps/compass/ChangeLog @@ -2,3 +2,4 @@ 0.02: Show text if uncalibrated 0.03: Eliminate flickering 0.04: Fix for Bangle.js 2 and themes +0.05: Fix bearing not clearing correctly (visible in single or double digit bearings) diff --git a/apps/compass/compass.js b/apps/compass/compass.js index d26081dd5..65ad83c4f 100644 --- a/apps/compass/compass.js +++ b/apps/compass/compass.js @@ -48,7 +48,7 @@ Bangle.on('mag', function(m) { } g.setFontAlign(0,0).setFont("6x8",3); var y = 36; - g.clearRect(M-40,y,M+40,y+24); + g.clearRect(M-40,24,M+40,48); g.drawString(Math.round(m.heading),M,y,true); } diff --git a/apps/cubescramble/ChangeLog b/apps/cubescramble/ChangeLog index 078be395e..6de5b7211 100644 --- a/apps/cubescramble/ChangeLog +++ b/apps/cubescramble/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial Release 0.02: Replace icon with one found on https://icons8.com +0.03: Re-render icon fixing display in settings diff --git a/apps/cubescramble/cube-scramble-icon.js b/apps/cubescramble/cube-scramble-icon.js index 686d47068..d22d6d33b 100644 --- a/apps/cubescramble/cube-scramble-icon.js +++ b/apps/cubescramble/cube-scramble-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("AH4A6iIAQCwkBC6MQC4kYxAACwMT/4ACmMe9wAC6IXLj4XD+IXE8IX/C9/zR4oXOmYABC6UTCwQXZjrXKf5IAHC713AAURindAAVBiatDmIXFi4XDuMdC4fRYooX/5nBC6Xc5gABC6UcCwQXF+aPMC471DC6MTCwQXHa4V2szXBC4bXBC5YAQC7se9wAC6MYxAACwJTCAAIXL8IXFQYoX/C/4XtjrXNu1ma4z/JAA4XEgAXRCwgA/AGo")) +require("heatshrink").decompress(atob("mEwwhC/AHcRACAWEgIXRiAXEjGIAAWBif/AAUxj3uAAXRC5cfC4fxC4nhC/4Xv+aPFC50zAAIXSiYWCC7Mda5T/JAA4Xeu4ACiMU7oACoMTVocxC4sXC4dxjoXD6LFFC//M4IXS7nMAAIXSjgWCC4vzR5gXHeoYXRiYWCC47XCu1ma4IXDa4IXLACAXdj3uAAXRjGIAAWBKYQABC5fhC4qDFC/4X/C9sda5t2szXGf5IAHC4kAC6IWEAH4A1")) diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 985321e91..c3102b4b9 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -2,3 +2,4 @@ 0.02: Multiple pages 0.03: cycle thru pages 0.04: reset to clock after 2 mins of inactivity +0.05: add Bangle 2 version diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md index 70f7ff931..ba2301d91 100644 --- a/apps/dtlaunch/README.md +++ b/apps/dtlaunch/README.md @@ -3,7 +3,7 @@ ![](screenshot.jpg) In the picture above, the Settings app is selected. -## Controls +## Controls- Bangle **BTN1** - move backward through app icons on a page @@ -13,4 +13,12 @@ In the picture above, the Settings app is selected. **Swipe Left** - move to next page of app icons +**Swipe Right** - move to previous page of app icons + +## Controls- Bangle 2 + +**Touch** - icon to select, scond touch launches app + +**Swipe Left** - move to next page of app icons + **Swipe Right** - move to previous page of app icons \ No newline at end of file diff --git a/apps/dtlaunch/app.js b/apps/dtlaunch/app-b1.js similarity index 100% rename from apps/dtlaunch/app.js rename to apps/dtlaunch/app-b1.js diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js new file mode 100644 index 000000000..674fe3677 --- /dev/null +++ b/apps/dtlaunch/app-b2.js @@ -0,0 +1,105 @@ +/* Desktop launcher +* +*/ + +var s = require("Storage"); +var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type)); +apps.sort((a,b)=>{ + var n=(0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; +}); +apps.forEach(app=>{ + if (app.icon) + app.icon = s.read(app.icon); // should just be a link to a memory area + }); + +var Napps = apps.length; +var Npages = Math.ceil(Napps/4); +var maxPage = Npages-1; +var selected = -1; +var oldselected = -1; +var page = 0; +const XOFF = 24; +const YOFF = 30; + +function draw_icon(p,n,selected) { + var x = (n%2)*72+XOFF; + var y = n>1?72+YOFF:YOFF; + (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+10,y+2,x+60,y+52); + g.clearRect(x+12,y+4,x+59,y+51); + g.setColor(g.theme.fg); + try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} + g.setFontAlign(0,-1,0).setFont("6x8",1); + var txt = apps[p*4+n].name.split(" "); + for (var i = 0; i < txt.length; i++) { + txt[i] = txt[i].trim(); + g.drawString(txt[i],x+36,y+54+i*8); + } +} + +function drawPage(p){ + g.reset(); + g.clearRect(0,24,175,175); + var O = 88+YOFF/2-12*(Npages/2); + for (var j=0;j{ + selected = 0; + oldselected=-1; + if (dir<0){ + ++page; if (page>maxPage) page=0; + drawPage(page); + } else { + --page; if (page<0) page=maxPage; + drawPage(page); + } +}); + +function isTouched(p,n){ + if (n<0 || n>3) return false; + var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; + var x2 = x1+71; var y2 = y1+81; + return (p.x>x1 && p.y>y1 && p.x{ + var i; + for (i=0;i<4;i++){ + if((page*4+i)=0) { + if (selected!=i){ + draw_icon(page,selected,false); + } else { + load(apps[page*4+i].src); + } + } + selected=i; + break; + } + } + } + if ((i==4 || (page*4+i)>Napps) && selected>=0) { + draw_icon(page,selected,false); + selected=-1; + } +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +drawPage(0); diff --git a/apps/dtlaunch/shot1.png b/apps/dtlaunch/shot1.png new file mode 100644 index 000000000..e6a9bcd3a Binary files /dev/null and b/apps/dtlaunch/shot1.png differ diff --git a/apps/dtlaunch/shot2.png b/apps/dtlaunch/shot2.png new file mode 100644 index 000000000..4c0c33c91 Binary files /dev/null and b/apps/dtlaunch/shot2.png differ diff --git a/apps/dtlaunch/shot3.png b/apps/dtlaunch/shot3.png new file mode 100644 index 000000000..1ffdf8090 Binary files /dev/null and b/apps/dtlaunch/shot3.png differ diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index ecbca5fb6..42ef60ab2 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -2,4 +2,5 @@ 0.02: Increase text brightness, improve controls, (try to) reduce memory usage 0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity 0.04: Setting to disable touch controls, minor bugfix -0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker \ No newline at end of file +0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker +0.06: Bangle.js 2 support diff --git a/apps/gbmusic/README.md b/apps/gbmusic/README.md index 4bad9b8c8..5d06164c2 100644 --- a/apps/gbmusic/README.md +++ b/apps/gbmusic/README.md @@ -3,7 +3,9 @@ If you have an Android phone with Gadgetbridge, this app allows you to view and control music playback. -![Screenshot: playing](screenshot.png) ![Screenshot: paused](screenshot_2.png) +| Bangle.js 1 | Bangle.js 2 | +|:-------------------------------------------|:-------------------------------------------| +| ![Screenshot: Bangle 1](screenshot_v1.png) | ![Screenshot: Bangle 2](screenshot_v2.png) | Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/). @@ -23,25 +25,27 @@ Automatically load the app when you play music and close when the music stops. (If the app opened automatically, it closes after music has been paused for 5 minutes.) **Simple button**: -Disable double/triple pressing Button 2: always simply toggle play/pause. +Disable double/triple pressing Middle Button: always simply toggle play/pause. (For music players which handle multiple button presses themselves.) ## Controls ### Buttons -* Button 1: Volume up -* Button 2: - - Single press: toggle play/pause - - Double press: next song - - Triple press: previous song +* Button 1 (*Bangle.js 1*): Volume up +* Middle Button: + - Single press: Toggle play/pause + - Double press: Next song + - Triple press: Previous song - Long-press: open application launcher -* Button 3: Volume down +* Button 3 (*Bangle.js 1*): Volume down ### Touch -* Left: pause/previous song -* Right: next song/resume -* Center: toggle play/pause -* Swipe: next/previous song +* Left: Pause/previous song +* Right: Next song/resume +* Center: Toggle play/pause +* Swipe left/right: Next/previous song +* Swipe up/down (*Bangle.js 2*): Volume up/down + ## Creator diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 5f95868bb..328b4a1ae 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -4,77 +4,9 @@ **/ let auto = false; // auto close if opened automatically let stat = ""; -let info = { - artist: "", - album: "", - track: "", - n: 0, - c: 0, -}; const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms) const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms) - -/////////////////////// -// Self-repeating timeouts -/////////////////////// - -// Clock -let tock = -1; -function tick() { - if (!Bangle.isLCDOn()) { - return; - } - const now = new Date; - if (now.getHours()*60+now.getMinutes()!==tock) { - drawDateTime(); - tock = now.getHours()*60+now.getMinutes(); - } - setTimeout(tick, 1000); // we only show minute precision anyway -} - -// Fade out while paused and auto closing -let fade = null; -function fadeOut() { - if (!Bangle.isLCDOn() || !fade) { - return; - } - drawMusic(false); // don't clear: draw over existing text to prevent flicker - setTimeout(fadeOut, 500); -} -function brightness() { - if (!fade) { - return 1; - } - return Math.max(0, 1-((Date.now()-fade)/POUT)); -} - -// Scroll long track names -// use an interval to get smooth movement -let offset = null, // scroll Offset: null = no scrolling - iScroll; -function scroll() { - offset += 10; - drawScroller(); -} -function scrollStart() { - if (offset!==null) { - return; // already started - } - offset = 0; - if (Bangle.isLCDOn()) { - if (!iScroll) { - iScroll = setInterval(scroll, 200); - } - drawScroller(); - } -} -function scrollStop() { - if (iScroll) { - clearInterval(iScroll); - iScroll = null; - } - offset = null; -} +const BANGLE2 = process.env.HWVERSION===2; /** * @param {string} text @@ -85,21 +17,22 @@ function fitText(text) { return Infinity; } // make a guess, then shrink/grow until it fits - const test = (s) => g.setFont("Vector", s).stringWidth(text); - let best = Math.floor(24000/test(100)); - if (test(best)===240) { // good guess! + const w = Bangle.appRect.w, + test = (s) => g.setFont("Vector", s).stringWidth(text); + let best = Math.floor(100*w/test(100)); + if (test(best)===w) { // good guess! return best; } - if (test(best)<240) { + if (test(best) 240 + // width > w do { best--; - } while(test(best)>240); + } while(test(best)>w); return best; } @@ -115,14 +48,6 @@ function textCode(text) { } return code%360; } -// dark magic -function hsv2rgb(h, s, v) { - const f = (n) => { - const k = (n+h/60)%6; - return v-v*s*Math.max(Math.min(k, 4-k, 1), 0); - }; - return {r: f(5), g: f(3), b: f(1)}; -} function f2hex(f) { return ("00"+(Math.round(f*255)).toString(16)).substr(-2); } @@ -131,38 +56,218 @@ function f2hex(f) { * @return {string} Semi-random color to use for given info */ function infoColor(name) { - let h, s, v; - if (name==="num") { - // always white - h = 0; - s = 0; - } else { - // make color depend deterministically on info - let code = textCode(info[name]); - switch(name) { - case "track": // also use album - code += textCode(info.album); - // fallthrough - case "album": // also use artist - code += textCode(info.artist); - } - h = code%360; - s = 0.7; + // make color depend deterministically on info + let code = textCode(layout[name].label); + switch(name) { + case "title": // also use album and artist + code += textCode(layout.album.label); + // fallthrough + case "album": // also use artist + code += textCode(layout.artist.label); } - v = brightness(); - const rgb = hsv2rgb(h, s, v); - return "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b); + let rgb; + if (g.getBPP()===3) { + // only pick 3-bit colors, always at full brightness + rgb = [code&1, (code&2)/2, (code&4)/4]; + if (g.setColor(rgb[0], rgb[1], rgb[2]).getColor()===g.theme.bg) { + // avoid picking the bg color + rgb = rgb.map(c => 1-c); + } + return "#"+f2hex(rgb[0])+f2hex(rgb[1])+f2hex(rgb[2]); + } else { + // pick any hue, adjust for brightness + const h = code%360, s = 0.7, b = brightness(); + return E.HSBtoRGB(h/360, s, b); + } +} + +/** + * Render scrolling title + * @param l + */ +function rScroller(l) { + g.setFont("Vector", Math.round(g.getHeight()*l.fsz.slice(0, -1)/100)); + const w = g.stringWidth(l.label)+40, + y = l.y+l.h/2; + l.offset = l.offset%w; + g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) + .setColor(l.col) + .setFontAlign(-1, 0) // left center + .clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) + .drawString(l.label, l.x-l.offset+40, y) + .drawString(l.label, l.x-l.offset+40+w, y); } /** - * Remember track color until info changes - * Because we need this every time we move the scroller - * @return {string} + * Render title + * @param l */ -function trackColor() { - if (!("track_color" in info) || fade) { - info.track_color = infoColor("track"); +function rTitle(l) { + if (l.offset!==null) { + rScroller(l); // already scrolling + return; } - return info.track_color; + let size = fitText(l.label); + if (sizel.h) { + size = l.h; + } + g.setFont("Vector", size) + .setFontAlign(0, -1) // center top + .drawString(l.label, l.x+l.w/2, l.y); +} +/** + * Render icon + * @param l + */ +function rIcon(l) { + const x2 = l.x+l.w-1, + y2 = l.y+l.h-1; + switch(l.icon) { + case "pause": + const w13 = l.w/3; + g.drawRect(l.x, l.y, l.x+w13, y2); + g.drawRect(l.x+l.w-w13, l.y, x2, y2); + break; + case "play": + g.drawPoly([ + l.x, l.y, + x2, l.y+l.h/2, + l.x, y2, + ], true); + break; + case "previous": + const w15 = l.w*1/5; + g.drawPoly([ + x2, l.y, + l.x+w15, l.y+l.h/2, + x2, y2, + ], true); + g.drawRect(l.x, l.y, l.x+w15, y2); + break; + case "next": + const w45 = l.w*4/5; + g.drawPoly([ + l.x, l.y, + l.x+w45, l.y+l.h/2, + l.x, y2, + ], true); + g.drawRect(l.x+w45, l.y, x2, y2); + break; + default: // red X + console.log(`Unknown icon: ${l.icon}`); + g.setColor("#f00") + .drawRect(l.x, l.y, x2, y2) + .drawLine(l.x, l.y, x2, y2) + .drawLine(l.x, y2, x2, l.y); + } +} +let layout; +function makeUI() { + global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices) + Bangle.loadWidgets(); + Bangle.drawWidgets(); + delete (global.gbmusic_active); + const Layout = require("Layout"); + layout = new Layout({ + type: "v", c: [ + { + type: "h", fillx: 1, c: [ + {id: "time", type: "txt", label: "88:88", valign: -1, halign: -1, font: "8%", bgCol: g.theme.bg}, + {fillx: 1}, + {id: "num", type: "txt", label: "88:88", valign: -1, halign: 1, font: "12%", bgCol: g.theme.bg}, + BANGLE2 ? {} : {id: "up", type: "txt", label: " +", font: "6x8:2"}, + ], + }, + {id: "title", type: "custom", label: "", fillx: 1, filly: 2, offset: null, font: "Vector:20%", render: rTitle, bgCol: g.theme.bg}, + {id: "artist", type: "custom", label: "", fillx: 1, filly: 1, size: 30, render: rInfo, bgCol: g.theme.bg}, + {id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg}, + {height: 10}, + { + type: "h", c: [ + {width: 3}, + {id: "prev", type: "custom", height: 15, width: 15, icon: "previous", render: rIcon, bgCol: g.theme.bg}, + {id: "date", type: "txt", halign: 0, valign: 1, label: "", font: "8%", fillx: 1, bgCol: g.theme.bg}, + {id: "next", type: "custom", height: 15, width: 15, icon: "next", render: rIcon, bgCol: g.theme.bg}, + BANGLE2 ? {width: 3} : {id: "down", type: "txt", label: " -", font: "6x8:2"}, + ], + }, + {height: 10}, + ], + }, {lazy: true}); + layout.render(); +} + +/////////////////////// +// Self-repeating timeouts +/////////////////////// + +// Clock +let tock = -1; +function tick() { + if (!BANGLE2 && !Bangle.isLCDOn()) { + return; + } + const now = new Date(); + if (now.getHours()*60+now.getMinutes()!==tock) { + drawDateTime(); + tock = now.getHours()*60+now.getMinutes(); + } + setTimeout(tick, 1000); // we only show minute precision anyway +} + +// Fade out while paused and auto closing +let fade = null; +function fadeOut() { + if (BANGLE2 || !Bangle.isLCDOn() || !fade) { + return; + } + layout.render(); + setTimeout(fadeOut, 500); +} +function brightness() { + if (!fade) { + return 1; + } + return Math.max(0, 1-((Date.now()-fade)/POUT)); +} + +// Scroll long track names +// use an interval to get smooth movement +let iScroll; +function scroll() { + layout.title.offset += 10; + rScroller(layout.title); +} +function scrollStart() { + if (layout.title.offset!==null) { + return; // already started + } + layout.title.offset = 0; + if (BANGLE2 || Bangle.isLCDOn()) { + if (!iScroll) { + iScroll = setInterval(scroll, 200); + } + rScroller(layout.title); + } +} +function scrollStop() { + if (iScroll) { + clearInterval(iScroll); + iScroll = null; + } + layout.title.offset = null; } //////////////////// @@ -172,10 +277,9 @@ function trackColor() { * Draw date and time */ function drawDateTime() { - const now = new Date; + const now = new Date(); const l = require("locale"); const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; - let time; if (is12) { const d12 = new Date(now.getTime()); const hour = d12.getHours(); @@ -184,29 +288,35 @@ function drawDateTime() { } else if (hour>12) { d12.setHours(hour-12); } - time = l.time(d12, true)+l.meridian(now); + layout.time.label = l.time(d12, true)+l.meridian(now); } else { - time = l.time(now, true); + layout.time.label = l.time(now, true); } - g.reset(); - g.setFont("Vector", 24) - .setFontAlign(-1, -1) // top left - .clearRect(10, 30, 119, 54) - .drawString(time, 10, 30); - - const date = require("locale").date(now, true); - g.setFont("Vector", 16) - .setFontAlign(0, 1) // bottom center - .setClipRect(35, 198, 199, 214) - .clearRect(31, 198, 199, 214) - .drawString(date, 119, 240-26); + layout.date.label = require("locale").date(now, true); + layout.render(); } +function drawControls() { + let l = layout; + const cc = a => (a ? "#f00" : "#0f0"); // control color: red for active, green for inactive + if (!BANGLE2) { + l.up.col = cc("volumeup" in tCommand); + l.down.col = cc("volumedown" in tCommand); + } + l.prev.icon = (stat==="play") ? "pause" : "prev"; + l.prev.col = cc("prev" in tCommand || "pause" in tCommand); + l.next.icon = (stat==="play") ? "next" : "play"; + l.next.col = cc("next" in tCommand || "play" in tCommand); + layout.render(); +} + +//////////////////////// +// GB event handlers +/////////////////////// /** - * Draw track number and total count - * @param {boolean} clr - Clear area before redrawing? + * Mangle track number and total count for display */ -function drawNum(clr) { +function formatNum(info) { let num = ""; if ("n" in info && info.n>0) { num = "#"+info.n; @@ -214,198 +324,26 @@ function drawNum(clr) { num += "/"+info.c; } } - g.reset(); - g.setFont("Vector", 30) - .setFontAlign(1, -1); // top right - if (clr) { - g.clearRect(225, 30, 120, 60); - } - g.drawString(num, 225, 30); -} -/** - * Clear rectangle used by track title - */ -function clearTrack() { - g.clearRect(0, 60, 239, 119); -} -/** - * Draw track title - * @param {boolean} clr - Clear area before redrawing? - */ -function drawTrack(clr) { - let size = fitText(info.track); - if (size<25) { - // the title is too long: start the scroller - scrollStart(); - return; - } else { - scrollStop(); - } - // stationary track - if (size>40) { - size = 40; - } - g.reset(); - g.setFont("Vector", size) - .setFontAlign(0, 1) // center bottom - .setColor(trackColor()); - if (clr) { - clearTrack(); - } - g.drawString(info.track, 119, 109); -} -/** - * Draw scrolling track title - */ -function drawScroller() { - g.reset(); - g.setFont("Vector", 40); - const w = g.stringWidth(info.track)+40; - offset = offset%w; - g.setFontAlign(-1, 1) // left bottom - .setColor(trackColor()); - clearTrack(); - g.drawString(info.track, -offset+40, 109) - .drawString(info.track, -offset+40+w, 109); + return num; } -/** - * Draw track artist and album - * @param {boolean} clr - Clear area before redrawing? - */ -function drawArtistAlbum(clr) { - // we just use small enough fonts to make these always fit - // calculate stuff before clear+redraw - const aCol = infoColor("artist"); - const bCol = infoColor("album"); - let aSiz = fitText(info.artist); - if (aSiz>30) { - aSiz = 30; - } - let bSiz = fitText(info.album); - if (bSiz>20) { - bSiz = 20; - } - g.reset(); - if (clr) { - g.clearRect(0, 120, 240, 189); - } - let top = 124; - if (info.artist) { - g.setFont("Vector", aSiz) - .setFontAlign(0, -1) // center top - .setColor(aCol) - .drawString(info.artist, 119, top); - top += aSiz+4; // fit album neatly under artist - } - if (info.album) { - g.setFont("Vector", bSiz) - .setFontAlign(0, -1) // center top - .setColor(bCol) - .drawString(info.album, 119, top); - } -} - -/** - * - * @param {string} icon Icon name - * @param {number} x - * @param {number} y - * @param {number} s Icon size - */ -function drawIcon(icon, x, y, s) { - ({ - pause: function(x, y, s) { - const w1 = s/3; - g.drawRect(x, y, x+w1, y+s); - g.drawRect(x+s-w1, y, x+s, y+s); - }, - play: function(x, y, s) { - g.drawPoly([ - x, y, - x+s, y+s/2, - x, y+s, - ], true); - }, - previous: function(x, y, s) { - const w2 = s*1/5; - g.drawPoly([ - x+s, y, - x+w2, y+s/2, - x+s, y+s, - ], true); - g.drawRect(x, y, x+w2, y+s); - }, - next: function(x, y, s) { - const w2 = s*4/5; - g.drawPoly([ - x, y, - x+w2, y+s/2, - x, y+s, - ], true); - g.drawRect(x+w2, y, x+s, y+s); - }, - })[icon](x, y, s); -} -function controlColor(ctrl) { - return (ctrl in tCommand) ? "#ff0000" : "#008800"; -} -function drawControl(ctrl, x, y) { - g.setColor(controlColor(ctrl)); - const s = 20; - if (stat!==controlState) { - g.clearRect(x, y, x+s, y+s); - } - drawIcon(ctrl, x, y, s); -} -let controlState; -function drawControls() { - g.reset(); - if (stat==="play") { - // left touch - drawControl("pause", 10, 190); - // right touch - drawControl("next", 200, 190); - } else { - drawControl("previous", 10, 190); - drawControl("play", 200, 190); - } - g.setFont("6x8", 2); - // BTN1 - g.setFontAlign(1, -1); - g.setColor(controlColor("volumeup")); - g.drawString("+", 240, 30); - // BTN2 - g.setFontAlign(1, 1); - g.setColor(controlColor("volumedown")); - g.drawString("-", 240, 210); - controlState = stat; -} - -/** - * @param {boolean} [clr=true] Clear area before redrawing? - */ -function drawMusic(clr) { - clr = !(clr===false); // undefined means yes - drawNum(clr); - drawTrack(clr); - drawArtistAlbum(clr); -} - -//////////////////////// -// GB event handlers -/////////////////////// /** * Update music info - * @param {Object} e - Gadgetbridge musicinfo event + * @param {Object} info - Gadgetbridge musicinfo event */ -function musicInfo(e) { - info = e; - delete (info.t); - offset = null; - if (Bangle.isLCDOn()) { - drawMusic(); - } +function musicInfo(info) { + scrollStop(); + layout.title.label = info.track || ""; + layout.album.label = info.album || ""; + layout.artist.label = info.artist || ""; + // color depends on all labels + layout.title.col = infoColor("title"); + layout.album.col = infoColor("album"); + layout.artist.col = infoColor("artist"); + layout.num.label = formatNum(info); + layout.render(); + rTitle(layout.title); // force redraw of title, or scroller might break + // reset auto exit interval if (tIxt) { clearTimeout(tIxt); tIxt = null; @@ -435,7 +373,6 @@ function musicState(e) { tIxt = null; } fade = null; - delete info.track_color; if (auto) { // auto opened -> auto close switch(stat) { case "stop": // never actually happens with my phone :-( @@ -444,7 +381,7 @@ function musicState(e) { case "play": // if inactive for double song duration (or an hour if unknown), load the clock // i.e. phone finished playing without bothering to notify the watch - tIxt = setTimeout(load, (info.dur*2000) || IOUT); + tIxt = setTimeout(load, (e.dur*2000) || IOUT); break; case "pause": default: @@ -456,8 +393,7 @@ function musicState(e) { break; } } - if (Bangle.isLCDOn()) { - drawMusic(false); // redraw in case we were fading out but resumed play + if (BANGLE2 || Bangle.isLCDOn()) { drawControls(); } } @@ -473,30 +409,34 @@ function musicState(e) { */ let tPress, nPress = 0; function startButtonWatches() { - // BTN1/3: volume control - // Wait for falling edge to avoid messing with volume while long-pressing BTN3 - // to reload the watch (and same for BTN2 for consistency) - setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"}); - setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"}); + let btn = BTN1; + if (!BANGLE2) { + // BTN1/3: volume control + // Wait for falling edge to avoid messing with volume while long-pressing BTN3 + // to reload the watch (and same for BTN2 for consistency) + setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"}); + setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"}); + btn = BTN2; + } - // BTN2: long-press for launcher, otherwise depends on number of presses + // middle button: long-press for launcher, otherwise depends on number of presses setWatch(() => { if (nPress===0) { tPress = setTimeout(() => {Bangle.showLauncher();}, 3000); } - }, BTN2, {repeat: true, edge: "rising"}); + }, btn, {repeat: true, edge: "rising"}); const s = require("Storage").readJSON("gbmusic.json", 1) || {}; if (s.simpleButton) { setWatch(() => { clearTimeout(tPress); togglePlay(); - }, BTN2, {repeat: true, edge: "falling"}); + }, btn, {repeat: true, edge: "falling"}); } else { setWatch(() => { nPress++; clearTimeout(tPress); tPress = setTimeout(handleButton2Press, 500); - }, BTN2, {repeat: true, edge: "falling"}); + }, btn, {repeat: true, edge: "falling"}); } } function handleButton2Press() { @@ -524,7 +464,7 @@ let tCommand = {}; */ function sendCommand(command) { Bluetooth.println(JSON.stringify({t: "music", n: command})); - // for controlColor + // for control color if (command in tCommand) { clearTimeout(tCommand[command]); } @@ -539,18 +479,29 @@ function sendCommand(command) { function togglePlay() { sendCommand(stat==="play" ? "pause" : "play"); } -function startTouchWatches() { +function pausePrev() { + sendCommand(stat==="play" ? "pause" : "previous"); +} +function nextPlay() { + sendCommand(stat==="play" ? "next" : "play"); +} + +/** + * Setup touch+swipe for Bangle.js 1 + */ +function touch1() { Bangle.on("touch", side => { if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware switch(side) { case 1: - sendCommand(stat==="play" ? "pause" : "previous"); + pausePrev(); break; case 2: - sendCommand(stat==="play" ? "next" : "play"); + nextPlay(); break; - case 3: + default: togglePlay(); + break; } }); Bangle.on("swipe", dir => { @@ -558,16 +509,56 @@ function startTouchWatches() { sendCommand(dir===1 ? "previous" : "next"); }); } +/** + * Setup touch+swipe for Bangle.js 2 + */ +function touch2() { + Bangle.on("touch", (side, xy) => { + const ar = Bangle.appRect; + if (xy.xar.x+ar.w*2/3) { + nextPlay(); + } else { + togglePlay(); + } + }); + // swiping + let drag; + Bangle.on("drag", e => { + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + if (Math.abs(dx)>Math.abs(dy)+10) { + // horizontal + sendCommand(dx>0 ? "previous" : "next"); + } else if (Math.abs(dy)>Math.abs(dx)+10) { + // vertical + sendCommand(dy>0 ? "volumedown" : "volumeup"); + } + } + }); +} +function startTouchWatches() { + if (BANGLE2) { + touch2(); + } else { + touch1(); + } +} function startLCDWatch() { + if (BANGLE2) { + return; // always keep drawing + } Bangle.on("lcdPower", (on) => { if (on) { // redraw and resume scrolling tick(); - drawMusic(); - drawControls(); + layout.render(); fadeOut(); - if (offset!==null) { - drawScroller(); + if (offset.offset!==null) { if (!iScroll) { iScroll = setInterval(scroll, 200); } @@ -585,15 +576,10 @@ function startLCDWatch() { ///////////////////// // Startup ///////////////////// -// check for saved music stat (by widget) to load g.clear(); -global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices) -Bangle.loadWidgets(); -Bangle.drawWidgets(); -delete (global.gbmusic_active); function startEmulator() { - if (typeof Bluetooth==="undefined") { // emulator! + if (typeof Bluetooth==="undefined" || typeof Bluetooth.println==="undefined") { // emulator! Bluetooth = { println: (line) => {console.log("Bluetooth:", line);}, }; @@ -609,6 +595,7 @@ function startWatches() { } function start() { + makeUI(); // start listening for music updates const _GB = global.GB; global.GB = (event) => { @@ -628,43 +615,39 @@ function start() { return; } }; - drawMusic(); - drawControls(); startWatches(); tick(); startEmulator(); } function init() { + // check for saved music status (by widget) to load let saved = require("Storage").readJSON("gbmusic.load.json", true); require("Storage").erase("gbmusic.load.json"); if (saved) { // autoloaded: load state was saved by widget - info = saved.info; - stat = saved.state; - delete saved; auto = true; start(); - } else { - delete saved; - let s = require("Storage").readJSON("gbmusic.json", 1) || {}; - if (!("autoStart" in s)) { - // user opened the app, but has not picked a setting yet - // ask them about autoloading now - E.showPrompt( - "Automatically load\n"+ - "when playing music?\n", - ).then(choice => { - s.autoStart = choice; - require("Storage").writeJSON("gbmusic.json", s); - delete s; - setTimeout(start, 0); - }); - } else { - delete s; - start(); - } + musicInfo(saved.info); + musicState(saved.state); + return; } -} -init(); + let s = require("Storage").readJSON("gbmusic.json", 1) || {}; + if ("autoStart" in s) { + start(); + return; + } + + // user opened the app, but has not picked a autoStart setting yet + // ask them about autoloading now + E.showPrompt( + "Automatically load\n"+ + "when playing music?\n" + ).then(choice => { + s.autoStart = choice; + require("Storage").writeJSON("gbmusic.json", s); + setTimeout(start, 0); + }); +} +init(); \ No newline at end of file diff --git a/apps/gbmusic/screenshot.png b/apps/gbmusic/screenshot.png deleted file mode 100644 index 569a6a2c5..000000000 Binary files a/apps/gbmusic/screenshot.png and /dev/null differ diff --git a/apps/gbmusic/screenshot_2.png b/apps/gbmusic/screenshot_2.png deleted file mode 100644 index f19f8f428..000000000 Binary files a/apps/gbmusic/screenshot_2.png and /dev/null differ diff --git a/apps/gbmusic/screenshot_v1.png b/apps/gbmusic/screenshot_v1.png new file mode 100644 index 000000000..3b290e459 Binary files /dev/null and b/apps/gbmusic/screenshot_v1.png differ diff --git a/apps/gbmusic/screenshot_v2.png b/apps/gbmusic/screenshot_v2.png new file mode 100644 index 000000000..b89b5022e Binary files /dev/null and b/apps/gbmusic/screenshot_v2.png differ diff --git a/apps/gbridge/sample_messages.js b/apps/gbridge/sample_messages.js index be33a25b8..046ffa9e4 100644 --- a/apps/gbridge/sample_messages.js +++ b/apps/gbridge/sample_messages.js @@ -15,7 +15,7 @@ GB({"t":"notify","id":1592721714,"src":"ALARMCLOCKRECEIVER"}) GB({"t":"notify-","id":1592721714}) // Weather update (doesn't show a notification, not handled by gbridge app: see weather app) -GB({"t":"weather","temp":288,"hum":94,"txt":"Light rain","wind":0,"loc":"Test City"}) +GB({"t":"weather","temp":288,"hum":94,"txt":"Light rain","wind":0,"wdir":120,"loc":"Test City"}) // Nextcloud updated a file GB({"t":"notify","id":1594184421,"src":"Nextcloud","title":"Downloaded","body":"test.file downloaded"}) diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 5eb96a0ea..bde4f8ab8 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -6,3 +6,4 @@ 0.05: Fix daily summary calculation 0.06: Fix daily health summary for movement (a line got deleted!) 0.07: Added coloured bar charts +0.08: Suppress bleed through of E.showMenu's when displaying bar charts diff --git a/apps/health/app.js b/apps/health/app.js index eae45c190..08d6ead17 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -236,6 +236,9 @@ Bangle.on('swipe', dir => { // use setWatch() as Bangle.setUI("updown",..) interacts with swipes function setButton(fn) { + // cancel callback, otherwise a slight up down movement will show the E.showMenu() + Bangle.setUI("updown", undefined); + if (process.env.HWVERSION == 1) btn = setWatch(fn, BTN2); else diff --git a/apps/hidmsicswipe/changelog b/apps/hidmsicswipe/changelog new file mode 100644 index 000000000..df3737358 --- /dev/null +++ b/apps/hidmsicswipe/changelog @@ -0,0 +1 @@ +0.01: Core functionnality based entirely on hidmsic diff --git a/apps/hidmsicswipe/hidmsicswipe-icon.js b/apps/hidmsicswipe/hidmsicswipe-icon.js new file mode 100644 index 000000000..6a0c64b9c --- /dev/null +++ b/apps/hidmsicswipe/hidmsicswipe-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A5xGICquZzAVUAAIXQCogXQCoxHPCox0BxIXNxIVFBAQXPUAwXPBw4XowAvuC/4X/C9sIC6kIxGZzIXSFgIWBC6QWEC6RECAAOJwAXQFwoXLxAqBC4MICweZCxhWEC4mICxxuDA4I3BCxQ/FQxpyEK6AucC4idMI5OICyQwBQpgA/AH4Au")) diff --git a/apps/hidmsicswipe/hidmsicswipe.js b/apps/hidmsicswipe/hidmsicswipe.js new file mode 100644 index 000000000..e0fc760a4 --- /dev/null +++ b/apps/hidmsicswipe/hidmsicswipe.js @@ -0,0 +1,93 @@ +var storage = require('Storage'); + +const settings = storage.readJSON('setting.json',1) || { HID: false }; + +var sendHid, next, prev, toggle, up, down, profile; +var lasty = 0; +var lastx = 0; + +if (settings.HID=="kbmedia") { + profile = 'Music'; + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([1,code], () => { + NRF.sendHIDReport([1,0], () => { + if (cb) cb(); + }); + }); + } catch(e) { + print(e); + } + }; + next = function (cb) { sendHid(0x01, cb); }; + prev = function (cb) { sendHid(0x02, cb); }; + toggle = function (cb) { sendHid(0x10, cb); }; + up = function (cb) {sendHid(0x40, cb); }; + down = function (cb) { sendHid(0x80, cb); }; +} else { + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kbmedia"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidmsicswipe.app.js"); + } else setTimeout(load, 1000); + }); +} + +function drawApp() { + g.clear(); + if(Bangle.isLocked()==false) E.showMessage('Swipe', 'Music'); + else E.showMessage('Locked', 'Music'); +} + +if (next) { + setWatch(function(e) { + var len = e.time - e.lastTime; + E.showMessage('lock'); + setTimeout(drawApp, 1000); + Bangle.setLocked(true); + }, BTN1, { edge:"falling",repeat:true,debounce:50}); + Bangle.on('drag', function(e) { + if(!e.b){ + //console.log(lasty); + //console.log(lastx); + if(lasty > 40){ + E.showMessage('down'); + setTimeout(drawApp, 1000); + down(() => {}); + } + else if(lasty < -40){ + E.showMessage('up'); + setTimeout(drawApp, 1000); + up(() => {}); + } else if(lastx < -40){ + E.showMessage('prev'); + setTimeout(drawApp, 1000); + prev(() => {}); + } else if(lastx > 40){ + E.showMessage('next'); + setTimeout(drawApp, 1000); + next(() => {}); + } else if(lastx==0 && lasty==0){ + E.showMessage('play/pause'); + setTimeout(drawApp, 1000); + toggle(() => {}); + } + lastx = 0; + lasty = 0; + } + else{ + lastx = lastx + e.dx; + lasty = lasty + e.dy; + } + }); + + Bangle.on("lock", function(on) { + if(!on){ + E.showMessage('unlock'); + setTimeout(drawApp, 1000); + } + }); + + drawApp(); +} diff --git a/apps/hidmsicswipe/hidmsicswipe.png b/apps/hidmsicswipe/hidmsicswipe.png new file mode 100644 index 000000000..923b5aa0e Binary files /dev/null and b/apps/hidmsicswipe/hidmsicswipe.png differ diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index c7ec09d30..750e7ddfc 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -1 +1,2 @@ 0.01: Launch app +0.02: Swipe left/right to set an alarm. diff --git a/apps/lcars/README.md b/apps/lcars/README.md index fdce30c1b..1a73a0d71 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -1,8 +1,16 @@ # LCARS clock -A simple LCARS inspired clock that shows: - * Current time - * Current date - * Battery level - * Steps +A simple LCARS inspired clock. +Note: To display the steps, its necessary to install +the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget). +## Features + * Shows the time + * Shows the date + * Shows the current battery level in % + * Shows the number of daily steps + * Swipe left/right to activate an alarm + + +## Creator +Made by [David Peer](https://github.com/peerdavid) \ No newline at end of file diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index cf884a6b7..c30cdfda6 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -1,43 +1,39 @@ -const locale = require('locale'); - - /* - * Assets: Images, fonts etc. + * Requirements and globals */ +const locale = require('locale'); +var alarm = -1; + var img = { width : 176, height : 151, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA==")) } -Graphics.prototype.setFontMinaSmall = function(scale) { +Graphics.prototype.setFontAntonioMedium = function(scale) { // Actual height 18 (17 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAA/8w/8wAAQAAAAAA4AA8AAAAA8AAwAAAAAAEABEABEQB/w/8AxEABEwB/w/8AxEABEABEAAAAH4MP8MMEM8GPcGOMGMMGMMH4ABwAAA/gAwgAggAggQwhw/nAAOAA4ADgAOfw8YwwQwAQQAYwAfwAPgAGAAfg85w/wwzgww4wwdgwHggHgAPwAIQAAA8AAwAAAAAAfwH/+fAP4ABgAAgAA4ABfAPH/+A/wAAAAAAEAAHgAfAAfAAHgAMAAAAAAAABgABgABgAf4AP4ABgABgABAAAAAADAADwADAAAAAgAAwAAwAAwAAwAAwAAAAADAADAADAAAAAAEAA8AH4A+AHwA+AAwAAAAAf/g//wwAwwAwwAwwAwwAwwAwf/gH+AAAAAAAYAAwAAwAA//wAAAAAAAAAwAwwBwwDwwDwwGwwcww4wfwwPAwAAAAAAwAwwQwwQwwQwwQwwYww4wf/gHHAAAAAEAAeAA+ADmAPGAcGAwGAh/wD/wAEAAEAAAAf4w/4wwwwwwwwwwwwwwwww/wgfgAAAAAAP/Af/gwwwwwwwwwwwwwwwwwww/gAAAgAAwAAwAAwAwwHww/Az4A/AA8AAAAAAAAfPg//wxwwwwwwwwwwwwww//wffgAAAAAAfwQ/wwwQwwYwwQwwQwwQw//wP/AAAAAAAMDAMDAMDAAAAAAAMDAMDwMDAAAAAAADgADgAHwAGwAMYAMYAIIAAAAEQAGYAGYAGYAGYAGYAGYAGYAAAAMYAMYAGwAGwAHgADgADAAAAAwAAwAAwAAwcwwcwwQAwQA/wAfgAAAAAAAB/8D/+TAGbHjbPzbMTbMzbMzb/zZ/zYAGf/+H/8AAAAAAABwAPwA+AH+A+GA8GAfmAD+AAfgADwAAQAAA//w//wwwwwwwwwwwwwxww//wffgAAAAAAP/Af/gwBwwAwwAwwAwwAwwAwwAwAAA//w//wwAwwAwwAwwAwwAw4Bwf/gH+AAAAAAAf/g//wwQwgQQgQQgQQgQQgQQgAQAAAf/w//wwQAgQAgQAgQAgQAgQAgAAAAAP/Af/gwAwwAwwAwwYwwYwwfwwfwAAAAAA//w//wAYAAYAAYAAYAAYAAYA//w//wAAA//w//wAAAAAAAAwAAwAAw//w//AAAA//w//wAYAA4AD8AHHAeDg4AwgAQAAAAAA//g//wAAwAAwAAwAAwAAwAAwAAQAAAP/w//w+AAPwAB+AAHwADwA/gH4A/AA/4A//wAAwAAA//w//wcAAPAADgAA4AAeAAHAADw//wAAAAAAH/Af/g4AwwAwwAwwAwwAwwAwcDwP/gB4AAAA//w//wwQAwQAwQAwYAwwA/wAPgAAAAH/Af/gwAwwAwwAwwA8wA8wA2cDkP/gB4AAAA//w//wwYAwYAwYAwcAwfA/zwPgwAAAAAAfgA/wwwQwwYwwYwwYwwYwwfwAPgAAAAAAwAAwAAwAA//w//wwAAwAAwAAwAAAAA/+A//gABwAAwAAwAAwAAwAAwAPg//AAAAAAA4AA/AAH4AA/AAHwADwAfgD8AfgA8AAgAAAAA4AA/AAH4AA/AAHwAHwA/AP4A/4Aw/AAHwAHwA/AP4A+AAwAAAAAwAw8DwOHAD8AB4AD8AOHA8DwwAwAAAAAAwAA8AAPAADwAA/wB/wHgAeAA4AAgAAgAQwBwwHwwOww8wxww3gw+Aw4AwwAQAAAH//f//YAAYAAQAAwAA+AAPwAB+AAPwAB8AAMQAAYAAYAAf//AAAAAA"), 32, atob("BgUHDAoRCwMGBggJBQYFBwwHCwsLCwsKCwsFBQkICQoPDAsKDAoKCwsEBgsKDgwMCgwLCwoMDBELCwoGBwY="), 18+(scale<<8)+(1<<16)); + g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16)); } -Graphics.prototype.setFontMinaLarge = function(scale) { - // Actual height 35 (34 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAB4AAAAAPgAAAAA+AAAAAD4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAH4AAAAD/gAAAB/8AAAA/+AAAAf/AAAAP/gAAAH/wAAAD/4AAAD/8AAAB/+AAAAP/AAAAA/AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///4AA////wAH////gA+AAAfADwAAA8AOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA8AAAPAD4AAB8AH////gAP///8AAf///gAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAcAAAAADgAAAAAOAAAAAB4AAAAAHAAAAAA8AAAAAD////8AP////wA/////AD////8AAAAAAAAAAAAAAAAAAAAAGAAAAAA4AAAHADgAAA8AOAAAHwA4AAA/ADgAAD8AOAAAfwA4AAD/ADgAAfcAOAAD5wA4AAfHADgAD4cAOAAfBwA4AD8HADwAfgcAPAD8BwAeA/AHAB+f4AcAD//ABwAH/wAHAAH8AAcAAAAAAAAAAAAAAAAAAAAAGAAABgAYAAAGADgAAAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADwB4A8APAPgDwAfD//+AB////4AD/8//AAD/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAAPwAAAAD/AAAAAf8AAAAH9wAAAB/HAAAAPwcAAAD+BwAAAfgHAAAH8AcAAB/ABwAAPwAHAAA+AAcAADgABwAAIAAHgAAAH///AAB///8AAH///wAAAAcAAAAABwAAAAAHAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAH/8AYAP//wBgA///AHAD//wAcAOAPABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgA8AOAOADwA4A8APADgD8H4AOAH//gA4AP/8AAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAD//+AAB///+AAP///8AB/BgH4APgOAHwA8A4APADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDwA8AOAP//wA4Af/+ABgA//wAAAA/8AAAAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAADgAAAAAOAAAAQA4AAAHADgAAD8AOAAA/wA4AAf+ADgAP/gAOAD/wAA4B/8AADg/+AAAOP/AAAA//wAAAD/4AAAAP8AAAAA/AAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/h/4AA//P/wAH////gA+B/AfADwD4A8AOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAPAPgDwA8A+APAB////4AH////gAP/j/8AAD4B8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAA//gAYAH//ABwA//+AHADwB4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAPADgDwA+AOAfAB+A4f4AD////AAH///4AAD//8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAeAAB8AD4AAHwAPgAAfAA+AAB4AB4AAAAAAAAAAAAAAAAAAAAAA="), 46, atob("CxAaDhgYGBgZFhkZCw=="), 40+(scale<<8)+(1<<16)); +Graphics.prototype.setFontAntonioLarge = function(scale) { + // Actual height 34 (34 - 1) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAADwAAAAAeAAAAADwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAD+AAAAH/wAAAP/+AAAf/+AAA//8AAB//4AAD//wAAD//gAAAf/AAAAD+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAB////gA/////AP////8D/////wfAAAA+DwAAADweAAAAeDwAAADwf////+D/////wP////8Af///+AAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAOAAAAADwAAAAAeAAAAAHgAAAAB/////wf////+D/////wf////+D/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAPwH/4AP+B//AH/wf/4D/+D4AB/9weAAf4ODwAP8BweAP/AOD///gBwP//wAOA//4ABwB/4AAOAAAAAAAAAAAAAAAAAAAAB8AA/gA/gAH/AP8AA/8D/gAH/wfAHAA+DwA4ADweAHgAeDwB8ADwf7/+H+D/////gP/9//8A//H/+AA/AH/AAAAAAAAAAAAAAAAAABwAAAAD+AAAAD/wAAAH/+AAAH/5wAAH/wOAAP/gBwAP/gAOAD/////wf////+D/////wf////+AAAABwAAAAAOAAAAABwAAAAAAAAAAAAAAAAAAeAD//4D/Af//Af8D//4D/wf//Af+DwPAADweB4AAeDwPAADweB///+DwP///weA///8DwD//+AAAA/8AAAAAAAAAAAAAAAAAAAAAA////AA/////AP////8D/////wfgPAB+DwB4ADweAOAAeDwBwADwf+PAA+D/x///wP+H//8A/wf//AAAA//gAAAAAAAAAAAAADgAAAAAeAAAAADwAAAAAeAAAD+DwAAP/weAA//+DwA///weB///8Dx//8AAf//wAAD//gAAAf/AAAAD/AAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAD/wf/wB//v//AP////8D/////weAPwAeDwA8ADwcAHAAeDwB8ADwf////+D/////wP/9//8A//H//AA/AD/AAAAAAAAAAAAAAAAAAAAAD//gfAA///D/AP//8f8D///j/weAA8A+DwADgDweAAcAeDwAHgDwf////+B/////gP////8Af///+AAP//4AAAAAAAAAAAAAAAAAAAAAAD4AfAAAfAD4AAD4AfAAAfAD4AAD4AfAAAAAAAAAAAAAA=="), 46, atob("Cg4QEBAQEBAQEBAQCQ=="), 39+(scale<<8)+(1<<16)); } - /* - * Queue drawing every minute + * Draw watch face */ var drawTimeout; function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; - draw(); + draw(true); }, 60000 - (Date.now() % 60000)); } -/* - * Draw watch face - */ -function draw(){ +function draw(queue){ g.reset(); g.clearRect(0, 24, g.getWidth(), g.getHeight()); @@ -48,43 +44,142 @@ function draw(){ var currentDate = new Date(); var timeStr = locale.time(currentDate,1); g.setFontAlign(0,0,0); - g.setFontMinaLarge(); - g.drawString(timeStr, 115, 53); + g.setFontAntonioLarge(); + g.drawString(timeStr, 100, 50); // Write date - g.setFontAlign(-1,-1,0); - g.setFontMinaSmall(); + g.setFontAlign(1,-1, 0); + g.setFontAntonioMedium(); var dayName = locale.dow(currentDate, true).toUpperCase(); var day = currentDate.getDate(); - g.drawString("DATE:", 40, 107); - g.drawString(dayName + " " + day, 100, 105); + g.drawString(day, 170, 30); + g.drawString(dayName, 170, 50); + + // Alarm + g.setFontAlign(-1,-1,0); + g.drawString("TMR:", 30, 107); + var alrmText = alarm >= 0 ? "T-"+alarm : "OFF"; + g.drawString(alrmText, 65, 107); // Draw battery var bat = E.getBattery(); - g.drawString("BAT:", 40, 127); - g.drawString(bat+"%", 100, 127); + var charging = Bangle.isCharging() ? "*" : ""; + g.drawString("BAT:", 30, 127); + g.drawString(charging + bat+ "%", 65, 127); // Draw steps - var steps = Bangle.getStepCount(); - g.drawString("STEP:", 40, 147); - g.drawString(steps, 100, 147); + var steps = getSteps(); + g.drawString("STEP:", 30, 147); + g.drawString(steps, 65, 147); + + // GPS + var gpsText = Bangle.isGPSOn() ? "ON" : "OFF"; + g.drawString("GPS:", 115, 107); + g.drawString(gpsText, 149, 107); + + + // HRM + var gpsText = Bangle.isHRMOn() ? "ON" : "OFF"; + g.drawString("HRM:", 115, 127); + g.drawString(gpsText, 149, 127); + + // CMP + var compassText = Bangle.isCompassOn() ? "ON" : "OFF"; + g.drawString("CMP:", 115, 147); + g.drawString(compassText, 149, 147); // Queue draw in one minute - queueDraw(); + if(queue){ + queueDraw(); + } } -// Clear the screen once, at startup -g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +/* + * Step counter via widget + */ +function getSteps() { + if (stepsWidget() !== undefined) + return stepsWidget().getSteps(); + return "???"; +} -// draw immediately at first, queue update -draw(); +function stepsWidget() { + if (WIDGETS.activepedom !== undefined) { + return WIDGETS.activepedom; + } else if (WIDGETS.wpedom !== undefined) { + return WIDGETS.wpedom; + } + return undefined; +} + +/* + * Handle alarm + */ +var alarmTimeout; +function queueAlarm() { + if (alarmTimeout) clearTimeout(alarmTimeout); + alarmTimeout = setTimeout(function() { + alarmTimeout = undefined; + handleAlarm(); + }, 60000 - (Date.now() % 60000)); +} + +function handleAlarm(){ + + // Check each minute + if(alarm > 0){ + alarm--; + queueAlarm(); + } + + // After n minutes, inform the user + if(alarm == 0){ + alarm = -1; + + var t = 300; + Bangle.buzz(t, 1) + .then(() => new Promise(resolve => setTimeout(resolve, t))) + .then(() => Bangle.buzz(t, 1)) + .then(() => new Promise(resolve => setTimeout(resolve, t))) + .then(() => Bangle.buzz(t, 1)) + .then(() => new Promise(resolve => setTimeout(resolve, t))) + .then(() => Bangle.buzz(t, 1)); + } + + // Update UI + draw(false); +} -// Stop updates when LCD is off, restart when on +/* + * Swipe to set an alarm + */ +Bangle.on('swipe',function(dir) { + // Increase alarm + if(dir == -1){ + alarm = alarm < 0 ? 0 : alarm; + alarm += 5; + queueAlarm(); + } + + // Decrease alarm + if(dir == +1){ + alarm -= 5; + alarm = alarm <= 0 ? -1 : alarm; + } + + // Update UI + draw(false); +}); + + +/* + * Stop updates when LCD is off, restart when on + */ Bangle.on('lcdPower',on=>{ if (on) { - draw(); // draw immediately, queue redraw + draw(true); // draw immediately, queue redraw } else { // stop draw timer if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; @@ -94,6 +189,12 @@ Bangle.on('lcdPower',on=>{ // Show launcher when middle button pressed Bangle.setUI("clock"); -// Load widgets +// Load widgets - needed by draw Bangle.loadWidgets(); + +// Clear the screen once, at startup and draw clock +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +draw(true); + +// After drawing the watch face, we can draw the widgets Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png new file mode 100644 index 000000000..dc577c4d0 Binary files /dev/null and b/apps/lcars/screenshot.png differ diff --git a/apps/magnav/ChangeLog b/apps/magnav/ChangeLog index 35e8798c6..2b2782c7b 100644 --- a/apps/magnav/ChangeLog +++ b/apps/magnav/ChangeLog @@ -2,5 +2,6 @@ 0.02: Course marker 0.03: Tilt compensation and calibration 0.04: Fix Font size +0.05: Inital portable version diff --git a/apps/magnav/README.md b/apps/magnav/README.md index a036644fb..7ef506b2e 100644 --- a/apps/magnav/README.md +++ b/apps/magnav/README.md @@ -6,19 +6,20 @@ This is a tilt and roll compensated compass with a linear display. The compass w ## Calibration -Correct operation of this app depends critically on calibration. When first run on a Bangle, the app will request calibration. This lasts for 30 seconds during which you should move the watch slowly through figures of 8. It is important that during calibration the watch is fully rotated around each of it axes. If the app does give the correct direction heading or is not stable with respect to tilt and roll - redo the calibration by pressing *BTN3*. Calibration data is recorded in a storage file named `magnav.json`. +Correct operation of this app depends critically on calibration. When first run on a Bangle, the app will request calibration. This lasts for 20 seconds during which you should move the watch slowly through figures of 8. It is important that during calibration the watch is fully rotated around each of it axes. If the app does give the correct direction heading or is not stable with respect to tilt and roll - redo the calibration by pressing *BTN2*. Calibration data is recorded in a storage file named `magnav.json`. + +Note: Charging your Bangle due to the magnetic connector clamp seems to require recalibration afterwards for accurate readings. ## Controls -*BTN1* - switches to your selected clock app. +*BTN1* - marks the current heading with a blue circle - see screen shot. This can be used to take a bearing and then follow it.. +(Swipe UP on Bangle 2) -*BTN2* - switches to the app launcher. +*BTN2* - invokes calibration ( can be cancelled if pressed accidentally). +(*BTN1* on Bangle 2) -*BTN3* - invokes calibration ( can be cancelled if pressed accidentally) - -*Touch Left* - marks the current heading with a blue circle - see screen shot. This can be used to take a bearing and then follow it. - -*Touch Right* - cancels the marker (blue circle not displayed). +*BTN3* - cancels the marker (blue circle not displayed) +(swipe DOWN on Bangle 2) ## Support diff --git a/apps/magnav/magnav.min.js b/apps/magnav/magnav.min.js deleted file mode 100644 index 1d5439164..000000000 --- a/apps/magnav/magnav.min.js +++ /dev/null @@ -1,10 +0,0 @@ -var Yoff=80,pal2color=new Uint16Array([0,65535,2047,50712],0,2),buf=Graphics.createArrayBuffer(240,60,2,{msb:!0});Bangle.setLCDTimeout(30);function flip(b,c){g.drawImage({width:240,height:60,bpp:2,buffer:b.buffer,palette:pal2color},0,c);b.clear()}var labels="N NE E SE S SW W NW".split(" "),brg=null; -function drawCompass(b){buf.setColor(1);buf.setFont("Vector",24);var c=b-90;0>c&&(c+=360);buf.fillRect(28,45,212,49);var a=30,d=15-c%15;15>d?a+=d:d=0;for(var e=d;e<=180-d;e+=15){var f=c+e;0==f%90?(buf.drawString(labels[Math.floor(f/45)%8],a-8,0),buf.fillRect(a-2,25,a+2,45)):0==f%45?(buf.drawString(labels[Math.floor(f/45)%8],a-12,0),buf.fillRect(a-2,30,a+2,45)):0==f%15&&buf.fillRect(a,35,a+1,45);a+=15}brg&&(b=brg-b,180b&&(b+=360),b+=120,30>b&&(b=14),210c?1:-1;180<=a&&(a=360-a,d=-d);if(2>a)return c;a=c+d*(1+Math.round(a/5));0>a&&(a+=360);360a&&(a+=360);return a} -function reading(){var b=tiltfixread(CALIBDATA.offset,CALIBDATA.scale);heading=newHeading(b,heading);drawCompass(heading);buf.setColor(1);buf.setFont("6x8",2);buf.setFontAlign(-1,-1);buf.drawString("o",170,0);buf.setFont("Vector",54);b=Math.round(heading);var c=b.toString();buf.drawString(10>b?"00"+c:100>b?"0"+c:c,70,10);flip(buf,Yoff+80)} -function calibrate(){var b=-32E3,c=-32E3,a=-32E3,d=32E3,e=32E3,f=32E3,k=setInterval(function(){var h=Bangle.getCompass();b=h.x>b?h.x:b;c=h.y>c?h.y:c;a=h.z>a?h.z:a;d=h.x{ + CALIBDATA=r; require("Storage").write("magnav.json",r); - CALIBDATA = r; - startdraw(); - setButtons(); + restart() }); } else { - startdraw(); - setTimeout(setButtons,1000); - } + restart() + } } if (first===undefined) first=false; stopdraw(); - clearWatch(); if (first) E.showAlert(msg,title).then(action.bind(null,true)); else E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action); } -Bangle.on('touch', function(b) { - if(!candraw) return; - if(b==1) brg=heading; - if(b==2) brg=null; - }); - var intervalRef; function startdraw(){ @@ -176,29 +174,17 @@ function stopdraw() { } function setButtons(){ - setWatch(()=>{load();}, BTN1, {repeat:false,edge:"falling"}); - setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); - setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"}); + function actions(v){ + if (!v) docalibrate(false); + else if (v==1) brg=null; + else brg=heading; + } + Bangle.setUI("updown",actions); } -var SCREENACCESS = { - withApp:true, - request:function(){ - this.withApp=false; - stopdraw(); - clearWatch(); - }, - release:function(){ - this.withApp=true; - startdraw(); - setButtons(); - } -}; - Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; if (on) { - startdraw(); + if (!calibrating) startdraw(); } else { stopdraw(); } @@ -209,7 +195,7 @@ Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); Bangle.loadWidgets(); Bangle.setCompassPower(1); if (!CALIBDATA) - docalibrate({},true); + docalibrate(true); else { startdraw(); setButtons(); @@ -217,4 +203,3 @@ else { - diff --git a/apps/magnav/magnav_b2.js b/apps/magnav/magnav_b2.js new file mode 100644 index 000000000..e54280796 --- /dev/null +++ b/apps/magnav/magnav_b2.js @@ -0,0 +1,192 @@ + +const Ypos = 40; + +const labels = ["N","NE","E","SE","S","SW","W","NW"]; +var brg=null; + +function drawCompass(course) { + "ram" + g.setColor(g.theme.fg); + g.setFont("Vector",18); + var start = course-90; + if (start<0) start+=360; + g.fillRect(16,Ypos+45,160,Ypos+49); + var xpos = 16; + var frag = 15 - start%15; + if (frag<15) xpos+=Math.floor((frag*4)/5); else frag = 0; + for (var i=frag;i<=180-frag;i+=15){ + var res = start + i; + if (res%90==0) { + g.drawString(labels[Math.floor(res/45)%8],xpos-6,Ypos+6); + g.fillRect(xpos-2,Ypos+25,xpos+2,Ypos+45); + } else if (res%45==0) { + g.drawString(labels[Math.floor(res/45)%8],xpos-9,Ypos+6); + g.fillRect(xpos-2,Ypos+30,xpos+2,Ypos+45); + } else if (res%15==0) { + g.fillRect(xpos,Ypos+35,xpos+1,Ypos+45); + } + xpos+=12; + } + if (brg) { + var bpos = brg - course; + if (bpos>180) bpos -=360; + if (bpos<-180) bpos +=360; + bpos= Math.floor((bpos*4)/5)+88; + if (bpos<16) bpos = 8; + if (bpos>160) bpos = 170; + g.setColor(g.theme.fg2); + g.fillCircle(bpos,Ypos+45,6); + } +} + +var heading = 0; +function newHeading(m,h){ + var s = Math.abs(m - h); + var delta = (m>h)?1:-1; + if (s>=180){s=360-s; delta = -delta;} + if (s<2) return h; + var hd = h + delta*(1 + Math.round(s/5)); + if (hd<0) hd+=360; + if (hd>360)hd-= 360; + return hd; +} + +var candraw = false; +var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; + +function tiltfixread(O,S){ + "ram" + var m = Bangle.getCompass(); + var g = Bangle.getAccel(); + m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z; + var d = Math.atan2(-m.dx,m.dy)*180/Math.PI; + if (d<0) d+=360; + var phi = Math.atan(-g.x/-g.z); + var cosphi = Math.cos(phi), sinphi = Math.sin(phi); + var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi)); + var costheta = Math.cos(theta), sintheta = Math.sin(theta); + var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta; + var yh = m.dz*sinphi - m.dx*cosphi; + var psi = Math.atan2(yh,xh)*180/Math.PI; + if (psi<0) psi+=360; + return psi; +} + +// Note actual mag is 360-m, error in firmware +function reading() { + "ram" + g.clearRect(0,24,175,175); + var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale); + heading = newHeading(d,heading); + drawCompass(heading); + g.setColor(g.theme.fg); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + g.drawString("o",120,Ypos+80); + g.setFont("Vector",40); + var course = Math.round(heading); + var cs = course.toString(); + cs = course<10?"00"+cs : course<100 ?"0"+cs : cs; + g.drawString(cs,50,Ypos+90); + g.setColor(g.theme.fg2); + g.fillPoly([88,Ypos+60,78,Ypos+80,98,Ypos+80]); + g.setColor(g.theme.fg); + g.flip(); +} + +function calibrate(){ + var max={x:-32000, y:-32000, z:-32000}, + min={x:32000, y:32000, z:32000}; + var ref = setInterval(()=>{ + var m = Bangle.getCompass(); + max.x = m.x>max.x?m.x:max.x; + max.y = m.y>max.y?m.y:max.y; + max.z = m.z>max.z?m.z:max.z; + min.x = m.x { + setTimeout(()=>{ + if(ref) clearInterval(ref); + var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2}; + var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2}; + var avg = (delta.x+delta.y+delta.z)/3; + var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z}; + resolve({offset:offset,scale:scale}); + },20000); + }); +} + +var calibrating=false; +function docalibrate(first){ + calibrating=true; + const title = "Calibrate"; + const msg = "takes 20 seconds"; + function restart() { + calibrating=false; + setButtons(); + startdraw(); + } + function action(b){ + if (b) { + g.clearRect(0,24,175,175); + g.setColor(g.theme.fg); + g.setFont("Vector",18); + g.setFontAlign(0,-1); + g.drawString("Fig 8s to",88,Ypos); + g.drawString("Calibrate",88,Ypos+18); + g.flip(); + calibrate().then((r)=>{ + CALIBDATA=r; + require("Storage").write("magnav.json",r); + restart(); + }); + } else { + restart(); + } + } + if (first===undefined) first=false; + stopdraw(); + if (first) + E.showAlert(msg,title).then(action.bind(null,true)); + else + E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action); +} + +var intervalRef; + +function startdraw(){ + g.clear(1); + Bangle.drawWidgets(); + candraw = true; + intervalRef = setInterval(reading,200); +} + +function stopdraw() { + candraw=false; + if(intervalRef) {clearInterval(intervalRef);} +} + +function setButtons(){ + function actions(v){ + if (!v) docalibrate(false); + else if (v==1) brg=null; + else brg=heading; + } + Bangle.setUI("updown",actions); +} + +Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); + +Bangle.loadWidgets(); +Bangle.setCompassPower(1); +if (!CALIBDATA) + docalibrate(true); +else { + startdraw(); + setButtons(); +} + + + diff --git a/apps/magnav/screenshot-b2.png b/apps/magnav/screenshot-b2.png new file mode 100644 index 000000000..63f830bfc Binary files /dev/null and b/apps/magnav/screenshot-b2.png differ diff --git a/apps/magnav/screenshot-light-b2.png b/apps/magnav/screenshot-light-b2.png new file mode 100644 index 000000000..943dc392c Binary files /dev/null and b/apps/magnav/screenshot-light-b2.png differ diff --git a/apps/messages/app.js b/apps/messages/app.js index 6c7cf5fc9..987d9184b 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -61,6 +61,7 @@ function getMessageImage(msg) { if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); if (msg.id=="back") return getBackImage(); diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index 9d02ae85e..442a5277a 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -6,6 +6,7 @@ 0.06: add minute tick for efficiency and nifty A clock 0.07: compatible with Bang;e.js 2 0.08: fix minute tick bug +0.09: use setUI clockupdown for controls + fix small display bug in nifty face diff --git a/apps/multiclock/README.md b/apps/multiclock/README.md index e8b8335ea..25c997329 100644 --- a/apps/multiclock/README.md +++ b/apps/multiclock/README.md @@ -5,7 +5,9 @@ This is a clock app that supports multiple clock faces. The user can switch betw ## Controls -Swipe left and right on both the Bangle and Bangle 2 switch between faces. BTN1 & BTH3 also switch faces on the Bangle. +Uses `setUI("clockupdown")` +BTN1 & BTH3 switch faces on the Bangle. +Touch upper right and lower right quadrant switch faces on the Bangle 2. ## Adding a new face Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure: diff --git a/apps/multiclock/multiclock.app.js b/apps/multiclock/multiclock.app.js index c24e5c94b..0565a7040 100644 --- a/apps/multiclock/multiclock.app.js +++ b/apps/multiclock/multiclock.app.js @@ -67,7 +67,7 @@ function setButtons(){ startdraw(); } } - Bangle.setUI("leftright", newFace); + Bangle.setUI("clockupdown", newFace); } E.on('kill',()=>{ diff --git a/apps/multiclock/nifty.face.js b/apps/multiclock/nifty.face.js index 2c2af6063..54962da34 100644 --- a/apps/multiclock/nifty.face.js +++ b/apps/multiclock/nifty.face.js @@ -28,7 +28,7 @@ var now = new Date(); const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); const minutes = d02(now.getMinutes()); - const day = d02(now.getDay()); + const day = d02(now.getDate()); const month = d02(now.getMonth() + 1); const year = now.getFullYear(); const month2 = locale.month(now, 3); diff --git a/apps/multiclock/screen-ana.png b/apps/multiclock/screen-ana.png new file mode 100644 index 000000000..67d794aa3 Binary files /dev/null and b/apps/multiclock/screen-ana.png differ diff --git a/apps/multiclock/screen-big.png b/apps/multiclock/screen-big.png new file mode 100644 index 000000000..80544d552 Binary files /dev/null and b/apps/multiclock/screen-big.png differ diff --git a/apps/multiclock/screen-date.png b/apps/multiclock/screen-date.png new file mode 100644 index 000000000..21093f458 Binary files /dev/null and b/apps/multiclock/screen-date.png differ diff --git a/apps/multiclock/screen-nifty.png b/apps/multiclock/screen-nifty.png new file mode 100644 index 000000000..884456125 Binary files /dev/null and b/apps/multiclock/screen-nifty.png differ diff --git a/apps/multiclock/screen-sec.png b/apps/multiclock/screen-sec.png new file mode 100644 index 000000000..cc1149254 Binary files /dev/null and b/apps/multiclock/screen-sec.png differ diff --git a/apps/multiclock/screen-td.png b/apps/multiclock/screen-td.png new file mode 100644 index 000000000..edea06a2e Binary files /dev/null and b/apps/multiclock/screen-td.png differ diff --git a/apps/multiclock/screen-word.png b/apps/multiclock/screen-word.png new file mode 100644 index 000000000..ad029a60f Binary files /dev/null and b/apps/multiclock/screen-word.png differ diff --git a/apps/qmsched/ChangeLog b/apps/qmsched/ChangeLog index 8bae1dba0..27b5421e8 100644 --- a/apps/qmsched/ChangeLog +++ b/apps/qmsched/ChangeLog @@ -1,3 +1,3 @@ 0.01: First version 0.02: Add widget - +0.03: Bangle.js 2 support diff --git a/apps/qmsched/app.js b/apps/qmsched/app.js index 105e09ea6..c6377d4ba 100644 --- a/apps/qmsched/app.js +++ b/apps/qmsched/app.js @@ -32,17 +32,16 @@ function formatTime(t) { } function showMainMenu() { - const menu = { - "": {"title": "Quiet Mode"}, - "Current Mode": { - value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0, - format: v => modeNames[v], - onchange: function(v) { - if (v<0) {v = 2;} - if (v>2) {v = 0;} - require("qmsched").setMode(v); - this.value = v; - }, + let menu = {"": {"title": "Quiet Mode"}}; + // "Current Mode""Silent" won't fit on Bangle.js 2 + menu["Current" + ((process.env.HWVERSION===2)?"":" Mode")]= { + value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0, + format: v => modeNames[v], + onchange: function(v) { + if (v<0) {v = 2;} + if (v>2) {v = 0;} + require("qmsched").setMode(v); + this.value = v; }, }; scheds.sort((a, b) => (a.hr-b.hr)); diff --git a/apps/snake/README.md b/apps/snake/README.md index 483eae7a9..278dffbc4 100644 --- a/apps/snake/README.md +++ b/apps/snake/README.md @@ -7,8 +7,7 @@ Eat apples and don't bite your tail. ## Controls -- UP: BTN1 -- DOWN: BTN3 -- LEFT: BTN4 -- RIGHT: BTN5 -- PAUSE: BTN2 +- BTN1: turn to left +- BTN2: pause +- BTN3: turn to right + diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog index 2286a7f70..c1b4a5fbb 100644 --- a/apps/swiperclocklaunch/ChangeLog +++ b/apps/swiperclocklaunch/ChangeLog @@ -1 +1,2 @@ -0.01: New App! \ No newline at end of file +0.01: New App! +0.02: Fix issue with mode being undefined diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js index 0bb8d588a..e9b203eee 100644 --- a/apps/swiperclocklaunch/boot.js +++ b/apps/swiperclocklaunch/boot.js @@ -3,6 +3,7 @@ var sui = Bangle.setUI; Bangle.setUI = function(mode, cb) { sui(mode,cb); + if(!mode) return; if (!mode.startsWith("clock")) return; Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; Bangle.on("swipe", Bangle.swipeHandler); @@ -14,4 +15,4 @@ setTimeout(function() { Bangle.swipeHandler = dir => { if (dir>0) load(); }; Bangle.on("swipe", Bangle.swipeHandler); } -}, 10); \ No newline at end of file +}, 10); diff --git a/apps/toucher/settings.js b/apps/toucher/settings.js index 6f7320513..51275d846 100644 --- a/apps/toucher/settings.js +++ b/apps/toucher/settings.js @@ -9,7 +9,7 @@ highres: true, animation : true, frame : 3, - debug: true + debug: false }; } @@ -56,4 +56,4 @@ }, '< Back': back }); -}); \ No newline at end of file +}); diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index 8f997a83e..c1a0504a4 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -6,4 +6,5 @@ 0.07: Add theme support and unknown icon. 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 +0.10: Use new Layout library +0.11: Bangle.js 2 support diff --git a/apps/weather/app.js b/apps/weather/app.js index 6a0852f81..8c8526fbd 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -53,8 +53,8 @@ function draw() { 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.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(); diff --git a/apps/weather/lib.js b/apps/weather/lib.js index 299009e74..76ed2aaa4 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -1,4 +1,5 @@ const storage = require('Storage'); +const B2 = process.env.HWVERSION===2; let expiryTimeout; function scheduleExpiry(json) { @@ -54,13 +55,13 @@ scheduleExpiry(storage.readJSON('weather.json')||{}); exports.drawIcon = function(cond, x, y, r) { function drawSun(x, y, r) { - g.setColor(g.theme.dark ? "#FE0" : "#FC0"); + g.setColor(B2 ? '#FF0' : (g.theme.dark ? "#FE0" : "#FC0")); g.fillCircle(x, y, r); } function drawCloud(x, y, r, c) { const u = r/12; - if (c==null) c = g.theme.dark ? "#BBB" : "#AAA"; + if (c==null) c = B2 ? '#FFF': (g.theme.dark ? "#BBB" : "#AAA"); g.setColor(c); g.fillCircle(x-8*u, y+3*u, 4*u); g.fillCircle(x-4*u, y-2*u, 5*u); @@ -77,7 +78,7 @@ exports.drawIcon = function(cond, x, y, r) { } function drawBrokenClouds(x, y, r) { - drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777"); + drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777"); // dithers on B2, but that's ok drawCloud(x-1/8*r, y+1/8*r, 7/8*r); } @@ -87,23 +88,23 @@ exports.drawIcon = function(cond, x, y, r) { } function drawRainLines(x, y, r) { - g.setColor(g.theme.dark ? "#0CF" : "#07F"); + g.setColor(B2 ? '#0FF' : (g.theme.dark ? "#0CF" : "#07F")); const y1 = y+1/2*r; const y2 = y+1*r; - - g.fillPolyAA([ + const poly = g.fillPolyAA ? p => g.fillPolyAA(p) : p => g.fillPoly(p); + poly([ x-6/12*r, y1, x-8/12*r, y2, x-7/12*r, y2, x-5/12*r, y1, ]); - g.fillPolyAA([ + poly([ x-2/12*r, y1, x-4/12*r, y2, x-3/12*r, y2, x-1/12*r, y1, ]); - g.fillPolyAA([ + poly([ x+2/12*r, y1, x+0/12*r, y2, x+1/12*r, y2, @@ -123,7 +124,7 @@ exports.drawIcon = function(cond, x, y, r) { function drawThunderstorm(x, y, r) { function drawLightning(x, y, r) { - g.setColor(g.theme.dark ? "#FE0" : "#FC0"); + g.setColor(B2 ? '#FF0' : (g.theme.dark ? "#FE0" : "#FC0")); g.fillPoly([ x-2/6*r, y-r, x-4/6*r, y+1/6*r, @@ -151,7 +152,7 @@ exports.drawIcon = function(cond, x, y, r) { } } - g.setColor(g.theme.dark ? "#FFF" : "#CCC"); + g.setColor(B2 ? '#FFF' : (g.theme.dark ? "#FFF" : "#CCC")); const w = 1/12*r; for(let i = 0; i<=6; ++i) { const points = [ @@ -186,7 +187,7 @@ exports.drawIcon = function(cond, x, y, r) { [-0.2, 0.3], ]; - g.setColor(g.theme.dark ? "#FFF" : "#CCC"); + g.setColor(B2 ? '#FFF' : (g.theme.dark ? "#FFF" : "#CCC")); for(let i = 0; i<5; ++i) { g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r, y+(0.4*i-0.7)*r-1); @@ -196,7 +197,7 @@ exports.drawIcon = function(cond, x, y, r) { } function drawUnknown(x, y, r) { - drawCloud(x, y, r, "#777"); + drawCloud(x, y, r, "#777"); // dithers on B2, but that's ok g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6); } diff --git a/apps/wid_a_battery_widget/ChangeLog b/apps/wid_a_battery_widget/ChangeLog new file mode 100644 index 000000000..9b0649c27 --- /dev/null +++ b/apps/wid_a_battery_widget/ChangeLog @@ -0,0 +1,2 @@ +1.00: Release for Bangle 2 (2021/11/18) +1.01: Internal id update to wid_* as per Gordon's request (2021/11/21) diff --git a/apps/a_battery_widget/README.md b/apps/wid_a_battery_widget/README.md similarity index 100% rename from apps/a_battery_widget/README.md rename to apps/wid_a_battery_widget/README.md diff --git a/apps/a_battery_widget/a_battery_widget-pic.jpg b/apps/wid_a_battery_widget/a_battery_widget-pic.jpg similarity index 100% rename from apps/a_battery_widget/a_battery_widget-pic.jpg rename to apps/wid_a_battery_widget/a_battery_widget-pic.jpg diff --git a/apps/a_battery_widget/widget.js b/apps/wid_a_battery_widget/widget.js similarity index 88% rename from apps/a_battery_widget/widget.js rename to apps/wid_a_battery_widget/widget.js index d6f8236d4..9fb06e320 100644 --- a/apps/a_battery_widget/widget.js +++ b/apps/wid_a_battery_widget/widget.js @@ -39,7 +39,7 @@ } Bangle.on('charging',function(charging) { draw(); }); - setInterval(()=>WIDGETS["a_battery_widget"].draw(), 60000); + setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), 60000); - WIDGETS["a_battery_widget"]={area:"tr",width:30,draw:draw}; + WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw}; })(); diff --git a/apps/a_battery_widget/widget.png b/apps/wid_a_battery_widget/widget.png similarity index 100% rename from apps/a_battery_widget/widget.png rename to apps/wid_a_battery_widget/widget.png diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index a84d26efd..ea45dc19b 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -209,6 +209,8 @@ apps.forEach((app,appIdx) => { // prefer "appid.json" over "appid.settings.json" (TODO: change to ERROR once all apps comply?) if (dataNames.includes(app.id+".settings.json") && !dataNames.includes(app.id+".json")) WARN(`App ${app.id} uses data file ${app.id+'.settings.json'} instead of ${app.id+'.json'}`) + else if (dataNames.includes(app.id+".settings.json")) + WARN(`App ${app.id} uses data file ${app.id+'.settings.json'}`) // settings files should be listed under data, not storage (TODO: change to ERROR once all apps comply?) if (fileNames.includes(app.id+".settings.json")) WARN(`App ${app.id} uses storage file ${app.id+'.settings.json'} instead of data file`) diff --git a/modules/Settings.js b/modules/Settings.js new file mode 100644 index 000000000..8d7fba653 --- /dev/null +++ b/modules/Settings.js @@ -0,0 +1,101 @@ +/* +- Read/write app settings, stored in .json +- Read/write global settings (stored in setting.json) + +Usage: +``` +// read a single app setting +value = require('Settings').get(appid, key, default); +// omit key to read all app settings +value = require('Settings').get(); +// write a single app setting +require('Settings').set(appid, key, value) +// omit key and pass an object as values to overwrite all settings +require('Settings').set(appid, values) + +// read Bangle settings by passing the Bangle object instead of an app name +value = require('Settings').get(Bangle, key, default); +// read all global settings +values = require('Settings').get(Bangle); +// write a global setting +require('Settings').set(Bangle, key, value) +``` + +For example: +``` +require('Settings').set('test', 'foo', 123); // writes to 'test.json' +require('Settings').set('test', 'bar', 456); // updates 'test.json' +// 'test.json' now contains {baz:123,bam:456} +baz = require('Settings').get('test', 'foo'); // baz = 123 +def = require('Settings').get('test', 'jkl', 789); // def = 789 +all = require('Settings').get('test'); // all = {foo: 123, bar: 456} +baz = require('Settings').get('test', 'baz'); // baz = undefined + +// read global setting +vibrate = require('Settings').get(Bangle, 'vibrate', true); + +// Hint: if your app reads multiple settings, you can create a helper function: +function s(key, def) { return require('Settings').get('myapp', key, def); } +var foo = s('foo setting', 'default value'), bar = s('bar setting'); +``` + +*/ + +/** + * Read setting value from file + * + * @param {string} file Settings file + * @param {string} key Setting to get, omit to get all settings as object + * @param {*} def Default value + * @return {*} Setting value (or default if not found) + */ +function get(file, key, def) { + var s = require("Storage").readJSON(file); + if (def===undefined && ["object", "undefined"].includes(typeof key)) { + // get(file) or get(file, def): get all settings + return (s!==undefined) ? s : key; + } + return ((typeof s==="object") && (key in s)) ? s[key] : def; +} + +/** + * Write setting value to file + * + * @param {string} file Settings file + * @param {string} key Setting to change, omit to replace all settings + * @param {*} value Value to store + */ +function set(file, key, value) { + if (value===undefined && typeof key==="object") { + // set(file, value): overwrite settings completely + require("Storage").writeJSON(file, key); + return; + } + var s = require("Storage").readJSON(file, 1); + if (typeof s!=="object") s = {}; + s[key] = value; + require("Storage").write(file, s); +} + +/** + * Read setting value + * + * @param {string|object} app App name or Bangle + * @param {string} key Setting to get, omit to get all settings as object + * @param {*} def Default value + * @return {*} Setting value (or default if not found) + */ +exports.get = function(app, key, def) { + return get((app===Bangle) ? 'setting.json' : app+".json", key, def); +}; + +/** + * Write setting value + * + * @param {string|object} app App name or Bangle + * @param {string} key Setting to change, omit to replace all settings + * @param {*} val Value to store + */ +exports.set = function(app, key, val) { + set((app===Bangle) ? 'setting.json' : app+".json", key, val); +};