Skip to content

Commit 6e608e0

Browse files
authored
Merge branch 'espruino:master' into master
2 parents e0b2622 + 6ff61b9 commit 6e608e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1548
-157
lines changed

apps/android/ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@
3131
0.30: Send firmware and hardware versions on connection
3232
Allow alarm enable/disable
3333
0.31: Implement API for activity fetching
34+
0.32: Added support for loyalty cards from gadgetbridge

apps/android/boot.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@
236236
event.t="remove";
237237
}
238238
require("messages").pushMessage(event);
239+
},
240+
"cards" : function() {
241+
// we receive all, just override what we have
242+
if (Array.isArray(event.d))
243+
require("Storage").writeJSON("android.cards.json", event.d);
239244
}
240245
};
241246
var h = HANDLERS[event.t];

apps/android/metadata.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "android",
33
"name": "Android Integration",
44
"shortName": "Android",
5-
"version": "0.31",
5+
"version": "0.32",
66
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
77
"icon": "app.png",
88
"tags": "tool,system,messages,notifications,gadgetbridge",
@@ -15,6 +15,6 @@
1515
{"name":"android.img","url":"app-icon.js","evaluate":true},
1616
{"name":"android.boot.js","url":"boot.js"}
1717
],
18-
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}],
18+
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
1919
"sortorder": -8
2020
}

apps/cards/Barcode.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* JS source adapted from https://github.com/lindell/JsBarcode
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to
10+
* deal in the Software without restriction, including without limitation the
11+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12+
* sell copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24+
* IN THE SOFTWARE.
25+
*/
26+
27+
class Barcode{
28+
constructor(data, options){
29+
this.data = data;
30+
this.text = options.text || data;
31+
this.options = options;
32+
}
33+
}
34+
35+
module.exports = Barcode;

apps/cards/ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.01: Simple app to display loyalty cards

