2022-03-20 09:36:34 +00:00
|
|
|
const COUNTER_TRIANGLE_SIZE = 10;
|
2022-03-09 13:53:24 +00:00
|
|
|
const TOKEN_EXTRA_HEIGHT = 16;
|
|
|
|
var TOKEN_DIGITS_HEIGHT = 30;
|
|
|
|
var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT;
|
2022-03-22 15:16:17 +00:00
|
|
|
const SETTINGS = "authentiwatch.json";
|
2021-10-29 13:37:38 +00:00
|
|
|
// Hash functions
|
|
|
|
const crypto = require("crypto");
|
2021-11-09 15:24:05 +00:00
|
|
|
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 },
|
|
|
|
};
|
2022-03-09 13:53:24 +00:00
|
|
|
const CALCULATING = /*LANG*/"Calculating";
|
|
|
|
const NO_TOKENS = /*LANG*/"No tokens";
|
|
|
|
const NOT_SUPPORTED = /*LANG*/"Not supported";
|
2021-10-29 13:37:38 +00:00
|
|
|
|
2021-12-03 04:24:46 +00:00
|
|
|
// sample settings:
|
|
|
|
// {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}}
|
2022-03-23 06:22:17 +00:00
|
|
|
var settings = require("Storage").readJSON(SETTINGS, true) || {tokens:[], misc:{}};
|
2021-12-01 14:05:53 +00:00
|
|
|
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
|
|
|
|
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
|
2021-10-29 13:37:38 +00:00
|
|
|
|
|
|
|
function b32decode(seedstr) {
|
|
|
|
// RFC4648
|
2022-03-02 08:55:38 +00:00
|
|
|
var buf = 0, bitcount = 0, retstr = "";
|
|
|
|
for (var c of seedstr.toUpperCase()) {
|
2022-03-07 15:33:23 +00:00
|
|
|
if (c == '0') c = 'O';
|
|
|
|
if (c == '1') c = 'I';
|
|
|
|
if (c == '8') c = 'B';
|
2022-03-02 08:55:38 +00:00
|
|
|
c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(c);
|
2021-10-29 13:37:38 +00:00
|
|
|
if (c != -1) {
|
|
|
|
buf <<= 5;
|
|
|
|
buf |= c;
|
|
|
|
bitcount += 5;
|
|
|
|
if (bitcount >= 8) {
|
|
|
|
retstr += String.fromCharCode(buf >> (bitcount - 8));
|
|
|
|
buf &= (0xFF >> (16 - bitcount));
|
|
|
|
bitcount -= 8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var retbuf = new Uint8Array(retstr.length);
|
2022-03-02 08:55:38 +00:00
|
|
|
for (var i in retstr) {
|
2021-10-29 13:37:38 +00:00
|
|
|
retbuf[i] = retstr.charCodeAt(i);
|
|
|
|
}
|
|
|
|
return retbuf;
|
|
|
|
}
|
2022-03-09 13:53:24 +00:00
|
|
|
|
2022-03-20 09:36:34 +00:00
|
|
|
function hmac(key, message, algo) {
|
|
|
|
var a = algos[algo.toUpperCase()];
|
2021-10-29 13:37:38 +00:00
|
|
|
// RFC2104
|
2021-11-09 15:24:05 +00:00
|
|
|
if (key.length > a.blksz) {
|
|
|
|
key = a.sha(key);
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
2021-11-09 15:24:05 +00:00
|
|
|
var istr = new Uint8Array(a.blksz + message.length);
|
|
|
|
var ostr = new Uint8Array(a.blksz + a.retsz);
|
|
|
|
for (var i = 0; i < a.blksz; ++i) {
|
2021-10-31 14:15:25 +00:00
|
|
|
var c = (i < key.length) ? key[i] : 0;
|
2021-10-29 13:37:38 +00:00
|
|
|
istr[i] = c ^ 0x36;
|
|
|
|
ostr[i] = c ^ 0x5C;
|
|
|
|
}
|
2021-11-09 15:24:05 +00:00
|
|
|
istr.set(message, a.blksz);
|
|
|
|
ostr.set(a.sha(istr), a.blksz);
|
|
|
|
var ret = a.sha(ostr);
|
2021-10-29 13:37:38 +00:00
|
|
|
// RFC4226 dynamic truncation
|
|
|
|
var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
|
|
|
|
return v.getUint32(0) & 0x7FFFFFFF;
|
|
|
|
}
|
2022-03-09 13:53:24 +00:00
|
|
|
|
2022-03-08 13:52:57 +00:00
|
|
|
function formatOtp(otp, digits) {
|
2022-03-20 09:36:34 +00:00
|
|
|
// add 0 padding
|
|
|
|
var ret = "" + otp % Math.pow(10, digits);
|
|
|
|
while (ret.length < digits) {
|
|
|
|
ret = "0" + ret;
|
|
|
|
}
|
|
|
|
// add a space after every 3rd or 4th digit
|
2022-03-08 13:52:57 +00:00
|
|
|
var re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : ".";
|
2022-03-20 09:36:34 +00:00
|
|
|
return ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim();
|
2022-03-08 13:52:57 +00:00
|
|
|
}
|
2022-03-09 13:53:24 +00:00
|
|
|
|
2022-03-20 09:36:34 +00:00
|
|
|
function hotp(token) {
|
|
|
|
var d = Date.now();
|
|
|
|
var tick, next;
|
2021-11-12 16:58:50 +00:00
|
|
|
if (token.period > 0) {
|
|
|
|
// RFC6238 - timed
|
2022-03-20 09:36:34 +00:00
|
|
|
var seconds = Math.floor(d / 1000);
|
2021-11-12 16:58:50 +00:00
|
|
|
tick = Math.floor(seconds / token.period);
|
2022-03-20 09:36:34 +00:00
|
|
|
next = (tick + 1) * token.period * 1000;
|
2021-11-12 16:58:50 +00:00
|
|
|
} else {
|
|
|
|
// RFC4226 - counter
|
|
|
|
tick = -token.period;
|
2022-03-20 09:36:34 +00:00
|
|
|
next = d + 30000;
|
2021-11-12 16:58:50 +00:00
|
|
|
}
|
2021-10-29 13:37:38 +00:00
|
|
|
var msg = new Uint8Array(8);
|
|
|
|
var v = new DataView(msg.buffer);
|
|
|
|
v.setUint32(0, tick >> 16 >> 16);
|
|
|
|
v.setUint32(4, tick & 0xFFFFFFFF);
|
2022-03-20 09:36:34 +00:00
|
|
|
var ret;
|
|
|
|
try {
|
|
|
|
ret = hmac(b32decode(token.secret), msg, token.algorithm);
|
|
|
|
ret = formatOtp(ret, token.digits);
|
|
|
|
} catch(err) {
|
|
|
|
ret = NOT_SUPPORTED;
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
2022-03-20 09:36:34 +00:00
|
|
|
return {hotp:ret, next:next};
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 09:36:34 +00:00
|
|
|
// Tokens are displayed in three states:
|
2022-03-23 06:22:17 +00:00
|
|
|
// 1. Unselected (state.id<0)
|
|
|
|
// 2. Selected, inactive (no code) (state.id>=0,state.hotp.hotp=="")
|
|
|
|
// 3. Selected, active (code showing) (state.id>=0,state.hotp.hotp!="")
|
2022-03-09 13:53:24 +00:00
|
|
|
var fontszCache = {};
|
2021-10-29 13:37:38 +00:00
|
|
|
var state = {
|
2022-03-20 09:36:34 +00:00
|
|
|
listy:0, // list scroll position
|
|
|
|
id:-1, // current token ID
|
|
|
|
hotp:{hotp:"",next:0}
|
2021-10-29 13:37:38 +00:00
|
|
|
};
|
|
|
|
|
2022-03-09 13:53:24 +00:00
|
|
|
function sizeFont(id, txt, w) {
|
|
|
|
var sz = fontszCache[id];
|
2022-03-08 13:52:57 +00:00
|
|
|
if (sz) {
|
|
|
|
g.setFont("Vector", sz);
|
|
|
|
} else {
|
2022-03-09 13:53:24 +00:00
|
|
|
sz = TOKEN_DIGITS_HEIGHT;
|
2022-03-08 13:52:57 +00:00
|
|
|
do {
|
|
|
|
g.setFont("Vector", sz--);
|
|
|
|
} while (g.stringWidth(txt) > w);
|
2022-03-09 13:53:24 +00:00
|
|
|
fontszCache[id] = sz + 1;
|
2022-03-08 13:52:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-20 09:36:34 +00:00
|
|
|
tokenY = id => id * TOKEN_HEIGHT + AR.y - state.listy;
|
|
|
|
half = n => Math.floor(n / 2);
|
|
|
|
|
|
|
|
function timerCalc() {
|
|
|
|
let timerfn = exitApp;
|
|
|
|
let timerdly = 10000;
|
2022-03-23 06:22:17 +00:00
|
|
|
if (state.id >= 0 && state.hotp.hotp != "") {
|
2022-03-22 15:16:17 +00:00
|
|
|
if (tokens[state.id].period > 0) {
|
2022-03-20 15:11:18 +00:00
|
|
|
// timed HOTP
|
|
|
|
if (state.hotp.next < Date.now()) {
|
2022-03-20 09:36:34 +00:00
|
|
|
if (state.cnt > 0) {
|
2022-03-22 15:16:17 +00:00
|
|
|
state.cnt--;
|
|
|
|
state.hotp = hotp(tokens[state.id]);
|
2022-03-20 09:36:34 +00:00
|
|
|
} else {
|
|
|
|
state.hotp.hotp = "";
|
|
|
|
}
|
2022-03-20 15:11:18 +00:00
|
|
|
timerdly = 1;
|
2022-03-20 09:36:34 +00:00
|
|
|
timerfn = updateCurrentToken;
|
2022-03-20 15:11:18 +00:00
|
|
|
} else {
|
|
|
|
timerdly = 1000;
|
|
|
|
timerfn = updateProgressBar;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// counter HOTP
|
|
|
|
if (state.cnt > 0) {
|
2022-03-22 15:16:17 +00:00
|
|
|
state.cnt--;
|
2022-03-20 15:11:18 +00:00
|
|
|
timerdly = 30000;
|
|
|
|
} else {
|
|
|
|
state.hotp.hotp = "";
|
|
|
|
timerdly = 1;
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
2022-03-20 15:11:18 +00:00
|
|
|
timerfn = updateCurrentToken;
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state.drawtimer) {
|
|
|
|
clearTimeout(state.drawtimer);
|
|
|
|
}
|
|
|
|
state.drawtimer = setTimeout(timerfn, timerdly);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateCurrentToken() {
|
|
|
|
drawToken(state.id);
|
|
|
|
timerCalc();
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateProgressBar() {
|
|
|
|
drawProgressBar();
|
|
|
|
timerCalc();
|
|
|
|
}
|
|
|
|
|
|
|
|
function drawProgressBar() {
|
|
|
|
let id = state.id;
|
2022-03-23 06:22:17 +00:00
|
|
|
if (id >= 0 && tokens[id].period > 0) {
|
2022-03-23 08:13:57 +00:00
|
|
|
let rem = Math.min(tokens[id].period, Math.floor((state.hotp.next - Date.now()) / 1000));
|
2022-03-20 15:11:18 +00:00
|
|
|
if (rem >= 0) {
|
|
|
|
let y1 = tokenY(id);
|
|
|
|
let y2 = y1 + TOKEN_HEIGHT - 1;
|
|
|
|
if (y2 >= AR.y && y1 <= AR.y2) {
|
|
|
|
// token visible
|
|
|
|
if ((y2 - 3) <= AR.y2)
|
|
|
|
{
|
|
|
|
// progress bar visible
|
|
|
|
y2 = Math.min(y2, AR.y2);
|
|
|
|
let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x;
|
|
|
|
g.setColor(g.theme.fgH)
|
|
|
|
.setBgColor(g.theme.bgH)
|
|
|
|
.fillRect(AR.x, y2 - 3, xr, y2)
|
|
|
|
.clearRect(xr + 1, y2 - 3, AR.x2, y2);
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
2022-03-20 15:11:18 +00:00
|
|
|
} else {
|
|
|
|
// token not visible
|
|
|
|
state.id = -1;
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// id = token ID number (0...)
|
|
|
|
function drawToken(id) {
|
2022-03-23 08:13:57 +00:00
|
|
|
let x1 = AR.x;
|
|
|
|
let y1 = tokenY(id);
|
|
|
|
let x2 = AR.x2;
|
|
|
|
let y2 = y1 + TOKEN_HEIGHT - 1;
|
|
|
|
let lbl = (id >= 0 && id < tokens.length) ? tokens[id].label.substr(0, 10) : "";
|
|
|
|
let adj;
|
2022-03-20 09:36:34 +00:00
|
|
|
g.setClipRect(x1, Math.max(y1, AR.y), x2, Math.min(y2, AR.y2));
|
|
|
|
if (id === state.id) {
|
2022-03-01 14:02:13 +00:00
|
|
|
g.setColor(g.theme.fgH)
|
2022-03-20 09:36:34 +00:00
|
|
|
.setBgColor(g.theme.bgH);
|
2021-10-29 13:37:38 +00:00
|
|
|
} else {
|
2022-03-01 14:02:13 +00:00
|
|
|
g.setColor(g.theme.fg)
|
|
|
|
.setBgColor(g.theme.bg);
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
|
|
|
if (id == state.id && state.hotp.hotp != "") {
|
|
|
|
// small label centered just below top line
|
|
|
|
g.setFont("Vector", TOKEN_EXTRA_HEIGHT)
|
|
|
|
.setFontAlign(0, -1, 0);
|
|
|
|
adj = y1;
|
|
|
|
} else {
|
|
|
|
// large label centered in box
|
|
|
|
sizeFont("l" + id, lbl, AR.w);
|
2021-10-29 13:37:38 +00:00
|
|
|
g.setFontAlign(0, 0, 0);
|
2022-03-20 09:36:34 +00:00
|
|
|
adj = half(y1 + y2);
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
2022-03-01 14:02:13 +00:00
|
|
|
g.clearRect(x1, y1, x2, y2)
|
2022-03-20 09:36:34 +00:00
|
|
|
.drawString(lbl, half(x1 + x2), adj, false);
|
|
|
|
if (id == state.id && state.hotp.hotp != "") {
|
|
|
|
adj = 0;
|
|
|
|
if (tokens[id].period <= 0) {
|
2021-11-13 04:04:30 +00:00
|
|
|
// counter - draw triangle as swipe hint
|
2022-03-20 09:36:34 +00:00
|
|
|
let yc = half(y1 + y2);
|
|
|
|
adj = COUNTER_TRIANGLE_SIZE;
|
|
|
|
g.fillPoly([AR.x, yc, AR.x + adj, yc - adj, AR.x + adj, yc + adj]);
|
|
|
|
adj += 2;
|
2021-11-13 04:04:30 +00:00
|
|
|
}
|
2021-10-29 13:37:38 +00:00
|
|
|
// digits just below label
|
2022-03-20 09:36:34 +00:00
|
|
|
x1 = half(x1 + adj + x2);
|
|
|
|
y1 += TOKEN_EXTRA_HEIGHT;
|
|
|
|
if (state.hotp.hotp == CALCULATING) {
|
|
|
|
sizeFont("c", CALCULATING, AR.w - adj);
|
|
|
|
g.drawString(CALCULATING, x1, y1, false)
|
|
|
|
.flip();
|
|
|
|
state.hotp = hotp(tokens[id]);
|
|
|
|
g.clearRect(AR.x + adj, y1, AR.x2, y2);
|
|
|
|
}
|
|
|
|
sizeFont("d" + id, state.hotp.hotp, AR.w - adj);
|
|
|
|
g.drawString(state.hotp.hotp, x1, y1, false);
|
|
|
|
if (tokens[id].period > 0) {
|
|
|
|
drawProgressBar();
|
|
|
|
}
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
2022-03-20 15:11:18 +00:00
|
|
|
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
|
|
|
|
2022-03-22 15:16:17 +00:00
|
|
|
function changeId(id) {
|
|
|
|
if (id != state.id) {
|
|
|
|
state.hotp.hotp = CALCULATING;
|
|
|
|
let pid = state.id;
|
|
|
|
state.id = id;
|
2022-03-23 06:22:17 +00:00
|
|
|
if (pid >= 0) {
|
2022-03-22 15:16:17 +00:00
|
|
|
drawToken(pid);
|
|
|
|
}
|
2022-03-23 06:22:17 +00:00
|
|
|
if (id >= 0) {
|
2022-03-22 15:16:17 +00:00
|
|
|
drawToken( id);
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
2021-12-03 04:02:31 +00:00
|
|
|
}
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function onDrag(e) {
|
2022-03-20 09:36:34 +00:00
|
|
|
state.cnt = 1;
|
|
|
|
if (e.b != 0 && e.dy != 0) {
|
2022-03-23 08:13:57 +00:00
|
|
|
let y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h));
|
2022-03-07 15:33:23 +00:00
|
|
|
if (state.listy != y) {
|
2022-03-23 08:13:57 +00:00
|
|
|
let id, dy = state.listy - y;
|
2022-03-07 15:33:23 +00:00
|
|
|
state.listy = y;
|
2022-03-20 09:36:34 +00:00
|
|
|
g.setClipRect(AR.x, AR.y, AR.x2, AR.y2)
|
2022-03-07 15:33:23 +00:00
|
|
|
.scroll(0, dy);
|
|
|
|
if (dy > 0) {
|
2022-03-09 13:53:24 +00:00
|
|
|
id = Math.floor((state.listy + dy) / TOKEN_HEIGHT);
|
2022-03-20 09:36:34 +00:00
|
|
|
y = tokenY(id + 1);
|
2022-03-07 15:33:23 +00:00
|
|
|
do {
|
2022-03-20 09:36:34 +00:00
|
|
|
drawToken(id);
|
2022-03-07 15:33:23 +00:00
|
|
|
id--;
|
2022-03-09 13:53:24 +00:00
|
|
|
y -= TOKEN_HEIGHT;
|
2022-03-20 09:36:34 +00:00
|
|
|
} while (y > AR.y);
|
2022-03-07 15:33:23 +00:00
|
|
|
}
|
|
|
|
if (dy < 0) {
|
2022-03-20 09:36:34 +00:00
|
|
|
id = Math.floor((state.listy + dy + AR.h) / TOKEN_HEIGHT);
|
|
|
|
y = tokenY(id);
|
|
|
|
while (y < AR.y2) {
|
|
|
|
drawToken(id);
|
2022-03-07 15:33:23 +00:00
|
|
|
id++;
|
2022-03-09 13:53:24 +00:00
|
|
|
y += TOKEN_HEIGHT;
|
2022-03-07 15:33:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-20 09:36:34 +00:00
|
|
|
if (e.b == 0) {
|
|
|
|
timerCalc();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onTouch(zone, e) {
|
|
|
|
state.cnt = 1;
|
|
|
|
if (e) {
|
2022-03-23 08:13:57 +00:00
|
|
|
let id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT);
|
2022-03-20 09:36:34 +00:00
|
|
|
if (id == state.id || tokens.length == 0 || id >= tokens.length) {
|
|
|
|
id = -1;
|
|
|
|
}
|
|
|
|
if (state.id != id) {
|
2022-03-23 06:22:17 +00:00
|
|
|
if (id >= 0) {
|
2022-03-20 09:36:34 +00:00
|
|
|
// scroll token into view if necessary
|
2022-03-23 08:13:57 +00:00
|
|
|
let dy = 0;
|
|
|
|
let y = id * TOKEN_HEIGHT - state.listy;
|
2022-03-20 09:36:34 +00:00
|
|
|
if (y < 0) {
|
2022-03-22 15:16:17 +00:00
|
|
|
dy -= y;
|
2022-03-20 09:36:34 +00:00
|
|
|
y = 0;
|
|
|
|
}
|
|
|
|
y += TOKEN_HEIGHT;
|
|
|
|
if (y > AR.h) {
|
2022-03-22 15:16:17 +00:00
|
|
|
dy -= (y - AR.h);
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
2022-03-22 15:16:17 +00:00
|
|
|
onDrag({b:1, dy:dy});
|
2022-03-20 09:36:34 +00:00
|
|
|
}
|
|
|
|
changeId(id);
|
|
|
|
}
|
|
|
|
}
|
2022-03-22 15:16:17 +00:00
|
|
|
timerCalc();
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function onSwipe(e) {
|
2022-03-20 09:36:34 +00:00
|
|
|
state.cnt = 1;
|
2022-03-22 15:16:17 +00:00
|
|
|
switch (e) {
|
|
|
|
case 1:
|
2021-12-31 07:56:39 +00:00
|
|
|
exitApp();
|
2022-03-22 15:16:17 +00:00
|
|
|
break;
|
|
|
|
case -1:
|
2022-03-23 06:22:17 +00:00
|
|
|
if (state.id >= 0 && tokens[state.id].period <= 0) {
|
2022-03-22 15:16:17 +00:00
|
|
|
tokens[state.id].period--;
|
|
|
|
require("Storage").writeJSON(SETTINGS, {tokens:tokens, misc:settings.misc});
|
|
|
|
state.hotp.hotp = CALCULATING;
|
|
|
|
drawToken(state.id);
|
|
|
|
}
|
2021-12-31 03:39:22 +00:00
|
|
|
}
|
2022-03-22 15:16:17 +00:00
|
|
|
timerCalc();
|
2021-10-29 13:37:38 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 08:13:57 +00:00
|
|
|
function bangleBtn(e) {
|
2022-03-20 09:36:34 +00:00
|
|
|
state.cnt = 1;
|
2021-11-13 09:44:32 +00:00
|
|
|
if (tokens.length > 0) {
|
2022-03-23 08:13:57 +00:00
|
|
|
let id = state.id;
|
2022-03-20 09:36:34 +00:00
|
|
|
switch (e) {
|
|
|
|
case -1: id--; break;
|
|
|
|
case 1: id++; break;
|
2021-11-13 09:44:32 +00:00
|
|
|
}
|
2022-03-20 09:36:34 +00:00
|
|
|
id = E.clip(id, 0, tokens.length - 1);
|
2022-03-23 08:13:57 +00:00
|
|
|
onDrag({b:1, dy:state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h))});
|
2022-03-22 15:16:17 +00:00
|
|
|
changeId(id);
|
2022-03-22 15:26:50 +00:00
|
|
|
drawProgressBar();
|
2021-11-13 09:44:32 +00:00
|
|
|
}
|
2022-03-22 15:16:17 +00:00
|
|
|
timerCalc();
|
2021-11-13 09:44:32 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 14:39:39 +00:00
|
|
|
function exitApp() {
|
2022-03-22 15:26:50 +00:00
|
|
|
if (state.drawtimer) {
|
|
|
|
clearTimeout(state.drawtimer);
|
|
|
|
}
|
2021-12-03 14:39:39 +00:00
|
|
|
Bangle.showLauncher();
|
|
|
|
}
|
|
|
|
|
2021-10-29 13:37:38 +00:00
|
|
|
Bangle.on('touch', onTouch);
|
|
|
|
Bangle.on('drag' , onDrag );
|
|
|
|
Bangle.on('swipe', onSwipe);
|
2022-03-23 08:13:57 +00:00
|
|
|
if (typeof BTN1 == 'number') {
|
|
|
|
if (typeof BTN2 == 'number' && typeof BTN3 == 'number') {
|
|
|
|
setWatch(()=>bangleBtn(-1), BTN1, {edge:"rising" , debounce:50, repeat:true});
|
|
|
|
setWatch(()=>exitApp() , BTN2, {edge:"falling", debounce:50});
|
|
|
|
setWatch(()=>bangleBtn( 1), BTN3, {edge:"rising" , debounce:50, repeat:true});
|
|
|
|
} else {
|
|
|
|
setWatch(()=>exitApp() , BTN1, {edge:"falling", debounce:50});
|
|
|
|
}
|
2021-11-13 09:44:32 +00:00
|
|
|
}
|
2021-10-31 14:15:25 +00:00
|
|
|
Bangle.loadWidgets();
|
2022-03-20 09:36:34 +00:00
|
|
|
const AR = Bangle.appRect;
|
2022-03-22 15:16:17 +00:00
|
|
|
// draw the initial display
|
|
|
|
g.clear();
|
|
|
|
if (tokens.length > 0) {
|
|
|
|
state.listy = AR.h;
|
|
|
|
onDrag({b:1, dy:AR.h});
|
|
|
|
} else {
|
|
|
|
g.setFont("Vector", TOKEN_DIGITS_HEIGHT)
|
|
|
|
.setFontAlign(0, 0, 0)
|
|
|
|
.drawString(NO_TOKENS, AR.x + half(AR.w), AR.y + half(AR.h), false);
|
|
|
|
}
|
|
|
|
timerCalc();
|
2021-10-31 14:15:25 +00:00
|
|
|
Bangle.drawWidgets();
|