diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js
new file mode 100644
index 000000000..58738d04a
--- /dev/null
+++ b/apps/2047pp/2047pp.app.js
@@ -0,0 +1,140 @@
+class TwoK {
+ constructor() {
+ this.b = Array(4).fill().map(() => Array(4).fill(0));
+ this.score = 0;
+ this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"};
+ }
+ drawBRect(x1, y1, x2, y2, th, c, cf, fill) {
+ g.setColor(c);
+ for (i=0; i
4) g.setColor(1, 1, 1);
+ else g.setColor(0, 0, 0);
+ g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7));
+ if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh);
+ }
+ }
+ shift(d) { // +/-1: shift x, +/- 2: shift y
+ var crc = E.CRC32(this.b.toString());
+ if (d==-1) { // shift x left
+ for (y=0; y<4; ++y) {
+ for (x=2; x>=0; x--)
+ if (this.b[y][x]==0) {
+ for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1];
+ this.b[y][3] = 0;
+ }
+ for (x=0; x<3; ++x)
+ if (this.b[y][x]==this.b[y][x+1]) {
+ this.score += 2*this.b[y][x];
+ this.b[y][x] += this.b[y][x+1];
+ for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1];
+ this.b[y][3] = 0;
+ }
+ }
+ }
+ else if (d==1) { // shift x right
+ for (y=0; y<4; ++y) {
+ for (x=1; x<4; x++)
+ if (this.b[y][x]==0) {
+ for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1];
+ this.b[y][0] = 0;
+ }
+ for (x=3; x>0; --x)
+ if (this.b[y][x]==this.b[y][x-1]) {
+ this.score += 2*this.b[y][x];
+ this.b[y][x] += this.b[y][x-1] ;
+ for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1];
+ this.b[y][0] = 0;
+ }
+ }
+ }
+ else if (d==-2) { // shift y down
+ for (x=0; x<4; ++x) {
+ for (y=1; y<4; y++)
+ if (this.b[y][x]==0) {
+ for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x];
+ this.b[0][x] = 0;
+ }
+ for (y=3; y>0; y--)
+ if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) {
+ this.score += 2*this.b[y][x];
+ this.b[y][x] += this.b[y-1][x];
+ for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x];
+ this.b[0][x] = 0;
+ }
+ }
+ }
+ else if (d==2) { // shift y up
+ for (x=0; x<4; ++x) {
+ for (y=2; y>=0; y--)
+ if (this.b[y][x]==0) {
+ for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x];
+ this.b[3][x] = 0;
+ }
+ for (y=0; y<3; ++y)
+ if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) {
+ this.score += 2*this.b[y][x];
+ this.b[y][x] += this.b[y+1][x];
+ for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x];
+ this.b[3][x] = 0;
+ }
+ }
+ }
+ return (E.CRC32(this.b.toString())!=crc);
+ }
+ addDigit() {
+ var d = Math.random()>0.9 ? 4 : 2;
+ var id = Math.floor(Math.random()*16);
+ while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16);
+ this.b[Math.floor(id/4)][id%4] = d;
+ }
+}
+
+function dragHandler(e) {
+ if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) {
+ var res = false;
+ if (Math.abs(e.dx)>Math.abs(e.dy)) {
+ if (e.dx>0) res = twok.shift(1);
+ if (e.dx<0) res = twok.shift(-1);
+ }
+ else {
+ if (e.dy>0) res = twok.shift(-2);
+ if (e.dy<0) res = twok.shift(2);
+ }
+ if (res) twok.addDigit();
+ twok.render();
+ }
+}
+
+function swipeHandler() {
+
+}
+
+function buttonHandler() {
+
+}
+
+var twok = new TwoK();
+twok.addDigit(); twok.addDigit();
+twok.render();
+if (process.env.HWVERSION==2) Bangle.on("drag", dragHandler);
+if (process.env.HWVERSION==1) {
+ Bangle.on("swipe", (e) => { res = twok.shift(e); if (res) twok.addDigit(); twok.render(); });
+ setWatch(() => { res = twok.shift(2); if (res) twok.addDigit(); twok.render(); }, BTN1, {repeat: true});
+ setWatch(() => { res = twok.shift(-2); if (res) twok.addDigit(); twok.render(); }, BTN3, {repeat: true});
+}
diff --git a/apps/2047pp/2047pp_screenshot.png b/apps/2047pp/2047pp_screenshot.png
new file mode 100644
index 000000000..8c407fb6f
Binary files /dev/null and b/apps/2047pp/2047pp_screenshot.png differ
diff --git a/apps/2047pp/README.md b/apps/2047pp/README.md
new file mode 100644
index 000000000..cac3323a6
--- /dev/null
+++ b/apps/2047pp/README.md
@@ -0,0 +1,9 @@
+
+# Game of 2047pp (2047++)
+
+Tile shifting game inspired by the well known 2048 game. Also very similar to another Bangle game, Game1024.
+
+Attempt to combine equal numbers by swiping left, right, up or down (on Bangle 2) or swiping left/right and using
+the top/bottom button (Bangle 1).
+
+
diff --git a/apps/2047pp/app-icon.js b/apps/2047pp/app-icon.js
new file mode 100644
index 000000000..4086d1879
--- /dev/null
+++ b/apps/2047pp/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A31gAeFtoxPF9wujGBYQG1YAWF6ur5gAYGIovOFzIABF6ReaMAwv/F/4v/F7ejv9/0Yvq1Eylksv4vqvIuBF9ZeDF9ZeBqovr1AsB0YvrLwXMF9ReDF9ZeBq1/v4vBqowKF7lWFYIAFF/7vXAAa/qF+jxB0YvsABov/F/4v/F6WsF7YgEF5xgaLwgvPGIQAWDwwvQADwvJGEguKF+AxhFpoA/AH4A/AFI="))
\ No newline at end of file
diff --git a/apps/2047pp/app.png b/apps/2047pp/app.png
new file mode 100644
index 000000000..d1fb4a5e5
Binary files /dev/null and b/apps/2047pp/app.png differ
diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json
new file mode 100644
index 000000000..f0fd6c1e3
--- /dev/null
+++ b/apps/2047pp/metadata.json
@@ -0,0 +1,15 @@
+{ "id": "2047pp",
+ "name": "2047pp",
+ "shortName":"2047pp",
+ "icon": "app.png",
+ "version":"0.01",
+ "description": "Bangle version of a tile shifting game",
+ "supports" : ["BANGLEJS","BANGLEJS2"],
+ "allow_emulator": true,
+ "readme": "README.md",
+ "tags": "game",
+ "storage": [
+ {"name":"2047pp.app.js","url":"2047pp.app.js"},
+ {"name":"2047pp.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/authentiwatch/ChangeLog b/apps/authentiwatch/ChangeLog
index bb2945db4..655916170 100644
--- a/apps/authentiwatch/ChangeLog
+++ b/apps/authentiwatch/ChangeLog
@@ -4,3 +4,4 @@
0.04: Fix tapping at very bottom of list, exit on inactivity
0.05: Add support for bulk importing and exporting tokens
0.06: Add spaces to codes for improved readability (thanks @BartS23)
+0.07: Bangle 2: Improve drag responsiveness and exit on button press
diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js
index 73b8bdeea..05d94fc46 100644
--- a/apps/authentiwatch/app.js
+++ b/apps/authentiwatch/app.js
@@ -1,6 +1,10 @@
-const tokenextraheight = 16;
-var tokendigitsheight = 30;
-var tokenheight = tokendigitsheight + tokenextraheight;
+const COUNTER_TRIANGLE_SIZE = 10;
+const TOKEN_EXTRA_HEIGHT = 16;
+var TOKEN_DIGITS_HEIGHT = 30;
+var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT;
+const PROGRESSBAR_HEIGHT = 3;
+const IDLE_REPEATS = 1; // when idle, the number of extra timed periods to show before hiding
+const SETTINGS = "authentiwatch.json";
// Hash functions
const crypto = require("crypto");
const algos = {
@@ -8,33 +12,24 @@ const algos = {
"SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 },
"SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 },
};
-const calculating = "Calculating";
-const notokens = "No tokens";
-const notsupported = "Not supported";
+const CALCULATING = /*LANG*/"Calculating";
+const NO_TOKENS = /*LANG*/"No tokens";
+const NOT_SUPPORTED = /*LANG*/"Not supported";
// sample settings:
// {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}}
-var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}};
+var settings = require("Storage").readJSON(SETTINGS, true) || {tokens:[], misc:{}};
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
-// 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);
+ // RFC4648 Base16/32/64 Data Encodings
+ let buf = 0, bitcount = 0, retstr = "";
+ for (let c of seedstr.toUpperCase()) {
+ if (c == '0') c = 'O';
+ if (c == '1') c = 'I';
+ if (c == '8') c = 'B';
+ c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(c);
if (c != -1) {
buf <<= 5;
buf |= c;
@@ -46,195 +41,127 @@ function b32decode(seedstr) {
}
}
}
- var retbuf = new Uint8Array(retstr.length);
- for (i in retstr) {
+ let retbuf = new Uint8Array(retstr.length);
+ for (let i in retstr) {
retbuf[i] = retstr.charCodeAt(i);
}
return retbuf;
}
-function do_hmac(key, message, algo) {
- var a = algos[algo];
- // RFC2104
+
+function hmac(key, message, algo) {
+ let a = algos[algo.toUpperCase()];
+ // RFC2104 HMAC
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;
+ let istr = new Uint8Array(a.blksz + message.length);
+ let ostr = new Uint8Array(a.blksz + a.retsz);
+ for (let i = 0; i < a.blksz; ++i) {
+ let 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);
+ let ret = a.sha(ostr);
+ // RFC4226 HOTP (dynamic truncation)
+ let v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4);
return v.getUint32(0) & 0x7FFFFFFF;
}
-function hotp(d, token, dohmac) {
- var tick;
+
+function formatOtp(otp, digits) {
+ // add 0 padding
+ let ret = "" + otp % Math.pow(10, digits);
+ while (ret.length < digits) {
+ ret = "0" + ret;
+ }
+ // add a space after every 3rd or 4th digit
+ let re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : ".";
+ return ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim();
+}
+
+function hotp(token) {
+ let d = Date.now();
+ let tick, next;
if (token.period > 0) {
// RFC6238 - timed
- var seconds = Math.floor(d.getTime() / 1000);
- tick = Math.floor(seconds / token.period);
+ tick = Math.floor(Math.floor(d / 1000) / token.period);
+ next = (tick + 1) * token.period * 1000;
} else {
// RFC4226 - counter
tick = -token.period;
+ next = d + 30000;
}
- var msg = new Uint8Array(8);
- var v = new DataView(msg.buffer);
+ let msg = new Uint8Array(8);
+ let v = new DataView(msg.buffer);
v.setUint32(0, tick >> 16 >> 16);
v.setUint32(4, tick & 0xFFFFFFFF);
- var ret = calculating;
- if (dohmac) {
- 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;
- }
- // add a space after every 3rd or 4th digit
- var re = (token.digits % 3 == 0 || (token.digits % 3 >= token.digits % 4 && token.digits % 4 != 0)) ? "" : ".";
- ret = ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim();
- } catch(err) {
- ret = notsupported;
- }
+ let ret;
+ try {
+ ret = hmac(b32decode(token.secret), msg, token.algorithm);
+ ret = formatOtp(ret, token.digits);
+ } catch(err) {
+ ret = NOT_SUPPORTED;
}
- return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)};
+ return {hotp:ret, next:next};
}
+// Tokens are displayed in three states:
+// 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!="")
+var fontszCache = {};
var state = {
- listy: 0,
- prevcur:0,
- curtoken:-1,
- nextTime:0,
- otp:"",
- rem:0,
- hide:0
+ listy:0, // list scroll position
+ id:-1, // current token ID
+ hotp:{hotp:"",next: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, lbl, sz;
- 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));
- lbl = tokens[id].label.substr(0, 10);
- if (id == state.curtoken) {
- // current token
- g.setColor(g.theme.fgH)
- .setBgColor(g.theme.bgH)
- .setFont("Vector", tokenextraheight)
- // center just below top line
- .setFontAlign(0, -1, 0);
- adj = y1;
- } else {
- g.setColor(g.theme.fg)
- .setBgColor(g.theme.bg);
- sz = tokendigitsheight;
+function sizeFont(id, txt, w) {
+ let sz = fontszCache[id];
+ if (!sz) {
+ sz = TOKEN_DIGITS_HEIGHT;
do {
g.setFont("Vector", sz--);
- } while (g.stringWidth(lbl) > r.w);
- // center in box
- g.setFontAlign(0, 0, 0);
- adj = (y1 + y2) / 2;
+ } while (g.stringWidth(txt) > w);
+ fontszCache[id] = ++sz;
}
- g.clearRect(x1, y1, x2, y2)
- .drawString(lbl, (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 = 12;
- }
- // digits just below label
- sz = tokendigitsheight;
- do {
- g.setFont("Vector", sz--);
- } while (g.stringWidth(state.otp) > (r.w - adj));
- g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + tokenextraheight, false);
- }
- // shaded lines top and bottom
- g.setColor(0.5, 0.5, 0.5)
- .drawLine(x1, y1, x2, y1)
- .drawLine(x1, y2, x2, y2)
- .setClipRect(0, 0, g.getWidth(), g.getHeight());
+ g.setFont("Vector", sz);
}
-function draw() {
- var timerfn = exitApp;
- var timerdly = 10000;
- var d = new Date();
- if (state.curtoken != -1) {
- var t = tokens[state.curtoken];
- if (state.otp == calculating) {
- state.otp = hotp(d, t, true).hotp;
- }
- 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;
+tokenY = id => id * TOKEN_HEIGHT + AR.y - state.listy;
+half = n => Math.floor(n / 2);
+
+function timerCalc() {
+ let timerfn = exitApp;
+ let timerdly = 10000;
+ if (state.id >= 0 && state.hotp.hotp != "") {
+ if (tokens[state.id].period > 0) {
+ // timed HOTP
+ if (state.hotp.next < Date.now()) {
+ if (state.cnt > 0) {
+ state.cnt--;
+ state.hotp = hotp(tokens[state.id]);
+ } else {
+ state.hotp.hotp = "";
}
- state.nextTime = 0;
+ timerdly = 1;
+ timerfn = updateCurrentToken;
} else {
- // time to generate a new token
- var r = hotp(d, t, state.otp != "");
- 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 / tokenheight);
- var y = id * tokenheight + 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:tokenheight});
- if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) {
- drewcur = true;
- }
- id += 1;
- y += tokenheight;
- }
- if (drewcur) {
- // the current token has been drawn - schedule a redraw
- if (tokens[state.curtoken].period > 0) {
- timerdly = (state.otp == calculating) ? 1 : 1000; // timed
- } else {
- timerdly = state.nexttime - d.getTime(); // counter
- }
- timerfn = draw;
- if (tokens[state.curtoken].period <= 0) {
- state.hide = 0;
+ timerdly = 1000;
+ timerfn = updateProgressBar;
}
} else {
- // de-select the current token if it is scrolled out of view
- if (state.curtoken != -1) {
- state.prevcur = state.curtoken;
- state.curtoken = -1;
+ // counter HOTP
+ if (state.cnt > 0) {
+ state.cnt--;
+ timerdly = 30000;
+ } else {
+ state.hotp.hotp = "";
+ timerdly = 1;
}
- state.nexttime = 0;
+ timerfn = updateCurrentToken;
}
- } else {
- g.setFont("Vector", tokendigitsheight)
- .setFontAlign(0, 0, 0)
- .drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
}
if (state.drawtimer) {
clearTimeout(state.drawtimer);
@@ -242,97 +169,236 @@ function draw() {
state.drawtimer = setTimeout(timerfn, timerdly);
}
-function onTouch(zone, e) {
- if (e) {
- var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenheight);
- if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) {
- id = -1;
- }
- if (state.curtoken != id) {
- if (id != -1) {
- var y = id * tokenheight - state.listy;
- if (y < 0) {
- state.listy += y;
- y = 0;
+function updateCurrentToken() {
+ drawToken(state.id);
+ timerCalc();
+}
+
+function updateProgressBar() {
+ drawProgressBar();
+ timerCalc();
+}
+
+function drawProgressBar() {
+ let id = state.id;
+ if (id >= 0 && tokens[id].period > 0) {
+ let rem = Math.min(tokens[id].period, Math.floor((state.hotp.next - Date.now()) / 1000));
+ if (rem >= 0) {
+ let y1 = tokenY(id);
+ let y2 = y1 + TOKEN_HEIGHT - 1;
+ if (y2 >= AR.y && y1 <= AR.y2) {
+ // token visible
+ y1 = y2 - PROGRESSBAR_HEIGHT;
+ if (y1 <= 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, y1, xr, y2)
+ .clearRect(xr + 1, y1, AR.x2, y2);
}
- y += tokenheight;
- if (y > Bangle.appRect.h) {
- state.listy += (y - Bangle.appRect.h);
- }
- state.otp = "";
+ } else {
+ // token not visible
+ state.id = -1;
}
- state.nextTime = 0;
- state.curtoken = id;
- state.hide = 2;
}
}
- draw();
+}
+
+// id = token ID number (0...)
+function drawToken(id) {
+ 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;
+ g.setClipRect(x1, Math.max(y1, AR.y), x2, Math.min(y2, AR.y2));
+ if (id === state.id) {
+ g.setColor(g.theme.fgH)
+ .setBgColor(g.theme.bgH);
+ } else {
+ g.setColor(g.theme.fg)
+ .setBgColor(g.theme.bg);
+ }
+ 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);
+ g.setFontAlign(0, 0, 0);
+ adj = half(y1 + y2);
+ }
+ g.clearRect(x1, y1, x2, y2)
+ .drawString(lbl, half(x1 + x2), adj, false);
+ if (id == state.id && state.hotp.hotp != "") {
+ adj = 0;
+ if (tokens[id].period <= 0) {
+ // counter - draw triangle as swipe hint
+ 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;
+ }
+ // digits just below label
+ 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();
+ }
+ }
+ g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
+}
+
+function changeId(id) {
+ if (id != state.id) {
+ state.hotp.hotp = CALCULATING;
+ let pid = state.id;
+ state.id = id;
+ if (pid >= 0) {
+ drawToken(pid);
+ }
+ if (id >= 0) {
+ drawToken( id);
+ }
+ }
}
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 * tokenheight - Bangle.appRect.h);
- state.listy = Math.max(0, newy);
- draw();
+ state.cnt = IDLE_REPEATS;
+ if (e.b != 0 && e.dy != 0) {
+ let y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h));
+ if (state.listy != y) {
+ let id, dy = state.listy - y;
+ state.listy = y;
+ g.setClipRect(AR.x, AR.y, AR.x2, AR.y2)
+ .scroll(0, dy);
+ if (dy > 0) {
+ id = Math.floor((state.listy + dy) / TOKEN_HEIGHT);
+ y = tokenY(id + 1);
+ do {
+ drawToken(id);
+ id--;
+ y -= TOKEN_HEIGHT;
+ } while (y > AR.y);
+ }
+ if (dy < 0) {
+ id = Math.floor((state.listy + dy + AR.h) / TOKEN_HEIGHT);
+ y = tokenY(id);
+ while (y < AR.y2) {
+ drawToken(id);
+ id++;
+ y += TOKEN_HEIGHT;
+ }
+ }
+ }
+ }
+ if (e.b == 0) {
+ timerCalc();
+ }
+}
+
+function onTouch(zone, e) {
+ state.cnt = IDLE_REPEATS;
+ if (e) {
+ let id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT);
+ if (id == state.id || tokens.length == 0 || id >= tokens.length) {
+ id = -1;
+ }
+ if (state.id != id) {
+ if (id >= 0) {
+ // scroll token into view if necessary
+ let dy = 0;
+ let y = id * TOKEN_HEIGHT - state.listy;
+ if (y < 0) {
+ dy -= y;
+ y = 0;
+ }
+ y += TOKEN_HEIGHT;
+ if (y > AR.h) {
+ dy -= (y - AR.h);
+ }
+ onDrag({b:1, dy:dy});
+ }
+ changeId(id);
+ }
+ }
+ timerCalc();
}
function onSwipe(e) {
- if (e == 1) {
+ state.cnt = IDLE_REPEATS;
+ switch (e) {
+ case 1:
exitApp();
+ break;
+ case -1:
+ if (state.id >= 0 && tokens[state.id].period <= 0) {
+ tokens[state.id].period--;
+ require("Storage").writeJSON(SETTINGS, {tokens:tokens, misc:settings.misc});
+ state.hotp.hotp = CALCULATING;
+ drawToken(state.id);
+ }
+ break;
}
- if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
- tokens[state.curtoken].period--;
- let newsettings={tokens:tokens,misc:settings.misc};
- require("Storage").writeJSON("authentiwatch.json", newsettings);
- state.nextTime = 0;
- state.otp = "";
- state.hide = 2;
- }
- draw();
+ timerCalc();
}
-function bangle1Btn(e) {
+function bangleBtn(e) {
+ state.cnt = IDLE_REPEATS;
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);
- state.listy = state.curtoken * tokenheight;
- state.listy -= (Bangle.appRect.h - tokenheight) / 2;
- state.listy = Math.min(state.listy, tokens.length * tokenheight - Bangle.appRect.h);
- state.listy = Math.max(state.listy, 0);
- var fakee = {};
- fakee.y = state.curtoken * tokenheight - state.listy + Bangle.appRect.y;
- state.curtoken = -1;
- state.nextTime = 0;
- onTouch(0, fakee);
- } else {
- draw(); // resets idle timer
+ let id = E.clip(state.id + e, 0, tokens.length - 1);
+ 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))});
+ changeId(id);
+ drawProgressBar();
}
+ timerCalc();
}
function exitApp() {
+ if (state.drawtimer) {
+ clearTimeout(state.drawtimer);
+ }
Bangle.showLauncher();
}
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(){exitApp(); }, BTN2, {edge:"falling", debounce:50});
- setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising" , debounce:50, repeat:true});
+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});
+ }
}
Bangle.loadWidgets();
-
-// Clear the screen once, at startup
+const AR = Bangle.appRect;
+// draw the initial display
g.clear();
-draw();
+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();
Bangle.drawWidgets();
diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html
index 5ee32fd8e..7d567d34f 100644
--- a/apps/authentiwatch/interface.html
+++ b/apps/authentiwatch/interface.html
@@ -54,9 +54,9 @@ var tokens = settings.tokens;
*/
function base32clean(val, nows) {
var ret = val.replaceAll(/\s+/g, ' ');
- ret = ret.replaceAll(/0/g, 'O');
- ret = ret.replaceAll(/1/g, 'I');
- ret = ret.replaceAll(/8/g, 'B');
+ ret = ret.replaceAll('0', 'O');
+ ret = ret.replaceAll('1', 'I');
+ ret = ret.replaceAll('8', 'B');
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
if (nows) {
ret = ret.replaceAll(/\s+/g, '');
@@ -81,9 +81,9 @@ function b32encode(str) {
function b32decode(seedstr) {
// RFC4648
- var i, buf = 0, bitcount = 0, ret = '';
- for (i in seedstr) {
- var c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(seedstr.charAt(i).toUpperCase(), 0);
+ var buf = 0, bitcount = 0, ret = '';
+ for (var c of seedstr.toUpperCase()) {
+ c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(c);
if (c != -1) {
buf <<= 5;
buf |= c;
@@ -405,7 +405,7 @@ class proto3decoder {
constructor(str) {
this.buf = [];
for (let i in str) {
- this.buf = this.buf.concat(str.charCodeAt(i));
+ this.buf.push(str.charCodeAt(i));
}
}
getVarint() {
@@ -487,7 +487,7 @@ function startScan(handler,cancel) {
document.body.className = 'scanning';
navigator.mediaDevices
.getUserMedia({video:{facingMode:'environment'}})
- .then(function(stream){
+ .then(stream => {
scanning=true;
video.setAttribute('playsinline',true);
video.srcObject = stream;
diff --git a/apps/authentiwatch/metadata.json b/apps/authentiwatch/metadata.json
index b4ed34a12..36e1ea34d 100644
--- a/apps/authentiwatch/metadata.json
+++ b/apps/authentiwatch/metadata.json
@@ -4,7 +4,7 @@
"shortName": "AuthWatch",
"icon": "app.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
- "version": "0.06",
+ "version": "0.07",
"description": "Google Authenticator compatible tool.",
"tags": "tool",
"interface": "interface.html",
diff --git a/apps/authentiwatch/screenshot1.png b/apps/authentiwatch/screenshot1.png
index c7ca744b4..c3ac0e3b7 100644
Binary files a/apps/authentiwatch/screenshot1.png and b/apps/authentiwatch/screenshot1.png differ
diff --git a/apps/authentiwatch/screenshot2.png b/apps/authentiwatch/screenshot2.png
index 8156dd3e8..26bafdbb2 100644
Binary files a/apps/authentiwatch/screenshot2.png and b/apps/authentiwatch/screenshot2.png differ
diff --git a/apps/authentiwatch/screenshot3.png b/apps/authentiwatch/screenshot3.png
index 6d14e0b96..80f2fb172 100644
Binary files a/apps/authentiwatch/screenshot3.png and b/apps/authentiwatch/screenshot3.png differ
diff --git a/apps/authentiwatch/screenshot4.png b/apps/authentiwatch/screenshot4.png
index 7576e1aff..00756228d 100644
Binary files a/apps/authentiwatch/screenshot4.png and b/apps/authentiwatch/screenshot4.png differ
diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog
index ef437fe3b..87b5f7def 100644
--- a/apps/boot/ChangeLog
+++ b/apps/boot/ChangeLog
@@ -49,3 +49,4 @@
0.43: Fix Gadgetbridge handling with Programmable:off
0.44: Write .boot0 without ever having it all in RAM (fix Bangle.js 1 issues with BTHRM)
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)
+0.46: Fix no clock found error on Bangle.js 2
diff --git a/apps/boot/bootloader.js b/apps/boot/bootloader.js
index 3cf885ac9..45e271f30 100644
--- a/apps/boot/bootloader.js
+++ b/apps/boot/bootloader.js
@@ -14,6 +14,6 @@ if (!clockApp) {
if (clockApp)
clockApp = require("Storage").read(clockApp.src);
}
-if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, BTN2, {repeat:false,edge:"falling"});`;
+if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, global.BTN2||BTN, {repeat:false,edge:"falling"});`;
eval(clockApp);
delete clockApp;
diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json
index c21ab6833..11884576f 100644
--- a/apps/boot/metadata.json
+++ b/apps/boot/metadata.json
@@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
- "version": "0.45",
+ "version": "0.46",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",
diff --git a/apps/clockcal/ChangeLog b/apps/clockcal/ChangeLog
index 299b1ec69..f73c6ed97 100644
--- a/apps/clockcal/ChangeLog
+++ b/apps/clockcal/ChangeLog
@@ -1,2 +1,3 @@
0.01: Initial upload
0.2: Added scrollable calendar and swipe gestures
+0.3: Configurable drag gestures
diff --git a/apps/clockcal/README.md b/apps/clockcal/README.md
index da6887177..d30205be0 100644
--- a/apps/clockcal/README.md
+++ b/apps/clockcal/README.md
@@ -9,25 +9,24 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu
||unlocked: smaller clock, but with seconds|
||swipe up for big calendar, (up down to scroll, left/right to exit)|
-
-
-
## Configurable Features
- Number of calendar rows (weeks)
- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included)
-- Clock Mode (24h/12h). Doesn't have an am/pm indicator. It's only there because it was easy.
+- Clock Mode (24h/12h). (No am/pm indicator)
- First day of the week
-- Red Saturday
-- Red Sunday
-- Swipes (to disable all gestures)
-- Swipes: music (swipe down)
-- Spipes: messages (swipe right)
+- Red Saturday/Sunday
+- Swipe/Drag gestures to launch features or apps.
## Auto detects your message/music apps:
-- swiping down will search your files for an app with the string "music" in its filename and launch it
-- swiping right will search your files for an app with the string "message" in its filename and launch it.
-- Configurable apps coming soon.
+- swiping down will search your files for an app with the string "message" in its filename and launch it. (configurable)
+- swiping right will search your files for an app with the string "music" in its filename and launch it. (configurable)
## Feedback
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.
So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues
+
+## Planned features:
+ - Internal lightweight music control, because switching apps has a loading time.
+ - Clean up settings
+ - Maybe am/pm indicator for 12h-users
+ - Step count (optional)
diff --git a/apps/clockcal/app.js b/apps/clockcal/app.js
index 86fa0815a..5e8c7f796 100644
--- a/apps/clockcal/app.js
+++ b/apps/clockcal/app.js
@@ -4,12 +4,13 @@ var s = Object.assign({
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
MODE24: true, //24h mode vs 12h mode
- FIRSTDAYOFFSET: 6, //First day of the week: 0-6: Sun, Sat, Fri, Thu, Wed, Tue, Mon
+ FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday?
- DRAGENABLED: true,
- DRAGMUSIC: true,
- DRAGMESSAGES: true
+ DRAGDOWN: "[AI:messg]",
+ DRAGRIGHT: "[AI:music]",
+ DRAGLEFT: "[ignore]",
+ DRAGUP: "[calend.]"
}, require('Storage').readJSON("clockcal.json", true) || {});
const h = g.getHeight();
@@ -27,13 +28,13 @@ var monthOffset = 0;
*/
function drawFullCalendar(monthOffset) {
addMonths = function (_d, _am) {
- var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
- while ((m + _am) > 11) { ay++; _am -= 12; }
- while ((m + _am) < 0) { ay--; _am += 12; }
- n = new Date(_d.getTime());
- n.setMonth(m + _am);
- n.setFullYear(y + ay);
- return n;
+ var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
+ while ((m + _am) > 11) { ay++; _am -= 12; }
+ while ((m + _am) < 0) { ay--; _am += 12; }
+ n = new Date(_d.getTime());
+ n.setMonth(m + _am);
+ n.setFullYear(y + ay);
+ return n;
};
monthOffset = (typeof monthOffset == "undefined") ? 0 : monthOffset;
state = "calendar";
@@ -45,22 +46,22 @@ function drawFullCalendar(monthOffset) {
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
d = addMonths(Date(), monthOffset);
tdy = Date().getDate() + "." + Date().getMonth();
- newmonth=false;
+ newmonth = false;
c_y = 0;
g.reset();
g.setBgColor(0);
g.clear();
- var prevmonth = addMonths(d, -1)
+ var prevmonth = addMonths(d, -1);
const today = prevmonth.getDate();
var rD = new Date(prevmonth.getTime());
rD.setDate(rD.getDate() - (today - 1));
- const dow = (s.FIRSTDAYOFFSET + rD.getDay()) % 7;
+ const dow = (s.FIRSTDAY + rD.getDay()) % 7;
rD.setDate(rD.getDate() - dow);
var rDate = rD.getDate();
bottomrightY = c_y - 3;
- clrsun=s.REDSUN?'#f00':'#fff';
- clrsat=s.REDSUN?'#f00':'#fff';
- var fg=[clrsun,'#fff','#fff','#fff','#fff','#fff',clrsat];
+ clrsun = s.REDSUN ? '#f00' : '#fff';
+ clrsat = s.REDSUN ? '#f00' : '#fff';
+ var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat];
for (var y = 1; y <= 11; y++) {
bottomrightY += CELL_H;
bottomrightX = -2;
@@ -69,14 +70,14 @@ function drawFullCalendar(monthOffset) {
rMonth = rD.getMonth();
rDate = rD.getDate();
if (tdy == rDate + "." + rMonth) {
- caldrawToday(rDate);
+ caldrawToday(rDate);
} else if (rDate == 1) {
- caldrawFirst(rDate);
+ caldrawFirst(rDate);
} else {
- caldrawNormal(rDate,fg[rD.getDay()]);
+ caldrawNormal(rDate, fg[rD.getDay()]);
}
if (newmonth && x == 7) {
- caldrawMonth(rDate,monthclr[rMonth % 6],months[rMonth],rD);
+ caldrawMonth(rDate, monthclr[rMonth % 6], months[rMonth], rD);
}
rD.setDate(rDate + 1);
}
@@ -84,7 +85,7 @@ function drawFullCalendar(monthOffset) {
delete addMonths;
if (DEBUG) console.log("Calendar performance (ms):" + (Date().getTime() - start));
}
-function caldrawMonth(rDate,c,m,rD) {
+function caldrawMonth(rDate, c, m, rD) {
g.setColor(c);
g.setFont("Vector", 18);
g.setFontAlign(-1, 1, 1);
@@ -93,29 +94,29 @@ function caldrawMonth(rDate,c,m,rD) {
newmonth = false;
}
function caldrawToday(rDate) {
- g.setFont("Vector", 16);
- g.setFontAlign(1, 1);
- g.setColor('#0f0');
- g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
- g.setColor('#000');
- g.drawString(rDate, bottomrightX, bottomrightY);
+ g.setFont("Vector", 16);
+ g.setFontAlign(1, 1);
+ g.setColor('#0f0');
+ g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
+ g.setColor('#000');
+ g.drawString(rDate, bottomrightX, bottomrightY);
}
function caldrawFirst(rDate) {
- g.flip();
- g.setFont("Vector", 16);
- g.setFontAlign(1, 1);
- bottomrightY += 3;
- newmonth = true;
- g.setColor('#0ff');
- g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
- g.setColor('#000');
- g.drawString(rDate, bottomrightX, bottomrightY);
+ g.flip();
+ g.setFont("Vector", 16);
+ g.setFontAlign(1, 1);
+ bottomrightY += 3;
+ newmonth = true;
+ g.setColor('#0ff');
+ g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
+ g.setColor('#000');
+ g.drawString(rDate, bottomrightX, bottomrightY);
}
-function caldrawNormal(rDate,c) {
- g.setFont("Vector", 16);
- g.setFontAlign(1, 1);
- g.setColor(c);
- g.drawString(rDate, bottomrightX, bottomrightY);//100
+function caldrawNormal(rDate, c) {
+ g.setFont("Vector", 16);
+ g.setFontAlign(1, 1);
+ g.setColor(c);
+ g.drawString(rDate, bottomrightX, bottomrightY);//100
}
function drawMinutes() {
if (DEBUG) console.log("|-->minutes");
@@ -163,7 +164,7 @@ function drawWatch() {
g.clear();
drawMinutes();
if (!dimSeconds) drawSeconds();
- const dow = (s.FIRSTDAYOFFSET + d.getDay()) % 7; //MO=0, SU=6
+ const dow = (s.FIRSTDAY + d.getDay()) % 7; //MO=0, SU=6
const today = d.getDate();
var rD = new Date(d.getTime());
rD.setDate(rD.getDate() - dow);
@@ -205,27 +206,52 @@ function BTevent() {
setTimeout(function () { Bangle.buzz(interval); }, interval * 3);
}
}
-
+function action(a) {
+ g.reset();
+ if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
+ if (DEBUG) console.log("action:" + a);
+ switch (a) {
+ case "[ignore]":
+ break;
+ case "[calend.]":
+ drawFullCalendar();
+ break;
+ case "[AI:music]":
+ l = require("Storage").list(RegExp("music.*app.js"));
+ if (l.length > 0) {
+ load(l[0]);
+ } else E.showAlert("Music app not found", "Not found").then(drawWatch);
+ break;
+ case "[AI:messg]":
+ l = require("Storage").list(RegExp("message.*app.js"));
+ if (l.length > 0) {
+ load(l[0]);
+ } else E.showAlert("Message app not found", "Not found").then(drawWatch);
+ break;
+ default:
+ l = require("Storage").list(RegExp(a + ".app.js"));
+ if (l.length > 0) {
+ load(l[0]);
+ } else E.showAlert(a + ": App not found", "Not found").then(drawWatch);
+ break;
+ }
+}
function input(dir) {
- if (s.DRAGENABLED) {
- Bangle.buzz(100,1);
- console.log("swipe:"+dir);
+ Bangle.buzz(100, 1);
+ if (DEBUG) console.log("swipe:" + dir);
switch (dir) {
case "r":
if (state == "calendar") {
drawWatch();
} else {
- if (s.DRAGMUSIC) {
- l=require("Storage").list(RegExp("music.*app"));
- if (l.length > 0) {
- load(l[0]);
- } else Bangle.buzz(3000,1);//not found
- }
+ action(s.DRAGRIGHT);
}
break;
case "l":
if (state == "calendar") {
drawWatch();
+ } else {
+ action(s.DRAGLEFT);
}
break;
case "d":
@@ -233,21 +259,15 @@ function input(dir) {
monthOffset--;
drawFullCalendar(monthOffset);
} else {
- if (s.DRAGMESSAGES) {
- l=require("Storage").list(RegExp("message.*app"));
- if (l.length > 0) {
- load(l[0]);
- } else Bangle.buzz(3000,1);//not found
- }
+ action(s.DRAGDOWN);
}
break;
case "u":
- if (state == "watch") {
- state = "calendar";
- drawFullCalendar(0);
- } else if (state == "calendar") {
+ if (state == "calendar") {
monthOffset++;
drawFullCalendar(monthOffset);
+ } else {
+ action(s.DRAGUP);
}
break;
default:
@@ -255,26 +275,24 @@ function input(dir) {
drawWatch();
}
break;
+
}
- }
}
let drag;
Bangle.on("drag", e => {
- if (s.DRAGENABLED) {
- if (!drag) {
- drag = { x: e.x, y: e.y };
- } else if (!e.b) {
- const dx = e.x - drag.x, dy = e.y - drag.y;
- var dir = "t";
- if (Math.abs(dx) > Math.abs(dy) + 10) {
- dir = (dx > 0) ? "r" : "l";
- } else if (Math.abs(dy) > Math.abs(dx) + 10) {
- dir = (dy > 0) ? "d" : "u";
- }
- drag = null;
- input(dir);
+ if (!drag) {
+ drag = { x: e.x, y: e.y };
+ } else if (!e.b) {
+ const dx = e.x - drag.x, dy = e.y - drag.y;
+ var dir = "t";
+ if (Math.abs(dx) > Math.abs(dy) + 20) {
+ dir = (dx > 0) ? "r" : "l";
+ } else if (Math.abs(dy) > Math.abs(dx) + 20) {
+ dir = (dy > 0) ? "d" : "u";
}
+ drag = null;
+ input(dir);
}
});
diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json
index a42e1ad2e..dde32f746 100644
--- a/apps/clockcal/metadata.json
+++ b/apps/clockcal/metadata.json
@@ -1,7 +1,7 @@
{
"id": "clockcal",
"name": "Clock & Calendar",
- "version": "0.2",
+ "version": "0.3",
"description": "Clock with Calendar",
"readme":"README.md",
"icon": "app.png",
diff --git a/apps/clockcal/settings.js b/apps/clockcal/settings.js
index c4ec764c9..abedad99b 100644
--- a/apps/clockcal/settings.js
+++ b/apps/clockcal/settings.js
@@ -1,19 +1,22 @@
(function (back) {
var FILE = "clockcal.json";
-
- settings = Object.assign({
+ defaults={
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
MODE24: true, //24h mode vs 12h mode
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday?
- DRAGENABLED: true, //Enable drag gestures (bigger calendar etc)
- DRAGMUSIC: true, //Enable drag down for music (looks for "music*app")
- DRAGMESSAGES: true //Enable drag right for messages (looks for "message*app")
- }, require('Storage').readJSON(FILE, true) || {});
-
+ DRAGDOWN: "[AI:messg]",
+ DRAGRIGHT: "[AI:music]",
+ DRAGLEFT: "[ignore]",
+ DRAGUP: "[calend.]"
+ };
+ settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
+ actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]"];
+ require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js","")));
+
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
@@ -70,27 +73,39 @@
writeSettings();
}
},
- 'Swipes (big cal.)?': {
- value: settings.DRAGENABLED,
- format: v => v ? "On" : "Off",
+ 'Drag Up ': {
+ min:0, max:actions.length-1,
+ value: actions.indexOf(settings.DRAGUP),
+ format: v => actions[v],
onchange: v => {
- settings.DRAGENABLED = v;
+ settings.DRAGUP = actions[v];
writeSettings();
}
},
- 'Swipes (music)?': {
- value: settings.DRAGMUSIC,
- format: v => v ? "On" : "Off",
+ 'Drag Right': {
+ min:0, max:actions.length-1,
+ value: actions.indexOf(settings.DRAGRIGHT),
+ format: v => actions[v],
onchange: v => {
- settings.DRAGMUSIC = v;
+ settings.DRAGRIGHT = actions[v];
writeSettings();
}
},
- 'Swipes (messg)?': {
- value: settings.DRAGMESSAGES,
- format: v => v ? "On" : "Off",
+ 'Drag Down': {
+ min:0, max:actions.length-1,
+ value: actions.indexOf(settings.DRAGDOWN),
+ format: v => actions[v],
onchange: v => {
- settings.DRAGMESSAGES = v;
+ settings.DRGDOWN = actions[v];
+ writeSettings();
+ }
+ },
+ 'Drag Left': {
+ min:0, max:actions.length-1,
+ value: actions.indexOf(settings.DRAGLEFT),
+ format: v => actions[v],
+ onchange: v => {
+ settings.DRAGLEFT = actions[v];
writeSettings();
}
},
@@ -100,17 +115,7 @@
format: v => ["No", "Yes"][v],
onchange: v => {
if (v == 1) {
- settings = {
- CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
- BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect.
- MODE24: true, //24h mode vs 12h mode
- FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
- REDSUN: true, // Use red color for sunday?
- REDSAT: true, // Use red color for saturday?
- DRAGENABLED: true,
- DRAGMUSIC: true,
- DRAGMESSAGES: true
- };
+ settings = defaults;
writeSettings();
load();
}
diff --git a/apps/waveclk/ChangeLog b/apps/waveclk/ChangeLog
index 8c2a33143..f1fb77c59 100644
--- a/apps/waveclk/ChangeLog
+++ b/apps/waveclk/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
0.02: Load widgets after setUI so widclk knows when to hide
+0.03: Show the day of the week
diff --git a/apps/waveclk/app.js b/apps/waveclk/app.js
index f1c67ce2f..18b28500b 100644
--- a/apps/waveclk/app.js
+++ b/apps/waveclk/app.js
@@ -41,6 +41,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("ZCOOL");
g.drawString(timeStr,x,y);
@@ -48,6 +49,9 @@ function draw() {
y += 35;
g.setFontAlign(0,0,1).setFont("6x8");
g.drawString(dateStr,g.getWidth()-8,g.getHeight()/2);
+ // draw the day of the week
+ g.setFontAlign(0,0,3).setFont("6x8");
+ g.drawString(dowStr,8,g.getHeight()/2);
// queue draw in one minute
queueDraw();
}
diff --git a/apps/waveclk/metadata.json b/apps/waveclk/metadata.json
index a8d270da2..9ba2798ff 100644
--- a/apps/waveclk/metadata.json
+++ b/apps/waveclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "waveclk",
"name": "Wave Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/)",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
diff --git a/apps/waveclk/screenshot.png b/apps/waveclk/screenshot.png
index 7f05ce688..161ef96ef 100644
Binary files a/apps/waveclk/screenshot.png and b/apps/waveclk/screenshot.png differ
diff --git a/gadgetbridge.js b/gadgetbridge.js
new file mode 100644
index 000000000..679fffc60
--- /dev/null
+++ b/gadgetbridge.js
@@ -0,0 +1,162 @@
+/* Detects if we're running under Gadgetbridge in a WebView, and if
+so it overwrites the 'Puck' library with a special one that calls back
+into Gadgetbridge to handle watch communications */
+
+/*// test code
+Android = {
+ bangleTx : function(data) {
+ console.log("TX : "+JSON.stringify(data));
+ }
+};*/
+
+if (typeof Android!=="undefined") {
+ console.log("Running under Gadgetbridge, overwrite Puck library");
+
+ var isBusy = false;
+ var queue = [];
+ var connection = {
+ cb : function(data) {},
+ write : function(data, writecb) {
+ Android.bangleTx(data);
+ Puck.writeProgress(data.length, data.length);
+ if (writecb) setTimeout(writecb,10);
+ },
+ close : function() {},
+ received : "",
+ hadData : false
+ }
+
+ function bangleRx(data) {
+// document.getElementById("status").innerText = "RX:"+data;
+ connection.received += data;
+ connection.hadData = true;
+ if (connection.cb) connection.cb(data);
+ }
+
+ function log(level, s) {
+ if (Puck.log) Puck.log(level, s);
+ }
+
+ function handleQueue() {
+ if (!queue.length) return;
+ var q = queue.shift();
+ log(3,"Executing "+JSON.stringify(q)+" from queue");
+ if (q.type == "write") Puck.write(q.data, q.callback, q.callbackNewline);
+ else log(1,"Unknown queue item "+JSON.stringify(q));
+ }
+
+ /* convenience function... Write data, call the callback with data:
+ callbackNewline = false => if no new data received for ~0.2 sec
+ callbackNewline = true => after a newline */
+ function write(data, callback, callbackNewline) {
+ let result;
+ /// If there wasn't a callback function, then promisify
+ if (typeof callback !== 'function') {
+ callbackNewline = callback;
+
+ result = new Promise((resolve, reject) => callback = (value, err) => {
+ if (err) reject(err);
+ else resolve(value);
+ });
+ }
+
+ if (isBusy) {
+ log(3, "Busy - adding Puck.write to queue");
+ queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
+ return result;
+ }
+
+ var cbTimeout;
+ function onWritten() {
+ if (callbackNewline) {
+ connection.cb = function(d) {
+ var newLineIdx = connection.received.indexOf("\n");
+ if (newLineIdx>=0) {
+ var l = connection.received.substr(0,newLineIdx);
+ connection.received = connection.received.substr(newLineIdx+1);
+ connection.cb = undefined;
+ if (cbTimeout) clearTimeout(cbTimeout);
+ cbTimeout = undefined;
+ if (callback)
+ callback(l);
+ isBusy = false;
+ handleQueue();
+ }
+ };
+ }
+ // wait for any received data if we have a callback...
+ var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
+ var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
+ var maxDataTime = dataWaitTime; // max time we wait after having received data
+ cbTimeout = setTimeout(function timeout() {
+ cbTimeout = undefined;
+ if (maxTime) maxTime--;
+ if (maxDataTime) maxDataTime--;
+ if (connection.hadData) maxDataTime=dataWaitTime;
+ if (maxDataTime && maxTime) {
+ cbTimeout = setTimeout(timeout, 100);
+ } else {
+ connection.cb = undefined;
+ if (callback)
+ callback(connection.received);
+ isBusy = false;
+ handleQueue();
+ connection.received = "";
+ }
+ connection.hadData = false;
+ }, 100);
+ }
+
+ if (!connection.txInProgress) connection.received = "";
+ isBusy = true;
+ connection.write(data, onWritten);
+ return result
+ }
+
+ // ----------------------------------------------------------
+
+ Puck = {
+ /// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
+ debug : Puck.debug,
+ /// Should we use flow control? Default is true
+ flowControl : true,
+ /// Used internally to write log information - you can replace this with your own function
+ log : function(level, s) { if (level <= this.debug) console.log(" "+s)},
+ /// Called with the current send progress or undefined when done - you can replace this with your own function
+ writeProgress : Puck.writeProgress,
+ connect : function(callback) {
+ setTimeout(callback, 10);
+ },
+ write : write,
+ eval : function(expr, cb) {
+ const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
+ .then(function (d) {
+ try {
+ return JSON.parse(d);
+ } catch (e) {
+ log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
+ return Promise.reject(d);
+ }
+ });
+ if (cb) {
+ return void response.then(cb, (err) => cb(null, err));
+ } else {
+ return response;
+ }
+ },
+ isConnected : function() { return true; },
+ getConnection : function() { return connection; },
+ close : function() {
+ if (connection)
+ connection.close();
+ },
+ };
+ // no need for header
+ document.getElementsByTagName("header")[0].style="display:none";
+ // force connection attempt automatically
+ setTimeout(function() {
+ getInstalledApps(true).catch(err => {
+ showToast("Device connection failed, "+err,"error");
+ });
+ }, 100);
+}
diff --git a/index.html b/index.html
index a418b48eb..bd8ddea5a 100644
--- a/index.html
+++ b/index.html
@@ -179,5 +179,6 @@
+
|