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 51e5978ee..8d90a1d1a 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,34 +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": "schoolCalendar", - "name": "School Calendar", - "shortName":"SCalendar", - "icon": "CalenderLogo.png", - "version": "0.01", - "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", - "tags": "tool", - "readme": "README.md", - "custom":"custom.html", - "supports": ["BANGLEJS"], - "screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}], - "storage": [ - {"name":"schoolCalendar.app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} - ], - "data": [ - {"name":"app.json"} - ] + { + "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 @@ + + +
+ + + + + + + + + + + + + + +No watch comms.
+| + + |