From b7b5d6764b357649fc258c3530c88dec25c168e6 Mon Sep 17 00:00:00 2001 From: Simon Weis Date: Thu, 26 Mar 2020 22:30:34 +0100 Subject: [PATCH 01/17] Adds gbridge call notification and refactor widget --- apps.json | 8 +- apps/gbridge/ChangeLog | 1 + apps/gbridge/gbridge-call-ico.js | 1 + apps/gbridge/gbridge-music-ico.js | 1 + apps/gbridge/gbridge-off-ico.js | 1 + apps/gbridge/gbridge-on-ico.js | 1 + apps/gbridge/widget.js | 356 ++++++++++++++++++++---------- 7 files changed, 250 insertions(+), 119 deletions(-) create mode 100644 apps/gbridge/gbridge-call-ico.js create mode 100644 apps/gbridge/gbridge-music-ico.js create mode 100644 apps/gbridge/gbridge-off-ico.js create mode 100644 apps/gbridge/gbridge-on-ico.js diff --git a/apps.json b/apps.json index d5b079f7a..b5350d349 100644 --- a/apps.json +++ b/apps.json @@ -66,13 +66,17 @@ { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", - "version":"0.04", + "version":"0.05", "description": "The default notification handler for Gadgetbridge notifications from Android", "tags": "tool,system,android,widget", "storage": [ {"name":"gbridge.app.js","url":"app.js"}, {"name":"gbridge.img","url":"app-icon.js","evaluate":true}, - {"name":"gbridge.wid.js","url":"widget.js"} + {"name":"gbridge.wid.js","url":"widget.js"}, + {"name":"gbridge-music-ico.img","url":"gbridge-music-ico.js","evaluate":true}, + {"name":"gbridge-off-ico.img","url":"gbridge-off-ico.js","evaluate":true}, + {"name":"gbridge-on-ico.img","url":"gbridge-on-ico.js","evaluate":true}, + {"name":"gbridge-call-ico.img","url":"gbridge-call-ico.js","evaluate":true} ] }, { "id": "mclock", diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index 28789ec04..3a6dd3bfd 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -2,3 +2,4 @@ 0.02: Increase contrast (darker notification background, white text) 0.03: Gadgetbridge widget now shows connection state 0.04: Tweaks for variable size widget system +0.05: Show incoming call notification \ No newline at end of file diff --git a/apps/gbridge/gbridge-call-ico.js b/apps/gbridge/gbridge-call-ico.js new file mode 100644 index 000000000..ce722a9a8 --- /dev/null +++ b/apps/gbridge/gbridge-call-ico.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw=")) \ No newline at end of file diff --git a/apps/gbridge/gbridge-music-ico.js b/apps/gbridge/gbridge-music-ico.js new file mode 100644 index 000000000..ff8f80883 --- /dev/null +++ b/apps/gbridge/gbridge-music-ico.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")) \ No newline at end of file diff --git a/apps/gbridge/gbridge-off-ico.js b/apps/gbridge/gbridge-off-ico.js new file mode 100644 index 000000000..1a722f372 --- /dev/null +++ b/apps/gbridge/gbridge-off-ico.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")) \ No newline at end of file diff --git a/apps/gbridge/gbridge-on-ico.js b/apps/gbridge/gbridge-on-ico.js new file mode 100644 index 000000000..f40c4149f --- /dev/null +++ b/apps/gbridge/gbridge-on-ico.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")) \ No newline at end of file diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index a787d7e0b..151dd3ad3 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -1,125 +1,247 @@ -(function() { - var musicState = "stop"; - var musicInfo = {"artist":"","album":"","track":""}; - var scrollPos = 0; - function gb(j) { - Bluetooth.println(JSON.stringify(j)); - } - function show(size,render) { - var oldMode = Bangle.getLCDMode(); - Bangle.setLCDMode("direct"); - g.setClipRect(0,240,239,319); - g.setColor("#222222"); - g.fillRect(1,241,238,318); - render(320-size); - g.setColor("#ffffff"); - g.fillRect(0,240,1,319); - g.fillRect(238,240,239,319); - g.fillRect(2,318,238,319); - Bangle.setLCDPower(1); // light up - Bangle.setLCDMode(oldMode); // clears cliprect - function anim() { - scrollPos-=2; - if (scrollPos<-size) scrollPos=-size; - Bangle.setLCDOffset(scrollPos); - if (scrollPos>-size) setTimeout(anim,10); - } - anim(); - } - function hide() { - function anim() { - scrollPos+=4; - if (scrollPos>0) scrollPos=0; - Bangle.setLCDOffset(scrollPos); - if (scrollPos<0) setTimeout(anim,10); - } - anim(); - } +(() => { - Bangle.on('touch',function() { - if (scrollPos) hide(); - }); - Bangle.on('swipe',function(dir) { - if (musicState=="play") { - gb({t:"music",n:dir>0?"next":"previous"}); - } - }); - gb({t:"status",bat:E.getBattery()}); + const gb = { + musicState: { + STOP: "stop", + PLAY: "play", + PAUSE: "pause" + }, - global.GB = function(j) { - switch (j.t) { - case "notify": - show(80,function(y) { - // TODO: icon based on src? - var x = 120; - g.setFontAlign(0,0); - g.setFont("6x8",1); - g.setColor("#40d040"); - g.drawString(j.src,x,y+7); - g.setColor("#ffffff"); - g.setFont("6x8",2); - g.drawString(j.title,x,y+25); - g.setFont("6x8",1); - g.setColor("#ffffff"); - // split text up a word boundaries - var txt = j.body.split("\n"); - var MAXCHARS = 38; - for (var i=0;iMAXCHARS) { - var p = MAXCHARS; - while (p>MAXCHARS-8 && !" \t-_".includes(l[p])) - p--; - if (p==MAXCHARS-8) p=MAXCHARS; - txt[i] = l.substr(0,p); - txt.splice(i+1,0,l.substr(p)); - } - } - g.setFontAlign(-1,-1); - g.drawString(txt.join("\n"),10,y+40); - Bangle.buzz(); - }); - break; - case "musicinfo": - musicInfo = j; - break; - case "musicstate": - musicState = j.state; - if (musicState=="play") - show(40,function(y) { - g.setColor("#ffffff"); - g.drawImage( require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")),8,y+8); - g.setFontAlign(-1,-1); - g.setFont("6x8",1); - var x = 40; - g.setFont("4x6",2); - g.setColor("#ffffff"); - g.drawString(musicInfo.artist,x,y+8); - g.setFont("6x8",1); - g.setColor("#ffffff"); - g.drawString(musicInfo.track,x,y+22); - }); - if (musicState=="pause") - hide(); - break; + muiscControl: { + NEXT: "next", + PREV: "previous" + }, + + callCommands: { + UNDEFINED: "undefined", + ACCEPT: "accept", + INCOMING: "incoming", + OUTGOING: "outgoing", + REJECT: "reject", + START: "start", + END: "end" + }, + + send: (message) => { + Bluetooth.println(JSON.stringify(message)); + }, + + controlMusic: (operation) => { + gb.send({ t: "music", n: operation }); + }, + + reportBatteryLevel: () => { + gb.send({ t: "status", bat: E.getBattery() }); + }, + }; + + const state = { + music: gb.musicState.STOP, + + musicInfo: { + artist: "", + album: "", + track: "" + }, + debug: false, + }; + + const notification = { + + backgroundColor: "#222222", + frameColor: "#ffffff", + titleColor: "#40d040", + contentColor: "#ffffff", + scrollPos: 0, + + show: (size, content) => { + var oldMode = Bangle.getLCDMode(); + Bangle.setLCDMode("direct"); + + g.setClipRect(0, 240, 239, 319); + g.setColor(notification.backgroundColor); + g.fillRect(1, 241, 238, 318); + + notification.drawContent(320 - size, content); + + g.setColor(notification.frameColor); + g.fillRect(0, 240, 1, 319); + g.fillRect(238, 240, 239, 319); + g.fillRect(2, 318, 238, 319); + + Bangle.setLCDPower(1); // light up + Bangle.setLCDMode(oldMode); // clears cliprect + + function anim() { + notification.scrollPos -= 2; + if (notification.scrollPos < -size) notification.scrollPos = -size; + Bangle.setLCDOffset(notification.scrollPos); + if (notification.scrollPos > -size) setTimeout(anim, 10); + } + anim(); + }, + + drawContent: (y, content) => { + + if (content.icon !== undefined) { + g.setColor(notification.contentColor); + const icon = require("Storage").read(content.icon); + g.drawImage(icon, 8, y + 8); + } + + var x = 120; + g.setFontAlign(0, 0); + + g.setFont("6x8", 1); + g.setColor(notification.titleColor); + g.drawString(content.title, x, y + 7); + + g.setColor(notification.contentColor); + g.setFont("6x8", 2); + g.drawString(content.header, x, y + 25); + + g.setFont("6x8", 1); + g.setColor(notification.contentColor); + g.setFontAlign(-1, -1); + g.drawString(content.body, 10, y + 40); + }, + + hide: () => { + function anim() { + notification.scrollPos += 4; + if (notification.scrollPos > 0) notification.scrollPos = 0; + Bangle.setLCDOffset(notification.scrollPos); + if (notification.scrollPos < 0) setTimeout(anim, 10); + } + anim(); + }, + + isVisible: () => { + return notification.scrollPos != 0; } }; -function draw() { - g.setColor(-1); - if (NRF.getSecurityStatus().connected) - g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")),this.x+1,this.y+1); - else - g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")),this.x+1,this.y+1); -} -function changed() { - WIDGETS["gbridgew"].draw(); - g.flip();// turns screen on -} -NRF.on('connected',changed); -NRF.on('disconnected',changed); + function showNotification(src, title, body) { -WIDGETS["gbridgew"]={area:"tl",width:24,draw:draw}; + // split text up at word boundaries + var txt = body.split("\n"); + var MAXCHARS = 38; + for (var i = 0; i < txt.length; i++) { + txt[i] = txt[i].trim(); + var l = txt[i]; + if (l.length > MAXCHARS) { + var p = MAXCHARS; + while (p > MAXCHARS - 8 && !" \t-_".includes(l[p])) + p--; + if (p == MAXCHARS - 8) p = MAXCHARS; + txt[i] = l.substr(0, p); + txt.splice(i + 1, 0, l.substr(p)); + } + } + var content = { + title: src, + header: title, + body: txt.join("\n") + }; + + notification.show(80, content); + Bangle.buzz(); + } + + function updateMusicInfo() { + if (state.music == gb.musicState.PLAY) { + + var content = { + title: "Now playing", + icon: "gbridge-music-ico.img", + header: state.musicInfo.artist, + body: state.musicInfo.track + }; + + notification.show(40, content); + } else { + notification.hide(); + } + } + + function handleCall(cmd, name, number) { + switch(cmd) { + + case gb.callCommands.ACCEPT: + notification.show(80, { + title: "Call incoming", + icon: "gbridge-call-ico.img", + header: name, + body: number + }); + Bangle.buzz(); + break; + + default: + if (state.debug) { + showNotification(cmd, name, number); + } + } + } + + global.GB = (event) => { + switch (event.t) { + case "notify": + showNotification(event.src, event.title, event.body); + break; + case "musicinfo": + state.musicInfo = event; + break; + case "musicstate": + state.musicInfo = event.state; + updateMusicInfo(); + break; + case "call": + handleCall(event.cmd, event.name, event.number); + break; + default: + if (state.debug) { + showNotification("Gadgetbridge", event.t, JSON.stringify(event)); + } + } + }; + + // Touch control + Bangle.on("touch", () => { + if (notification.isVisible()) { + notification.hide(); + } + }); + + Bangle.on("swipe", (dir) => { + if (state.music == gb.musicState.PLAY) { + gb.controlMusic(dir > 0 ? gb.muiscControl.NEXT : gb.muiscControl.PREV); + } + }); + + function drawIcon() { + g.setColor(-1); + + let icon; + if (NRF.getSecurityStatus().connected) { + icon = require("Storage").read("gbridge-on-ico.img"); + } else { + icon = require("Storage").read("gbridge-off-ico.img"); + } + + g.drawImage(icon, this.x + 1, this.y + 1); + } + + function changedConnectionState() { + WIDGETS["gbridgew"].draw(); + g.flip(); // turns screen on + } + + NRF.on("connected", changedConnectionState); + NRF.on("disconnected", changedConnectionState); + + WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: drawIcon }; + + gb.reportBatteryLevel(); })(); From 33a88627d9876ce48fe8d26ffa5d973289fe0c1a Mon Sep 17 00:00:00 2001 From: Simon Weis Date: Sat, 28 Mar 2020 13:30:13 +0100 Subject: [PATCH 02/17] Fix music notifications --- apps/gbridge/widget.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index 151dd3ad3..0b68d3e1b 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -153,10 +153,10 @@ if (state.music == gb.musicState.PLAY) { var content = { - title: "Now playing", + title: state.musicInfo.artist, icon: "gbridge-music-ico.img", - header: state.musicInfo.artist, - body: state.musicInfo.track + header: state.musicInfo.track, + body:"" }; notification.show(40, content); @@ -194,7 +194,7 @@ state.musicInfo = event; break; case "musicstate": - state.musicInfo = event.state; + state.music = event.state; updateMusicInfo(); break; case "call": From 63d4dbb101298caf4b25585c67a407af6f9e806b Mon Sep 17 00:00:00 2001 From: Simon Weis Date: Thu, 2 Apr 2020 20:36:58 +0200 Subject: [PATCH 03/17] Inline variables and restore notification code --- apps.json | 6 +- apps/gbridge/gbridge-call-ico.js | 1 - apps/gbridge/gbridge-music-ico.js | 1 - apps/gbridge/gbridge-off-ico.js | 1 - apps/gbridge/gbridge-on-ico.js | 1 - apps/gbridge/widget.js | 271 ++++++++++++------------------ 6 files changed, 111 insertions(+), 170 deletions(-) delete mode 100644 apps/gbridge/gbridge-call-ico.js delete mode 100644 apps/gbridge/gbridge-music-ico.js delete mode 100644 apps/gbridge/gbridge-off-ico.js delete mode 100644 apps/gbridge/gbridge-on-ico.js diff --git a/apps.json b/apps.json index b5350d349..51a8f5af5 100644 --- a/apps.json +++ b/apps.json @@ -72,11 +72,7 @@ "storage": [ {"name":"gbridge.app.js","url":"app.js"}, {"name":"gbridge.img","url":"app-icon.js","evaluate":true}, - {"name":"gbridge.wid.js","url":"widget.js"}, - {"name":"gbridge-music-ico.img","url":"gbridge-music-ico.js","evaluate":true}, - {"name":"gbridge-off-ico.img","url":"gbridge-off-ico.js","evaluate":true}, - {"name":"gbridge-on-ico.img","url":"gbridge-on-ico.js","evaluate":true}, - {"name":"gbridge-call-ico.img","url":"gbridge-call-ico.js","evaluate":true} + {"name":"gbridge.wid.js","url":"widget.js"} ] }, { "id": "mclock", diff --git a/apps/gbridge/gbridge-call-ico.js b/apps/gbridge/gbridge-call-ico.js deleted file mode 100644 index ce722a9a8..000000000 --- a/apps/gbridge/gbridge-call-ico.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw=")) \ No newline at end of file diff --git a/apps/gbridge/gbridge-music-ico.js b/apps/gbridge/gbridge-music-ico.js deleted file mode 100644 index ff8f80883..000000000 --- a/apps/gbridge/gbridge-music-ico.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")) \ No newline at end of file diff --git a/apps/gbridge/gbridge-off-ico.js b/apps/gbridge/gbridge-off-ico.js deleted file mode 100644 index 1a722f372..000000000 --- a/apps/gbridge/gbridge-off-ico.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")) \ No newline at end of file diff --git a/apps/gbridge/gbridge-on-ico.js b/apps/gbridge/gbridge-on-ico.js deleted file mode 100644 index f40c4149f..000000000 --- a/apps/gbridge/gbridge-on-ico.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")) \ No newline at end of file diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index 0b68d3e1b..d60492c20 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -1,130 +1,64 @@ (() => { - const gb = { - musicState: { - STOP: "stop", - PLAY: "play", - PAUSE: "pause" - }, - - muiscControl: { - NEXT: "next", - PREV: "previous" - }, - - callCommands: { - UNDEFINED: "undefined", - ACCEPT: "accept", - INCOMING: "incoming", - OUTGOING: "outgoing", - REJECT: "reject", - START: "start", - END: "end" - }, - - send: (message) => { - Bluetooth.println(JSON.stringify(message)); - }, - - controlMusic: (operation) => { - gb.send({ t: "music", n: operation }); - }, - - reportBatteryLevel: () => { - gb.send({ t: "status", bat: E.getBattery() }); - }, - }; - const state = { - music: gb.musicState.STOP, + music: "stop", musicInfo: { artist: "", album: "", track: "" }, - debug: false, + + scrollPos: 0 }; - const notification = { + function gbSend(message) { + Bluetooth.println(JSON.stringify(message)); + } - backgroundColor: "#222222", - frameColor: "#ffffff", - titleColor: "#40d040", - contentColor: "#ffffff", - scrollPos: 0, + function showNotification(size, render) { + var oldMode = Bangle.getLCDMode(); - show: (size, content) => { - var oldMode = Bangle.getLCDMode(); - Bangle.setLCDMode("direct"); + Bangle.setLCDMode("direct"); + g.setClipRect(0, 240, 239, 319); + g.setColor("#222222"); + g.fillRect(1, 241, 238, 318); - g.setClipRect(0, 240, 239, 319); - g.setColor(notification.backgroundColor); - g.fillRect(1, 241, 238, 318); + render(320 - size); - notification.drawContent(320 - size, content); + g.setColor("#ffffff"); + g.fillRect(0, 240, 1, 319); + g.fillRect(238, 240, 239, 319); + g.fillRect(2, 318, 238, 319); - g.setColor(notification.frameColor); - g.fillRect(0, 240, 1, 319); - g.fillRect(238, 240, 239, 319); - g.fillRect(2, 318, 238, 319); + Bangle.setLCDPower(1); // light up + Bangle.setLCDMode(oldMode); // clears cliprect - Bangle.setLCDPower(1); // light up - Bangle.setLCDMode(oldMode); // clears cliprect - - function anim() { - notification.scrollPos -= 2; - if (notification.scrollPos < -size) notification.scrollPos = -size; - Bangle.setLCDOffset(notification.scrollPos); - if (notification.scrollPos > -size) setTimeout(anim, 10); + function anim() { + state.scrollPos -= 2; + if (state.scrollPos < -size) { + state.scrollPos = -size; } - anim(); - }, - - drawContent: (y, content) => { - - if (content.icon !== undefined) { - g.setColor(notification.contentColor); - const icon = require("Storage").read(content.icon); - g.drawImage(icon, 8, y + 8); - } - - var x = 120; - g.setFontAlign(0, 0); - - g.setFont("6x8", 1); - g.setColor(notification.titleColor); - g.drawString(content.title, x, y + 7); - - g.setColor(notification.contentColor); - g.setFont("6x8", 2); - g.drawString(content.header, x, y + 25); - - g.setFont("6x8", 1); - g.setColor(notification.contentColor); - g.setFontAlign(-1, -1); - g.drawString(content.body, 10, y + 40); - }, - - hide: () => { - function anim() { - notification.scrollPos += 4; - if (notification.scrollPos > 0) notification.scrollPos = 0; - Bangle.setLCDOffset(notification.scrollPos); - if (notification.scrollPos < 0) setTimeout(anim, 10); - } - anim(); - }, - - isVisible: () => { - return notification.scrollPos != 0; + Bangle.setLCDOffset(state.scrollPos); + if (state.scrollPos > -size) setTimeout(anim, 10); } - }; + anim(); + } - function showNotification(src, title, body) { + function hideNotification() { + function anim() { + state.scrollPos += 4; + if (state.scrollPos > 0) state.scrollPos = 0; + Bangle.setLCDOffset(state.scrollPos); + if (state.scrollPos < 0) setTimeout(anim, 10); + } + anim(); + } + + function handleNotificationEvent(event) { // split text up at word boundaries - var txt = body.split("\n"); + var txt = event.body.split("\n"); var MAXCHARS = 38; for (var i = 0; i < txt.length; i++) { txt[i] = txt[i].trim(); @@ -139,98 +73,113 @@ } } - var content = { - title: src, - header: title, - body: txt.join("\n") - }; + showNotification(80, (y) => { + + // TODO: icon based on src? + var x = 120; + g.setFontAlign(0, 0); + g.setFont("6x8", 1); + g.setColor("#40d040"); + g.drawString(event.src, x, y + 7); + + g.setColor("#ffffff"); + g.setFont("6x8", 2); + g.drawString(event.title, x, y + 25); + + g.setFont("6x8", 1); + g.setColor("#ffffff"); + g.setFontAlign(-1, -1); + g.drawString(txt.join("\n"), 10, y + 40); + }); - notification.show(80, content); Bangle.buzz(); } - function updateMusicInfo() { - if (state.music == gb.musicState.PLAY) { + function handleMusicStateUpdate(event) { + state.music = event.state - var content = { - title: state.musicInfo.artist, - icon: "gbridge-music-ico.img", - header: state.musicInfo.track, - body:"" - }; + if (state.music == "play") { + showNotification(40, (y) => { + g.setColor("#ffffff"); + g.drawImage(require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")), 8, y + 8); - notification.show(40, content); - } else { - notification.hide(); + g.setFontAlign(-1, -1); + var x = 40; + g.setFont("4x6", 2); + g.setColor("#ffffff"); + g.drawString(state.musicInfo.artist, x, y + 8); + + g.setFont("6x8", 1); + g.setColor("#ffffff"); + g.drawString(state.musicInfo.track, x, y + 22); + }); + } + + if (state.music == "pause") { + hideNotification(); } } - function handleCall(cmd, name, number) { - switch(cmd) { + function handleCallEvent(event) { - case gb.callCommands.ACCEPT: - notification.show(80, { - title: "Call incoming", - icon: "gbridge-call-ico.img", - header: name, - body: number - }); - Bangle.buzz(); + if (event.cmd == "accept") { + showNotification(40, (y) => { + g.setColor("#ffffff"); + g.drawImage(require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw=")), 8, y + 8); + + g.setFontAlign(-1, -1); + var x = 40; + g.setFont("4x6", 2); + g.setColor("#ffffff"); + g.drawString(event.name, x, y + 8); + + g.setFont("6x8", 1); + g.setColor("#ffffff"); + g.drawString(event.number, x, y + 22); + }); + + Bangle.buzz(); break; - - default: - if (state.debug) { - showNotification(cmd, name, number); - } } } global.GB = (event) => { switch (event.t) { case "notify": - showNotification(event.src, event.title, event.body); + handleNotificationEvent(event); break; case "musicinfo": state.musicInfo = event; break; case "musicstate": - state.music = event.state; - updateMusicInfo(); + handleMusicStateUpdate(event); break; case "call": - handleCall(event.cmd, event.name, event.number); + handleCallEvent(event); break; - default: - if (state.debug) { - showNotification("Gadgetbridge", event.t, JSON.stringify(event)); - } } }; // Touch control Bangle.on("touch", () => { - if (notification.isVisible()) { - notification.hide(); + if (state.scrollPos) { + hideNotification(); } }); Bangle.on("swipe", (dir) => { - if (state.music == gb.musicState.PLAY) { - gb.controlMusic(dir > 0 ? gb.muiscControl.NEXT : gb.muiscControl.PREV); + if (state.music == "play") { + const command = dir > 0 ? "next" : "previous" + gbSend({ t: "music", n: command }); } }); - function drawIcon() { + function draw() { g.setColor(-1); - - let icon; - if (NRF.getSecurityStatus().connected) { - icon = require("Storage").read("gbridge-on-ico.img"); - } else { - icon = require("Storage").read("gbridge-off-ico.img"); - } - - g.drawImage(icon, this.x + 1, this.y + 1); + if (NRF.getSecurityStatus().connected) + g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")), this.x + 1, this.y + 1); + else + g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")), this.x + 1, this.y + 1); } function changedConnectionState() { @@ -241,7 +190,7 @@ NRF.on("connected", changedConnectionState); NRF.on("disconnected", changedConnectionState); - WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: drawIcon }; + WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: draw }; - gb.reportBatteryLevel(); + gbSend({ t: "status", bat: E.getBattery() }); })(); From d60af1dabdde79ee6e2e0f25dce6bdc531f58a7e Mon Sep 17 00:00:00 2001 From: Simon Weis Date: Thu, 2 Apr 2020 20:40:56 +0200 Subject: [PATCH 04/17] Remove lost break --- apps/gbridge/widget.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index d60492c20..2042ea7a0 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -139,7 +139,6 @@ }); Bangle.buzz(); - break; } } From e7c8ea9813eba5813d2b374cb0da834d35950f31 Mon Sep 17 00:00:00 2001 From: Dimitri Gigot Date: Thu, 2 Apr 2020 20:29:37 +0000 Subject: [PATCH 05/17] add touch and swipe support --- apps/toucher/app.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/toucher/app.js b/apps/toucher/app.js index 1b5a9186c..2b80198c9 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -103,10 +103,28 @@ function drawMenu(){ } drawMenu(); + +// Physical buttons setWatch(prev, BTN1, {repeat:true}); -setWatch(prev, BTN4, {repeat:true}); - setWatch(next, BTN3, {repeat:true}); -setWatch(next, BTN5, {repeat:true}); - setWatch(run, BTN2, {repeat:true,edge:"falling"}); + +// Screen event +Bangle.on('touch', function(button){ + switch(button){ + case 1: + prev(); + break; + case 2: + next(); + break; + case 3: + run(); + break; + } +}); + +Bangle.on('swipe', dir => { + if(dir == 1) prev(); + else next(); +}); \ No newline at end of file From f143f3eac50da3dc1ef6fe4616bd0f0cf690b15d Mon Sep 17 00:00:00 2001 From: Dimitri Gigot Date: Thu, 2 Apr 2020 20:31:21 +0000 Subject: [PATCH 06/17] Increase version --- apps.json | 2 +- apps/toucher/ChangeLog | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 2bd8ef7bf..e0f5142af 100644 --- a/apps.json +++ b/apps.json @@ -999,7 +999,7 @@ "name": "Touch Launcher", "shortName":"Menu", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Touch enable left to right launcher.", "tags": "tool,system,launcher", "type":"launch", diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog index 5560f00bc..bd3d5d225 100644 --- a/apps/toucher/ChangeLog +++ b/apps/toucher/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Add swipe support and doucle tap to run application \ No newline at end of file From 2ed00456bf12366f373f0d5a261d0ed36caa4238 Mon Sep 17 00:00:00 2001 From: nic Date: Fri, 3 Apr 2020 00:34:18 -0300 Subject: [PATCH 07/17] Addded Portuguese from Brazil --- apps/locale/locales.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 42a2225e4..7f6fbaef9 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -370,4 +370,21 @@ var locales = { abday: "Vas,Hét,Ke,Szer,Csüt,Pén,Szom", day: "Vasárnap,Hétfő,Kedd,Szerda,Csütörtök,Péntek,Szombat", trans: { yes: "igen", Yes: "Igen", no: "nem", No: "Nem", ok: "ok", on: "be", off: "ki" }}, + "pt_BR": { + lang: "pt_BR", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "R$", currency_first:true, + int_curr_symbol: "BRL", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"am",1:"pm"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "", 1: "%d/%m/%y" }, + abmonth: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", + month: "Janeiro,Fevereiro,Março,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro", + abday: "Dom,Seg,Ter,Qua,Qui,Sex,Sab", + day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado", + trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "certo", on: "ligado", off: "desligado" }}, }; From b135c097cf28f5ada86c5acdae019197ede1f6b7 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 3 Apr 2020 08:16:42 +0100 Subject: [PATCH 08/17] Add unicode flags for Language --- apps/locale/locale.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/locale/locale.html b/apps/locale/locale.html index 5d7882e00..5cb4b4598 100644 --- a/apps/locale/locale.html +++ b/apps/locale/locale.html @@ -56,7 +56,14 @@ exports = { name : "en_GB", currencySym:"£", }); var languageSelector = document.getElementById("languages"); - languageSelector.innerHTML = Object.keys(locales).map(l=>``).join("\n"); + languageSelector.innerHTML = Object.keys(locales).map(l=>{ + var localeParts = l.split("_"); // en_GB -> ["en","GB"] + var icon = ""; + // If we have a 2 char ISO country code, use it to get the unicode flag + if (localeParts[1] && localeParts[1].length==2) + icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" "; + return `` + }).join("\n"); document.getElementById("upload").addEventListener("click", function() { From 4f60a450227c840f7f7faa9e358eda14f485bf5f Mon Sep 17 00:00:00 2001 From: Paul Cockrell Date: Fri, 3 Apr 2020 09:16:22 +0100 Subject: [PATCH 09/17] Bug fix - use 12 / 24 hr clock based on user settings --- apps.json | 2 +- apps/marioclock/ChangeLog | 1 + apps/marioclock/marioclock-app.js | 15 +++++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps.json b/apps.json index e0f5142af..3bdc55f07 100644 --- a/apps.json +++ b/apps.json @@ -892,7 +892,7 @@ { "id": "marioclock", "name": "Mario Clock", "icon": "marioclock.png", - "version":"0.04", + "version":"0.05", "description": "Animated Mario clock, jumps to change the time!", "tags": "clock,mario,retro", "type": "clock", diff --git a/apps/marioclock/ChangeLog b/apps/marioclock/ChangeLog index 4334ad92c..74db9bc18 100644 --- a/apps/marioclock/ChangeLog +++ b/apps/marioclock/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fix day of the week and add padding 0.03: use short date format from locale, take timeout from settings 0.04: modify date to display to be more at the original idea but still localized +0.05: use 12/24 hour clock from settings diff --git a/apps/marioclock/marioclock-app.js b/apps/marioclock/marioclock-app.js index ecbaba38a..49ae20b76 100644 --- a/apps/marioclock/marioclock-app.js +++ b/apps/marioclock/marioclock-app.js @@ -1,14 +1,15 @@ /********************************** - BangleJS MARIO CLOCK V0.1.0 + BangleJS MARIO CLOCK + Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock + Converting images to 1bit BMP: Image > Mode > Indexed and tick the "Use black and white (1-bit) palette", Then export as BMP. + Online Image convertor: https://www.espruino.com/Image+Converter **********************************/ -var locale = require("locale"); +const locale = require("locale"); const storage = require('Storage'); -const settings = (storage.readJSON('setting.json',1)||{}); -const timeout = settings.timeout||10; +const settings = (storage.readJSON('setting.json',1) || {}); +const timeout = settings.timeout || 10; +const is12Hour = settings["12hour"] || false; // Screen dimensions let W, H; @@ -273,7 +274,8 @@ function drawTime() { drawBrick(42, 25); const t = new Date(); - const hours = ("0" + t.getHours()).substr(-2); + const h = t.getHours(); + const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2); const mins = ("0" + t.getMinutes()).substr(-2); g.setFont("6x8"); @@ -374,8 +376,9 @@ function init() { Bangle.setLCDPower(true); } }); + + startTimers(); } // Initialise! init(); -startTimers(); From 50322a0f05b4d152b66740fed12f3018ddc83593 Mon Sep 17 00:00:00 2001 From: Paul Cockrell Date: Fri, 3 Apr 2020 09:18:05 +0100 Subject: [PATCH 10/17] Lint --- apps/marioclock/marioclock-app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/marioclock/marioclock-app.js b/apps/marioclock/marioclock-app.js index 49ae20b76..2eeb21c97 100644 --- a/apps/marioclock/marioclock-app.js +++ b/apps/marioclock/marioclock-app.js @@ -7,7 +7,7 @@ const locale = require("locale"); const storage = require('Storage'); -const settings = (storage.readJSON('setting.json',1) || {}); +const settings = (storage.readJSON('setting.json', 1) || {}); const timeout = settings.timeout || 10; const is12Hour = settings["12hour"] || false; From 6bac8e982b1b116e6b9f5e5ffd18a8557a7b5f11 Mon Sep 17 00:00:00 2001 From: Stefano Baldan Date: Fri, 3 Apr 2020 10:22:14 +0200 Subject: [PATCH 11/17] RPG dice app --- apps.json | 14 +++++++ apps/rpgdice/ChangeLog | 1 + apps/rpgdice/app-icon.js | 1 + apps/rpgdice/app.js | 86 +++++++++++++++++++++++++++++++++++++++ apps/rpgdice/rpgdice.png | Bin 0 -> 6491 bytes 5 files changed, 102 insertions(+) create mode 100755 apps/rpgdice/ChangeLog create mode 100755 apps/rpgdice/app-icon.js create mode 100755 apps/rpgdice/app.js create mode 100755 apps/rpgdice/rpgdice.png diff --git a/apps.json b/apps.json index 3bdc55f07..15f6a0cda 100644 --- a/apps.json +++ b/apps.json @@ -1020,5 +1020,19 @@ {"name":"balltastic.app.js","url":"app.js"}, {"name":"balltastic.img","url":"app-icon.js","evaluate":true} ] + }, + { + "id": "rpgdice", + "name": "RPG dice", + "icon": "rpgdice.png", + "version": "0.01", + "description": "Simple RPG dice rolling app.", + "tags": "game,fun", + "type": "app", + "allow_emulator": true, + "storage": [ + {"name":"rpgdice.app.js","url": "app.js"}, + {"name":"rpgdice.img","url": "app-icon.js","evaluate":true} + ] } ] diff --git a/apps/rpgdice/ChangeLog b/apps/rpgdice/ChangeLog new file mode 100755 index 000000000..7b83706bf --- /dev/null +++ b/apps/rpgdice/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/rpgdice/app-icon.js b/apps/rpgdice/app-icon.js new file mode 100755 index 000000000..d6fd1fda5 --- /dev/null +++ b/apps/rpgdice/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgMJgMQgMZzOREaERiERzIACiIVOAIIUCz///ORgIXNIQIAC/4ABJJYsBCogYEAYMQiAWGLAoAJJI8JLAYoCAAgJBJIIwGBohxBJI4YBJIwOFC4w5EC4hdOzIgCyLFDC45hHAAZJDgJAKMQwyBSYSOBxIXGPRTdChOfxChHbpRhBC4P5GAgAOgEZFAKjIBAz1EC5YYJxAvBJ4IXQzGIxEQB4RbPCoOIwEAOKAsCC4QvCFiAXDdwwsMC5eebogVGAALWBC42f/AWLC4zwCUgIEBCxK+DE4bsFC5+f/IrBC4RzHXwkZzATEDgP/RZAXFz5ECf4oXMCYKICC6hABMAQXOgAXBLgLrHRxZfCC6sBCo4XLLwIXBbAgXRMIQAGRxgwChIXVgEQIYimOGAZ6CSgOJC6CrCC4TZBC6IwCC4QWQPQYXKOggAFPQOfC5AWKPQgXGCpR6FOwoWOPQQXDIZYwHC4QVRAAQXBBxgA=")) \ No newline at end of file diff --git a/apps/rpgdice/app.js b/apps/rpgdice/app.js new file mode 100755 index 000000000..2007d6ab0 --- /dev/null +++ b/apps/rpgdice/app.js @@ -0,0 +1,86 @@ +const dice = [4, 6, 8, 10, 12, 20, 100]; +const nFlips = 20; +const delay = 500; + +let dieIndex = 1; +let face = 0; +let rolling = false; + +let bgColor; +let fgColor; + +function getDie() { + return dice[dieIndex]; +} + +function setColors(lastBounce) { + if (lastBounce) { + bgColor = 0xFFFF; + fgColor = 0x0000; + } else { + bgColor = 0x0000 + fgColor = 0xFFFF; + } +} + +function flipFace() { + while(true) { + let newFace = Math.floor(Math.random() * getDie()) + 1; + if (newFace !== face) { + face = newFace; + break; + } + } +} + +function draw() { + g.setColor(bgColor); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + g.setColor(fgColor); + g.setFontAlign(0, 0); + g.setFontVector(40); + g.drawString('d' + getDie(), 180, 30); + g.setFontVector(100); + g.drawString(face, 120, 120); +} + +function roll(bounces) { + flipFace(); + setColors(bounces === 0); + draw(); + if (bounces > 0) { + setTimeout(() => roll(bounces - 1), delay / bounces); + } else { + rolling = false; + } +} + +function startRolling() { + if (rolling) return; + rolling = true; + roll(nFlips); +} + +function changeDie() { + if (rolling) return; + dieIndex = (dieIndex + 1) % dice.length; + draw(); +} + +Bangle.on('lcdPower',function(on) { + if (on) { + startRolling(); + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +startRolling(); + +// Top button rolls the die, bottom button changes it +setWatch(startRolling, BTN1, {repeat:true}); +setWatch(changeDie, BTN3, {repeat:true}); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); diff --git a/apps/rpgdice/rpgdice.png b/apps/rpgdice/rpgdice.png new file mode 100755 index 0000000000000000000000000000000000000000..d14b9c8368ff88c94648cdde8487a412985850b7 GIT binary patch literal 6491 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 Bk5&Kx literal 0 HcmV?d00001 From 7b79074cb7e0d70639bde8c7cbacd123b7d4d636 Mon Sep 17 00:00:00 2001 From: MaBecker Date: Fri, 3 Apr 2020 13:13:14 +0200 Subject: [PATCH 12/17] add wigdet moon phase --- apps.json | 13 ++++++++++++- apps/widmp/ChangeLog | 1 + apps/widmp/widget.js | 33 +++++++++++++++++++++++++++++++++ apps/widmp/widget.png | Bin 0 -> 2075 bytes 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 apps/widmp/ChangeLog create mode 100644 apps/widmp/widget.js create mode 100644 apps/widmp/widget.png diff --git a/apps.json b/apps.json index 15f6a0cda..27115f068 100644 --- a/apps.json +++ b/apps.json @@ -1034,5 +1034,16 @@ {"name":"rpgdice.app.js","url": "app.js"}, {"name":"rpgdice.img","url": "app-icon.js","evaluate":true} ] - } + }, + { "id": "widmp", + "name": "Moon Phase Widget", + "icon": "widget.png", + "version":"0.01", + "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases", + "tags": "widget,tools", + "type":"widget", + "storage": [ + {"name":"widmp.wid.js","url":"widget.js"} + ] + }, ] diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/widmp/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js new file mode 100644 index 000000000..be4c2bb39 --- /dev/null +++ b/apps/widmp/widget.js @@ -0,0 +1,33 @@ +/* jshint esversion: 6 */ +(() => { + + const BLACK = 0, MOON = 0x41f, MC = 29.5305882, NM = 694039.09; + var r = 12, mx = 0, my = 0; + + var moon = { + 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, + 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, + 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, + 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, + 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r + r, my + r);}, + 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} + }; + + function moonPhase(d) { + var tmp, month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); + if (month < 3) {year--; month += 12;} + tmp = ((365.25 * year + 30.6 * ++month + day - NM) / MC); + return Math.round(((tmp - (tmp | 0)) * 7)+1); + } + + function draw() { + mx = this.x; my = this.y + 12; + moon[moonPhase(Date())](); + } + + WIDGETS["widmoon"] = { area: "tr", width: 24, draw: draw }; + +})(); diff --git a/apps/widmp/widget.png b/apps/widmp/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..32803f4741583805fdf3d3812c7e6ac30d66748b GIT binary patch literal 2075 zcmY*adpy(YAKzx=(hhTpST=Ku*ael#=6=8Bw}hP(Yc__>vN0m&Qj<$!Zb$N)N^%sH zlT&EX*Ygb5=5gO-eFnu=Kx) z{8z`7!Xn4hm~2`c10mHV9f(U{?I(U0fbIoUMoPbUWJds~tQ(b5VUi^8CPXiK)@ zq*>ct@idCWS*mZ3#eHS|U+%jP4lOnRpTm6X^lMhqs=Xo({o}LQEBbaD69E7@buU+^ zz$n=_M7r<^!_x{~hT>T(B^`$Lx#K}FHK=-yeC^a3k++gLy>Gmsf!4QXMo;(R&yIhn znmauH!DjAx$|L-Snwg1sCESQPS52?;V)^+jr(vBQb+=NlyVDfy5INc3JEx^tpw7HCT4l{Y_T^u~KRCA&QOx3r>Dr z=$or@g(ORkHAb$(OM&g31Nn4dod9a$WL7!`O% zwaM_>B7H^=4M|zWpr<4m^K}dG{-u+d{>~Yq`oij7ZR&qk;rwz!Rb^9D*t1O<%+)8a z^|Y2h>3!6FhSk+sao!afo!A}{bwZ%`cPZ~}P+kUD_K$I<=E-64aMrR3Hq%xhgrq+}f1i zuf&&r(UD(ryoYs~(d4K%oj9B6QQOi+VQzr(F`ew<#$9UVf3w zlr@NLN_ha+u%cZ^%vH3DSY;YPjsvvpgw<^kv!=>D&`U3rBZMqJSaezu&X8X)&pT z^?~`TWzDW-TJ3En*f(YE^=cR+Pn7C&nYCJ%+Mfn$R=JJY((jmU4U((snNq91qf84| zeM3*TJD;GxUN`Gm-Y#;V>w=0RdNRfp!mu0lg=k1+tjYTa&X>}{E5$%vhoD- z`*hxNG>>AevGL5AU~TVkR>h|&ja%J++O#*dYGyiQ$bW$fXL9SaXO7eifb0@NJLhu- zz(;;zIu&|@W!v_bCDf7c4L~lr>&;pB?u5Us7M8x`&c>*WzT1-veQmby`rAI}F7zKk z4rVFo8n=508&Z8I&b}Rtb72t}4_j^^`{`S{bmc7z?M0;tcC(#4L0@+7%l7VZ_E2QG z(UW5iZ7Y#kxNcOvcRtr z*T*9^;W^>^3}i0}ZsZQuH;|iUPwDF_83aJq=kMmr3Rg1VH`~)5KE~@GW$d5S8Z(KE ztTa4kQ#5cbG_1S&9O=kWTJ{`LZ0%*X&2#@}ZkE8STfjNngWIA{1)62dh+X?{z{Z3Z zudl9o8%A%?Yx6)&hM#DA3gs(TE}r+ET%Y!)*FUXssz8Zv7ywB}r&>mqw!7cyBJf*e z{EgDNOL{kg5y>?!9iK2yc3rZ(ATrQ6uAf-o<5*EK_Hdg&=TEG7XlUdRMK?Bojem}U z`&Fa4{V)@?zU;$C#bJ`*k8a5!Xxnc7p4y6$T%*eO}+UOjf`43%$b7TMj literal 0 HcmV?d00001 From 04cdb224474e50f1b22960d68550bbb808c8b769 Mon Sep 17 00:00:00 2001 From: MaBecker Date: Fri, 3 Apr 2020 13:20:52 +0200 Subject: [PATCH 13/17] add widget moon phase --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 27115f068..487e3f835 100644 --- a/apps.json +++ b/apps.json @@ -1045,5 +1045,5 @@ "storage": [ {"name":"widmp.wid.js","url":"widget.js"} ] - }, + } ] From 86ab3706eaea81d99b08611957a700ac06946253 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 3 Apr 2020 13:42:31 +0100 Subject: [PATCH 14/17] Gadgetbridge App 'Connected' state is no longer toggleable --- apps.json | 2 +- apps/gbridge/ChangeLog | 1 + apps/gbridge/app.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 487e3f835..d502b37c4 100644 --- a/apps.json +++ b/apps.json @@ -91,7 +91,7 @@ { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", - "version":"0.05", + "version":"0.06", "description": "The default notification handler for Gadgetbridge notifications from Android", "tags": "tool,system,android,widget", "storage": [ diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index ad6b01d6a..0bcf94e25 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -4,3 +4,4 @@ 0.04: Tweaks for variable size widget system 0.05: Show incoming call notification Optimize animation, limit title length +0.06: Gadgetbridge App 'Connected' state is no longer toggleable diff --git a/apps/gbridge/app.js b/apps/gbridge/app.js index 45dc0e33d..d12f0f768 100644 --- a/apps/gbridge/app.js +++ b/apps/gbridge/app.js @@ -4,7 +4,7 @@ function gb(j) { var mainmenu = { "" : { "title" : "Gadgetbridge" }, - "Connected" : { value : NRF.getSecurityStatus().connected }, + "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, "Find Phone" : function() { E.showMenu(findPhone); }, "Exit" : ()=> {load();}, }; From 9b918055da339385d56ca08bbd328ee6c696c58e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 3 Apr 2020 14:27:45 +0100 Subject: [PATCH 15/17] Fix progress bar - now goes smoothly up over the course of the app upload. Also tidy it up significantly and reduce duplication --- index.html | 1 + js/comms.js | 41 ++++++++++++--- js/index.js | 144 ++++------------------------------------------------ js/ui.js | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 140 deletions(-) create mode 100644 js/ui.js diff --git a/index.html b/index.html index efaf84a61..6e1a9b554 100644 --- a/index.html +++ b/index.html @@ -129,6 +129,7 @@ + diff --git a/js/comms.js b/js/comms.js index e2cbf0cdd..91ae54b68 100644 --- a/js/comms.js +++ b/js/comms.js @@ -9,14 +9,19 @@ reset : (opt) => new Promise((resolve,reject) => { }); }), uploadApp : (app,skipReset) => { + Progress.show({title:`Uploading ${app.name}`,sticky:true}); return AppInfo.getFiles(app, httpGet).then(fileContents => { return new Promise((resolve,reject) => { console.log("uploadApp",fileContents.map(f=>f.name).join(", ")); + var maxBytes = fileContents.reduce((b,f)=>b+f.content.length, 0)||1; + var currentBytes = 0; + // Upload each file one at a time function doUploadFiles() { // No files left - print 'reboot' message if (fileContents.length==0) { Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { + Progress.hide({sticky:true}); if (result===null) return reject(""); resolve(app); }); @@ -24,17 +29,27 @@ uploadApp : (app,skipReset) => { } var f = fileContents.shift(); console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`); + Progress.show({ + min:currentBytes / maxBytes, + max:(currentBytes+f.content.length) / maxBytes}); + currentBytes += f.content.length; // Chould check CRC here if needed instead of returning 'OK'... // E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) Puck.write(`\x10${f.cmd};Bluetooth.println("OK")\n`,(result) => { - if (!result || result.trim()!="OK") return reject("Unexpected response "+(result||"")); + if (!result || result.trim()!="OK") { + Progress.hide({sticky:true}); + return reject("Unexpected response "+(result||"")); + } doUploadFiles(); }, true); // wait for a newline } // Start the upload function doUpload() { Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => { - if (result===null) return reject(""); + if (result===null) { + Progress.hide({sticky:true}); + return reject(""); + } doUploadFiles(); }); } @@ -48,10 +63,15 @@ uploadApp : (app,skipReset) => { }); }, getInstalledApps : () => { + Progress.show({title:`Getting app list...`,sticky:true}); return new Promise((resolve,reject) => { Puck.write("\x03",(result) => { - if (result===null) return reject(""); + if (result===null) { + Progress.hide({sticky:true}); + return reject(""); + } Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => { + Progress.hide({sticky:true}); if (appList===null) return reject(err || ""); console.log("getInstalledApps", appList); resolve(appList); @@ -60,6 +80,7 @@ getInstalledApps : () => { }); }, removeApp : app => { // expects an app structure + Progress.show({title:`Removing ${app.name}`,sticky:true}); var storage = [{name:app.id+".info"}].concat(app.storage); var cmds = storage.map(file=>{ return `\x10require("Storage").erase(${toJS(file.name)});\n`; @@ -67,15 +88,21 @@ removeApp : app => { // expects an app structure console.log("removeApp", cmds); return Comms.reset().then(new Promise((resolve,reject) => { Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { + Progress.hide({sticky:true}); if (result===null) return reject(""); resolve(); }); - })); + })).catch(function(reason) { + Progress.hide({sticky:true}); + return Promise.reject(reason); + }); }, removeAllApps : () => { + Progress.show({title:"Removing all apps",progess:"animate",sticky:true}); return new Promise((resolve,reject) => { // Use write with newline here so we wait for it to finish Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => { + Progress.hide({sticky:true}); if (!result || result.trim()!="OK") return reject(err || ""); resolve(); }, true /* wait for newline */); @@ -171,10 +198,10 @@ readStorageFile : (filename) => { // StorageFiles are different to normal storag fileContent = fileContent.substr(newLineIdx+1); } } else { - showProgress(undefined,100*fileContent.length / (fileSize||1000000)); + Progress.show({percent:100*fileContent.length / (fileSize||1000000)}); } if (finished) { - hideProgress(); + Progress.hide(); connection.received = ""; connection.cb = undefined; resolve(fileContent); @@ -188,7 +215,7 @@ readStorageFile : (filename) => { // StorageFiles are different to normal storag while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); } Bluetooth.print("\xFF"); })()\n`,() => { - showProgress(`Reading ${JSON.stringify(filename)}`,0); + Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0}); console.log(`StorageFile read started...`); }); }); diff --git a/js/index.js b/js/index.js index b21fc907d..60b66436a 100644 --- a/js/index.js +++ b/js/index.js @@ -14,119 +14,7 @@ httpGet("apps.json").then(apps=>{ refreshFilter(); }); -// Status // =========================================== Top Navigation -function showToast(message, type) { - // toast-primary, toast-success, toast-warning or toast-error - var style = "toast-primary"; - if (type=="success") style = "toast-success"; - else if (type=="error") style = "toast-error"; - else if (type!==undefined) console.log("showToast: unknown toast "+type); - var toastcontainer = document.getElementById("toastcontainer"); - var msgDiv = htmlElement(`
`); - msgDiv.innerHTML = message; - toastcontainer.append(msgDiv); - setTimeout(function() { - msgDiv.remove(); - }, 5000); -} -var progressToast; // the DOM element -var progressSticky; // showProgress(,,"sticky") don't remove until hideProgress("sticky") -var progressInterval; // the interval used if showProgress(..., "animate") -var progressPercent; // the current progress percentage -function showProgress(text, percent, sticky) { - if (sticky=="sticky") - progressSticky = true; - if (!progressToast) { - if (progressInterval) { - clearInterval(progressInterval); - progressInterval = undefined; - } - if (percent == "animate") { - progressInterval = setInterval(function() { - progressPercent += 2; - if (progressPercent>100) progressPercent=0; - showProgress(undefined, progressPercent); - }, 100); - percent = 0; - } - progressPercent = percent; - - var toastcontainer = document.getElementById("toastcontainer"); - progressToast = htmlElement(`
- ${text ? `
${text}
`:``} -
-
-
-
`); - toastcontainer.append(progressToast); - } else { - var pt=document.getElementById("progressToast"); - pt.setAttribute("aria-valuenow",percent); - pt.style.width = percent+"%"; - } -} -function hideProgress(sticky) { - if (progressSticky && sticky!="sticky") - return; - progressSticky = false; - if (progressInterval) { - clearInterval(progressInterval); - progressInterval = undefined; - } - if (progressToast) progressToast.remove(); - progressToast = undefined; -} - -Puck.writeProgress = function(charsSent, charsTotal) { - if (charsSent===undefined) { - hideProgress(); - return; - } - var percent = Math.round(charsSent*100/charsTotal); - showProgress(undefined, percent); -} -function showPrompt(title, text, buttons) { - if (!buttons) buttons={yes:1,no:1}; - return new Promise((resolve,reject) => { - var modal = htmlElement(``); - document.body.append(modal); - modal.querySelector("a[href='#close']").addEventListener("click",event => { - event.preventDefault(); - reject("User cancelled"); - modal.remove(); - }); - htmlToArray(modal.getElementsByTagName("button")).forEach(button => { - button.addEventListener("click",event => { - event.preventDefault(); - var isYes = event.target.getAttribute("isyes")=="1"; - if (isYes) resolve(); - else reject("User cancelled"); - modal.remove(); - }); - }); - }); -} function showChangeLog(appid) { var app = appNameToApp(appid); function show(contents) { @@ -170,12 +58,11 @@ function handleCustomApp(appTemplate) { Object.keys(appFiles).forEach(k => app[k] = appFiles[k]); console.log("Received custom app", app); modal.remove(); - showProgress(`Uploading ${app.name}`,undefined,"sticky"); Comms.uploadApp(app).then(()=>{ - hideProgress("sticky"); + Progress.hide({sticky:true}); resolve(); }).catch(e => { - hideProgress("sticky"); + Progress.hide({sticky:true}); reject(e); }); }, false); @@ -334,9 +221,8 @@ function refreshLibrary() { // upload icon.classList.remove("icon-upload"); icon.classList.add("loading"); - showProgress(`Uploading ${app.name}`,undefined,"sticky"); Comms.uploadApp(app).then((appJSON) => { - hideProgress("sticky"); + Progress.hide({sticky:true}); if (appJSON) appsInstalled.push(appJSON); showToast(app.name+" Uploaded!", "success"); icon.classList.remove("loading"); @@ -344,7 +230,7 @@ function refreshLibrary() { refreshMyApps(); refreshLibrary(); }).catch(err => { - hideProgress("sticky"); + Progress.hide({sticky:true}); showToast("Upload failed, "+err, "error"); icon.classList.remove("loading"); icon.classList.add("icon-upload"); @@ -403,19 +289,16 @@ function customApp(app) { function updateApp(app) { if (app.custom) return customApp(app); - showProgress(`Upgrading ${app.name}`,undefined,"sticky"); return Comms.removeApp(app).then(()=>{ showToast(app.name+" removed successfully. Updating...",); appsInstalled = appsInstalled.filter(a=>a.id!=app.id); return Comms.uploadApp(app); }).then((appJSON) => { - hideProgress("sticky"); if (appJSON) appsInstalled.push(appJSON); showToast(app.name+" Updated!", "success"); refreshMyApps(); refreshLibrary(); }, err=>{ - hideProgress("sticky"); showToast(app.name+" update failed, "+err,"error"); refreshMyApps(); refreshLibrary(); @@ -488,18 +371,15 @@ return `
function getInstalledApps() { showLoadingIndicator("myappscontainer"); - showProgress(`Getting app list...`,undefined,"sticky"); // Get apps and files return Comms.getInstalledApps() .then(appJSON => { - hideProgress("sticky"); appsInstalled = appJSON; refreshMyApps(); refreshLibrary(); }) .then(() => handleConnectionChange(true)) .catch(err=>{ - hideProgress("sticky"); return Promise.reject(); }); } @@ -555,15 +435,14 @@ document.getElementById("settime").addEventListener("click",event=>{ }); document.getElementById("removeall").addEventListener("click",event=>{ showPrompt("Remove All","Really remove all apps?").then(() => { - showProgress("Removing all apps","animate", "sticky"); return Comms.removeAllApps(); }).then(()=>{ - hideProgress("sticky"); + Progress.hide({sticky:true}); appsInstalled = []; showToast("All apps removed","success"); return getInstalledApps(); }).catch(err=>{ - hideProgress("sticky"); + Progress.hide({sticky:true}); showToast("App removal failed, "+err,"error"); }); }); @@ -578,24 +457,23 @@ document.getElementById("installdefault").addEventListener("click",event=>{ appCount = defaultApps.length; return showPrompt("Install Defaults","Remove everything and install default apps?"); }).then(() => { - showProgress("Removing all apps","animate", "sticky"); return Comms.removeAllApps(); }).then(()=>{ - hideProgress("sticky"); + Progress.hide({sticky:true}); appsInstalled = []; showToast(`Existing apps removed. Installing ${appCount} apps...`); return new Promise((resolve,reject) => { function upload() { var app = defaultApps.shift(); if (app===undefined) return resolve(); - showProgress(`${app.name} (${appCount-defaultApps.length}/${appCount})`,undefined,"sticky"); + Progress.show({title:`${app.name} (${appCount-defaultApps.length}/${appCount})`,sticky:true}); Comms.uploadApp(app,"skip_reset").then((appJSON) => { - hideProgress("sticky"); + Progress.hide({sticky:true}); if (appJSON) appsInstalled.push(appJSON); showToast(`(${appCount-defaultApps.length}/${appCount}) ${app.name} Uploaded`); upload(); }).catch(function() { - hideProgress("sticky"); + Progress.hide({sticky:true}); reject() }); } @@ -607,7 +485,7 @@ document.getElementById("installdefault").addEventListener("click",event=>{ showToast("Default apps successfully installed!","success"); return getInstalledApps(); }).catch(err=>{ - hideProgress("sticky"); + Progress.hide({sticky:true}); showToast("App Install failed, "+err,"error"); }); }); diff --git a/js/ui.js b/js/ui.js new file mode 100644 index 000000000..c88091872 --- /dev/null +++ b/js/ui.js @@ -0,0 +1,140 @@ +// General UI tools (progress bar, toast, prompt) + +/// Handle progress bars +var Progress = { + domElement : null, // the DOM element + sticky : false, // Progress.show({..., sticky:true}) don't remove until Progress.hide({sticky:true}) + interval : undefined, // the interval used if Progress.show({progress:"animate"}) + percent : undefined, // the current progress percentage + min : 0, // scaling for percentage + max : 1, // scaling for percentage + + /* Show a Progress message + Progress.show({ + sticky : bool // keep showing text even when Progress.hide is called (unless Progress.hide({sticky:true})) + percent : number | "animate" + min : // minimum scale for percentage (default 0) + max : // maximum scale for percentage (default 1) + }) */ + show : function(options) { + options = options||{}; + var text = options.title; + if (options.sticky) Progress.sticky = true; + if (options.min!==undefined) Progress.min = options.min; + if (options.max!==undefined) Progress.max = options.max; + var percent = options.percent; + if (percent!==undefined) + percent = Progress.min*100 + (Progress.max-Progress.min)*percent; + if (!Progress.domElement) { + if (Progress.interval) { + clearInterval(Progress.interval); + Progress.interval = undefined; + } + if (percent == "animate") { + Progress.interval = setInterval(function() { + Progress.percent += 2; + if (Progress.percent>100) Progress.percent=0; + Progress.show({percent:Progress.percent}); + }, 100); + percent = 0; + } + + var toastcontainer = document.getElementById("toastcontainer"); + Progress.domElement = htmlElement(`
+ ${text ? `
${text}
`:``} +
+
+
+
`); + toastcontainer.append(Progress.domElement); + } else { + var pt=document.getElementById("Progress.domElement"); + pt.setAttribute("aria-valuenow",percent); + pt.style.width = percent+"%"; + } + }, + // Progress.hide({sticky:true}) undoes Progress.show({title:"title", sticky:true}) + hide : function(options) { + options = options||{}; + if (Progress.sticky && !options.sticky) + return; + Progress.sticky = false; + Progress.min = 0; + Progress.max = 1; + if (Progress.interval) { + clearInterval(Progress.interval); + Progress.interval = undefined; + } + if (Progress.domElement) Progress.domElement.remove(); + Progress.domElement = undefined; + } +}; + +/// Add progress handler so we get nice uploads +Puck.writeProgress = function(charsSent, charsTotal) { + if (charsSent===undefined) { + Progress.hide(); + return; + } + var percent = Math.round(charsSent*100/charsTotal); + Progress.show({percent: percent}); +} + +/// Show a 'toast' message for status +function showToast(message, type) { + // toast-primary, toast-success, toast-warning or toast-error + var style = "toast-primary"; + if (type=="success") style = "toast-success"; + else if (type=="error") style = "toast-error"; + else if (type!==undefined) console.log("showToast: unknown toast "+type); + var toastcontainer = document.getElementById("toastcontainer"); + var msgDiv = htmlElement(`
`); + msgDiv.innerHTML = message; + toastcontainer.append(msgDiv); + setTimeout(function() { + msgDiv.remove(); + }, 5000); +} + +/// Show a yes/no prompt +function showPrompt(title, text, buttons) { + if (!buttons) buttons={yes:1,no:1}; + return new Promise((resolve,reject) => { + var modal = htmlElement(``); + document.body.append(modal); + modal.querySelector("a[href='#close']").addEventListener("click",event => { + event.preventDefault(); + reject("User cancelled"); + modal.remove(); + }); + htmlToArray(modal.getElementsByTagName("button")).forEach(button => { + button.addEventListener("click",event => { + event.preventDefault(); + var isYes = event.target.getAttribute("isyes")=="1"; + if (isYes) resolve(); + else reject("User cancelled"); + modal.remove(); + }); + }); + }); +} From f66aab5823c6b1974c38ebf729c223017740773b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 3 Apr 2020 15:15:06 +0100 Subject: [PATCH 16/17] Add page to export files from Stopwatch, save files in better format and with better filename --- apps.json | 3 +- apps/heart/interface.html | 12 +---- apps/swatch/ChangeLog | 1 + apps/swatch/interface.html | 90 ++++++++++++++++++++++++++++++++++++++ apps/swatch/stopwatch.js | 32 +++++++------- lib/interface.js | 18 +++++++- 6 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 apps/swatch/interface.html diff --git a/apps.json b/apps.json index d502b37c4..18d20b9da 100644 --- a/apps.json +++ b/apps.json @@ -392,7 +392,8 @@ { "id": "swatch", "name": "Stopwatch", "icon": "stopwatch.png", - "version":"0.03", + "version":"0.04", + "interface": "interface.html", "description": "Simple stopwatch with Lap Time logging to a JSON file", "tags": "health", "allow_emulator":true, diff --git a/apps/heart/interface.html b/apps/heart/interface.html index 177e2cdfb..4a21d2e27 100644 --- a/apps/heart/interface.html +++ b/apps/heart/interface.html @@ -11,17 +11,7 @@ var domRecords = document.getElementById("records"); function saveRecord(record,name) { var csv = `${record.map(rec=>[rec.time, rec.bpm, rec.confidence].join(",")).join("\n")}`; - var a = document.createElement("a"), - file = new Blob([csv], {type: "Comma-separated value file"}); - var url = URL.createObjectURL(file); - a.href = url; - a.download = name+".csv"; - document.body.appendChild(a); - a.click(); - setTimeout(function() { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); + Util.saveCSV(name, csv); } diff --git a/apps/swatch/ChangeLog b/apps/swatch/ChangeLog index 86a782585..2900c9aa1 100644 --- a/apps/swatch/ChangeLog +++ b/apps/swatch/ChangeLog @@ -3,3 +3,4 @@ Lap log now scrolls into 2nd column after 18th entry, able to display 36 entries before going off screen 0.03: Added ability to save Lap log as a date named JSON file into memory Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running +0.04: Changed save file filename, add interface.html to allow laps to be loaded diff --git a/apps/swatch/interface.html b/apps/swatch/interface.html new file mode 100644 index 000000000..928c5fe39 --- /dev/null +++ b/apps/swatch/interface.html @@ -0,0 +1,90 @@ + + + + + +
+ + + + + diff --git a/apps/swatch/stopwatch.js b/apps/swatch/stopwatch.js index d4136d8ed..2ebd8198b 100644 --- a/apps/swatch/stopwatch.js +++ b/apps/swatch/stopwatch.js @@ -4,7 +4,6 @@ var started = false; var timeY = 60; var hsXPos = 0; var lapTimes = []; -var saveTimes = []; var displayInterval; function timeToText(t) { @@ -25,7 +24,7 @@ function updateLabels() { for (var i in lapTimes) { if (i<18) {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);} - else + else {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-18)*8);} } drawsecs(); @@ -51,10 +50,8 @@ function drawms() { g.clearRect(hsXPos,timeY,220,timeY+20); g.drawString("."+("0"+hs).substr(-2),hsXPos,timeY+10); } -function saveconvert() { - for (var v in lapTimes){ - saveTimes[v]=v+1+"-"+timeToText(lapTimes[(lapTimes.length-1)-v]); - } +function getLapTimesArray() { + return lapTimes.map(timeToText).reverse(); } setWatch(function() { // Start/stop @@ -80,16 +77,21 @@ setWatch(function() { // Start/stop }, BTN2, {repeat:true}); setWatch(function() { // Lap Bangle.beep(); - if (started) tCurrent = Date.now(); - lapTimes.unshift(tCurrent-tStart); - tStart = tCurrent; - if (!started) - { - var timenow= Date(); - saveconvert(); - require("Storage").writeJSON("StpWch-"+timenow.toString(), saveTimes); + if (started) { + tCurrent = Date.now(); + lapTimes.unshift(tCurrent-tStart); + } + tStart = tCurrent; + if (!started) { // save + var timenow= Date(); + var filename = "swatch-"+(new Date()).toISOString().substr(0,16).replace("T","_")+".json"; + // this maxes out the 28 char maximum + require("Storage").writeJSON(filename, getLapTimesArray()); + E.showMessage("Laps Saved","Stopwatch"); + setTimeout(updateLabels, 1000); + } else { + updateLabels(); } - updateLabels(); }, BTN1, {repeat:true}); setWatch(function() { // Reset if (!started) { diff --git a/lib/interface.js b/lib/interface.js index 414c9d7fb..7e8be4fd9 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -39,7 +39,10 @@ var Util = { window.postMessage({type:"readstoragefile",data:filename,id:__id}); }, eraseStorageFile : function(filename,callback) { - Puck.write(`\x10require("Storage").open(${JSON.stringify(filename)}","r").erase()\n`,callback); + Puck.write(`\x10require("Storage").open(${JSON.stringify(filename)},"r").erase()\n`,callback); + }, + eraseStorage : function(filename,callback) { + Puck.write(`\x10require("Storage").erase(${JSON.stringify(filename)})\n`,callback); }, showModal : function(title) { if (!Util.domModal) { @@ -66,6 +69,19 @@ var Util = { hideModal : function() { if (!Util.domModal) return; Util.domModal.classList.remove("active"); + }, + saveCSV : function(filename, csvData) { + var a = document.createElement("a"), + file = new Blob([csvData], {type: "Comma-separated value file"}); + var url = URL.createObjectURL(file); + a.href = url; + a.download = filename+".csv"; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); } }; window.addEventListener("message", function(event) { From fbce9aaa7cd1bddaefa71ee8a9b15fb54912d7f1 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 3 Apr 2020 15:24:09 +0100 Subject: [PATCH 17/17] stopwatch - Added widgets --- apps.json | 2 +- apps/swatch/ChangeLog | 1 + apps/swatch/stopwatch.js | 14 +++++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps.json b/apps.json index 18d20b9da..dcb27d1d1 100644 --- a/apps.json +++ b/apps.json @@ -392,7 +392,7 @@ { "id": "swatch", "name": "Stopwatch", "icon": "stopwatch.png", - "version":"0.04", + "version":"0.05", "interface": "interface.html", "description": "Simple stopwatch with Lap Time logging to a JSON file", "tags": "health", diff --git a/apps/swatch/ChangeLog b/apps/swatch/ChangeLog index 2900c9aa1..3246eeced 100644 --- a/apps/swatch/ChangeLog +++ b/apps/swatch/ChangeLog @@ -4,3 +4,4 @@ 0.03: Added ability to save Lap log as a date named JSON file into memory Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running 0.04: Changed save file filename, add interface.html to allow laps to be loaded +0.05: Added widgets diff --git a/apps/swatch/stopwatch.js b/apps/swatch/stopwatch.js index 2ebd8198b..6f8ad9e34 100644 --- a/apps/swatch/stopwatch.js +++ b/apps/swatch/stopwatch.js @@ -13,24 +13,26 @@ function timeToText(t) { return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2); } function updateLabels() { - g.clear(); + g.reset(1); + g.clearRect(0,23,g.getWidth()-1,g.getHeight()-24); g.setFont("6x8",2); g.setFontAlign(0,0,3); g.drawString(started?"STOP":"GO",230,120); - if (!started) g.drawString("RESET",230,190); + if (!started) g.drawString("RESET",230,180); g.drawString(started?"LAP":"SAVE",230,50); g.setFont("6x8",1); g.setFontAlign(-1,-1); for (var i in lapTimes) { - if (i<18) + if (i<16) {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);} - else - {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-18)*8);} + else if (i<32) + {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-16)*8);} } drawsecs(); } function drawsecs() { var t = tCurrent-tStart; + g.reset(1); g.setFont("Vector",48); g.setFontAlign(0,0); var secs = Math.floor(t/1000)%60; @@ -103,3 +105,5 @@ setWatch(function() { // Reset }, BTN3, {repeat:true}); updateLabels(); +Bangle.loadWidgets(); +Bangle.drawWidgets();