Merge branch 'espruino:master' into master

master
Jelco Huijser 2022-03-25 00:43:34 +01:00 committed by GitHub
commit efc7507be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 794 additions and 370 deletions

140
apps/2047pp/2047pp.app.js Normal file
View File

@ -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<th; ++i) g.drawRect(x1+i, y1+i, x2-i, y2-i);
if (fill) g.setColor(cf).fillRect(x1+th, y1+th, x2-th, y2-th);
}
render() {
const yo = 20;
const xo = yo/2;
h = g.getHeight()-yo;
w = g.getWidth()-yo;
bh = Math.floor(h/4);
bw = Math.floor(w/4);
g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0);
g.setFont("Vector", 16).setColor("#fff").drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false);
for (y=0; y<4; ++y)
for (x=0; x<4; ++x) {
b = this.b[y][x];
this.drawBRect(xo+x*bw, yo+y*bh-1, xo+(x+1)*bh-1, yo+(y+1)*bh-2, 4, "#a88", this.cmap[b], true);
if (b > 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});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

9
apps/2047pp/README.md Normal file
View File

@ -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).
![Screenshot](./2047pp_screenshot.png)

1
apps/2047pp/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A31gAeFtoxPF9wujGBYQG1YAWF6ur5gAYGIovOFzIABF6ReaMAwv/F/4v/F7ejv9/0Yvq1Eylksv4vqvIuBF9ZeDF9ZeBqovr1AsB0YvrLwXMF9ReDF9ZeBq1/v4vBqowKF7lWFYIAFF/7vXAAa/qF+jxB0YvsABov/F/4v/F6WsF7YgEF5xgaLwgvPGIQAWDwwvQADwvJGEguKF+AxhFpoA/AH4A/AFI="))

BIN
apps/2047pp/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

15
apps/2047pp/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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

View File

@ -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;

View File

@ -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",

View File

@ -1,2 +1,3 @@
0.01: Initial upload
0.2: Added scrollable calendar and swipe gestures
0.3: Configurable drag gestures

View File

@ -9,25 +9,24 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu
|![unlocked screen](screenshot2.png)|unlocked: smaller clock, but with seconds|
|![big calendar](screenshot3.png)|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)

View File

@ -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);
}
});

View File

@ -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",

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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"}],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

162
gadgetbridge.js Normal file
View File

@ -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("<BLE> "+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);
}

View File

@ -179,5 +179,6 @@
<script src="core/js/appinfo.js"></script>
<script src="core/js/index.js"></script>
<script src="core/js/pwa.js" defer></script>
<script src="gadgetbridge.js"></script>
</body>
</html>