apps/cards/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Cards
2+
3+
Simple app to display loyalty cards synced from Catima through GadgetBridge.
4+
The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported.
5+
6+
To come back to the visualization of the card's details from the code view, simply press the button.
7+
8+
Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes).
9+
10+
### Supported codes types
11+
12+
* `CODE_39`
13+
* `CODABAR`
14+
* `QR_CODE`
15+
16+
### Disclaimer
17+
18+
This app is a proof of concept, many codes are too complex to be rendered by the bangle's screen or hardware (at least with the current logic), keep that in mind.
19+
20+
### How to sync
21+
22+
_WIP: we currently cannot synchronize cards, a PR is under review in GadgetBridge repo, soon we will see support on nightly builds_
23+
24+
You can test it by sending on your bangle a file like this:
25+
26+
_android.cards.json_
27+
28+
```json
29+
[
30+
{
31+
"id": 1,
32+
"name": "First card",
33+
"value": "01234",
34+
"note": "Some stuff",
35+
"type": "CODE_39",
36+
"balance": "15 EUR",
37+
"expiration": "1691102081"
38+
},
39+
{
40+
"id": 2,
41+
"name": "Second card",
42+
"value": "Hello world",
43+
"note": "This is a qr generated on the bangle!",
44+
"type": "QR_CODE",
45+
"balance": "2 P"
46+
}
47+
]
48+
```
49+
50+
### Credits
51+
52+
Barcode generation adapted from [lindell/JsBarcode](https://github.com/lindell/JsBarcode)
53+
54+
QR code generation adapted from [ricmoo/QRCode](https://github.com/ricmoo/QRCode)

apps/cards/app-icon.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AYoIAjF/4v/F/4v/F/4v/FAdNAAsoADgv/F/4v/F/4vqu4AjF/4v/F/4v6poAjF/4AfFAYAGF/4v/F/4v/F/4v/F94A/AH4A/AH4A/ABo"))

apps/cards/app.js

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/* CARDS is a list of:
2+
{id:int,
3+
name,
4+
value,
5+
type,
6+
expiration,
7+
color,
8+
balance,
9+
note,
10+
...
11+
}
12+
*/
13+
14+
Bangle.loadWidgets();
15+
Bangle.drawWidgets();
16+
17+
//may make it configurable in the future
18+
const WHITE=-1
19+
const BLACK=0
20+
21+
var FILE = "android.cards.json";
22+
23+
var Locale = require("locale");
24+
25+
var fontSmall = "6x8";
26+
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
27+
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
28+
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
29+
30+
var CARDS = require("Storage").readJSON("android.cards.json",true)||[];
31+
var settings = require("Storage").readJSON("cards.settings.json",true)||{};
32+
33+
function getDate(timestamp) {
34+
return new Date(timestamp*1000);
35+
}
36+
function formatDay(date) {
37+
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
38+
if (!settings.useToday) {
39+
return formattedDate;
40+
}
41+
const today = new Date(Date.now());
42+
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
43+
return /*LANG*/"Today ";
44+
else {
45+
const tomorrow = new Date(Date.now() + 86400 * 1000);
46+
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
47+
return /*LANG*/"Tomorrow ";
48+
}
49+
return formattedDate;
50+
}
51+
}
52+
53+
function getColor(intColor) {
54+
return "#"+(0x1000000+Number(intColor)).toString(16).padStart(6,"0");
55+
}
56+
function isLight(color) {
57+
var r = +("0x"+color.slice(1,3));
58+
var g = +("0x"+color.slice(3,5));
59+
var b = +("0x"+color.slice(5,7));
60+
var threshold = 0x88 * 3;
61+
return (r+g+b) > threshold;
62+
}
63+
64+
function printSquareCode(binary, size) {
65+
var padding = 5;
66+
var ratio = (g.getWidth()-(2*padding))/size;
67+
for (var y = 0; y < size; y++) {
68+
for (var x = 0; x < size; x++) {
69+
if (binary[x + y * size]) {
70+
g.setColor(BLACK).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
71+
} else {
72+
g.setColor(WHITE).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
73+
}
74+
}
75+
}
76+
}
77+
function printLinearCode(binary) {
78+
var yFrom = 15;
79+
var yTo = 28;
80+
var width = g.getWidth()/binary.length;
81+
for(var b = 0; b < binary.length; b++){
82+
var x = b * width;
83+
if(binary[b] === "1"){
84+
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
85+
}
86+
else if(binary[b]){
87+
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
88+
}
89+
}
90+
}
91+
92+
function showCode(card) {
93+
E.showScroller();
94+
// keeping it on rising edge would come back twice..
95+
setWatch(()=>showCard(card), BTN, {edge:"falling"});
96+
// theme independent
97+
g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight());
98+
switch (card.type) {
99+
case "QR_CODE": {
100+
const getBinaryQR = require("cards.qrcode.js");
101+
let code = getBinaryQR(card.value);
102+
printSquareCode(code.data, code.size);
103+
break;
104+
}
105+
case "CODE_39": {
106+
g.setFont("Vector:20");
107+
g.setFontAlign(0,1).setColor(BLACK);
108+
g.drawString(card.value, g.getWidth()/2, g.getHeight());
109+
const CODE39 = require("cards.code39.js");
110+
let code = new CODE39(card.value, {});
111+
printLinearCode(code.encode().data);
112+
break;
113+
}
114+
case "CODABAR": {
115+
g.setFont("Vector:20");
116+
g.setFontAlign(0,1).setColor(BLACK);
117+
g.drawString(card.value, g.getWidth()/2, g.getHeight());
118+
const codabar = require("cards.codabar.js");
119+
let code = new codabar(card.value, {});
120+
printLinearCode(code.encode().data);
121+
break;
122+
}
123+
default:
124+
g.clear(true);
125+
g.setFont("Vector:30");
126+
g.setFontAlign(0,0);
127+
g.drawString(card.value, g.getWidth()/2, g.getHeight()/2);
128+
}
129+
}
130+
131+
function showCard(card) {
132+
var lines = [];
133+
var bodyFont = fontBig;
134+
if(!card) return;
135+
g.setFont(bodyFont);
136+
//var lines = [];
137+
if (card.name) lines = g.wrapString(card.name, g.getWidth()-10);
138+
var titleCnt = lines.length;
139+
var start = getDate(card.expiration);
140+
var includeDay = true;
141+
lines = lines.concat("", /*LANG*/"View code");
142+
var valueLine = lines.length - 1;
143+
if (card.expiration)
144+
lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10));
145+
if(card.balance)
146+
lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10));
147+
if(card.note && card.note.trim())
148+
lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10));
149+
lines = lines.concat("",/*LANG*/"< Back");
150+
var titleBgColor = card.color ? getColor(card.color) : g.theme.bg2;
151+
var titleColor = g.theme.fg2;
152+
if (card.color)
153+
titleColor = isLight(titleBgColor) ? BLACK : WHITE;
154+
E.showScroller({
155+
h : g.getFontHeight(), // height of each menu item in pixels
156+
c : lines.length, // number of menu items
157+
// a function to draw a menu item
158+
draw : function(idx, r) {
159+
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
160+
g.setBgColor(idx<titleCnt ? titleBgColor : g.theme.bg).
161+
setColor(idx<titleCnt ? titleColor : g.theme.fg).
162+
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
163+
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
164+
}, select : function(idx) {
165+
if (idx>=lines.length-2)
166+
showList();
167+
else if (idx==valueLine)
168+
showCode(card);
169+
},
170+
back : () => showList()
171+
});
172+
}
173+
174+
// https://github.com/metafloor/bwip-js
175+
// https://github.com/lindell/JsBarcode
176+
177+
function showList() {
178+
if(CARDS.length == 0) {
179+
E.showMessage(/*LANG*/"No cards");
180+
return;
181+
}
182+
E.showScroller({
183+
h : 52,
184+
c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
185+
draw : function(idx, r) {"ram"
186+
var card = CARDS[idx];
187+
g.setColor(g.theme.fg);
188+
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
189+
if (!card) return;
190+
var isPast = false;
191+
var x = r.x+2, name = card.name;
192+
var body = card.expiration ? formatDay(getDate(card.expiration)) : "";
193+
if (card.balance) body += "\n" + card.balance;
194+
if (name) g.setFontAlign(-1,-1).setFont(fontBig)
195+
.setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2);
196+
if (body) {
197+
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
198+
g.drawString(body, x+10,r.y+20);
199+
}
200+
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
201+
if(card.color) {
202+
g.setColor(getColor(card.color));
203+
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
204+
}
205+
},
206+
select : idx => showCard(CARDS[idx]),
207+
back : () => load()
208+
});
209+
}
210+
showList();

apps/cards/app.png

218 Bytes
Loading

0 commit comments

Comments
 (0)