From d5c445f47254ec8865b785854bcf8c170985f2f1 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 15 Aug 2023 14:44:21 +0200 Subject: [PATCH 01/39] Added `cards` app This app will be able to render cards as synchronized by Catima --- apps/cards/Barcode.js | 9 + apps/cards/README.md | 44 ++ apps/cards/app-icon.js | 1 + apps/cards/app.js | 183 ++++++ apps/cards/app.png | Bin 0 -> 218 bytes apps/cards/codabar.js | 63 +++ apps/cards/code39.js | 105 ++++ apps/cards/metadata.json | 21 + apps/cards/qrcode.js | 675 +++++++++++++++++++++++ apps/cards/screenshot_cards_barcode.png | Bin 0 -> 2222 bytes apps/cards/screenshot_cards_card1.png | Bin 0 -> 2643 bytes apps/cards/screenshot_cards_overview.png | Bin 0 -> 2287 bytes apps/cards/screenshot_cards_qrcode.png | Bin 0 -> 2935 bytes apps/cards/settings.js | 24 + 14 files changed, 1125 insertions(+) create mode 100644 apps/cards/Barcode.js create mode 100644 apps/cards/README.md create mode 100644 apps/cards/app-icon.js create mode 100644 apps/cards/app.js create mode 100644 apps/cards/app.png create mode 100644 apps/cards/codabar.js create mode 100644 apps/cards/code39.js create mode 100644 apps/cards/metadata.json create mode 100644 apps/cards/qrcode.js create mode 100644 apps/cards/screenshot_cards_barcode.png create mode 100644 apps/cards/screenshot_cards_card1.png create mode 100644 apps/cards/screenshot_cards_overview.png create mode 100644 apps/cards/screenshot_cards_qrcode.png create mode 100644 apps/cards/settings.js diff --git a/apps/cards/Barcode.js b/apps/cards/Barcode.js new file mode 100644 index 000000000..ad27da7e6 --- /dev/null +++ b/apps/cards/Barcode.js @@ -0,0 +1,9 @@ +class Barcode{ + constructor(data, options){ + this.data = data; + this.text = options.text || data; + this.options = options; + } +} + +module.exports = Barcode; diff --git a/apps/cards/README.md b/apps/cards/README.md new file mode 100644 index 000000000..724ef9534 --- /dev/null +++ b/apps/cards/README.md @@ -0,0 +1,44 @@ +# Cards + +Basic viewer for loyalty cards synced from Catima through GadgetBridge. +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. + +Double tapping on the code will come back to the visualization of the card's details. + +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). + +### Supported codes types + +* `CODE_39` +* `CODABAR` +* `QR_CODE` + +### How to sync + +_WIP: we currently cannot synchronize cards_ + +You can test it by sending on your bangle a file like this: + +_android.cards.json_ + +```json +[ + { + "id": 1, + "name": "First card", + "value": "01234", + "note": "Some stuff", + "type": "CODE_39", + "balance": "15 EUR", + "expiration": "1691102081" + }, + { + "id": 2, + "name": "Second card", + "value": "Hello world", + "note": "This is a qr generated on the bangle!", + "type": "QR_CODE", + "balance": "2 P" + } +] +``` diff --git a/apps/cards/app-icon.js b/apps/cards/app-icon.js new file mode 100644 index 000000000..3ec6948c4 --- /dev/null +++ b/apps/cards/app-icon.js @@ -0,0 +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")) diff --git a/apps/cards/app.js b/apps/cards/app.js new file mode 100644 index 000000000..52eaa392c --- /dev/null +++ b/apps/cards/app.js @@ -0,0 +1,183 @@ +/* CARDS is a list of: + {id:int, + name, + value, + type, + expiration, + color, + balance, + note, + ... + } +*/ + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var FILE = "android.cards.json"; + +var Locale = require("locale"); + +var fontSmall = "6x8"; +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; +var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + +var CARDS = require("Storage").readJSON("android.cards.json",true)||[]; +var settings = require("Storage").readJSON("cards.settings.json",true)||{}; + +function getDate(timestamp) { + return new Date(timestamp*1000); +} +function formatDay(date) { + let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,""); + if (!settings.useToday) { + return formattedDate; + } + const today = new Date(Date.now()); + if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth()) + return /*LANG*/"Today "; + else { + const tomorrow = new Date(Date.now() + 86400 * 1000); + if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) { + return /*LANG*/"Tomorrow "; + } + return formattedDate; + } +} + +function printSquareCode(binary, size) { + var ratio = g.getWidth()/size; + for (var y = 0; y < size; y++) { + for (var x = 0; x < size; x++) { + if (binary[x + y * size]) { + g.setColor(g.theme.bg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + } else { + g.setColor(g.theme.fg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + } + } + } +} +function printLinearCode(binary) { + var yFrom = 0; + var width = g.getWidth()/binary.length; + for(var b = 0; b < binary.length; b++){ + var x = b * width; + if(binary[b] === "1"){ + g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + } + else if(binary[b]){ + g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + } + } +} + +function showCode(card) { + var code; + //FIXME doesn't work.. + var listener = (data) => { + if(data.double) showCard(card); + Bangle.removeListener("tap", listener); + }; + Bangle.on("tap", listener); + switch (card.type) { + case "QR_CODE": + const getBinaryQR = require("cards.qrcode.js"); + code = getBinaryQR(card.value); + printSquareCode(code.data, code.size); + break; + case "CODE_39": + const CODE39 = require("cards.code39.js"); + code = new CODE39(card.value, {}); + printLinearCode(code.encode().data); + break; + case "CODABAR": + const codabar = require("cards.codabar.js"); + code = new codabar(card.value, {}); + printLinearCode(code.encode().data); + break; + default: + g.clear(true); + g.setFont("Vector:15"); + g.setFontAlign(0,0); + g.drawString(card.value, g.getWidth()/2, g.getHeight()/2); + } +} + +function showCard(card) { + var lines = []; + var bodyFont = fontBig; + if(!card) return; + g.setFont(bodyFont); + //var lines = []; + if (card.name) lines = g.wrapString(card.name, g.getWidth()-10); + var titleCnt = lines.length; + var start = getDate(card.expiration); + var includeDay = true; + if (titleCnt) lines.push(""); // add blank line after name + lines = lines.concat("", /*LANG*/"Tap here to see the value"); + var valueLine = lines.length - 1; + if (card.expiration) + lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10)); + if(card.balance) + lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10)); + if(card.note && card.note.trim()) + lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10)); + lines = lines.concat("",/*LANG*/"< Back"); + E.showScroller({ + h : g.getFontHeight(), // height of each menu item in pixels + c : lines.length, // number of menu items + // a function to draw a menu item + draw : function(idx, r) { + // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 + g.setBgColor(idx=lines.length-2) + showList(); + if (idx>=valueLine) + showCode(card); + }, + back : () => showList() + }); +} + +// https://github.com/metafloor/bwip-js +// https://github.com/lindell/JsBarcode + +function showList() { + if(CARDS.length == 0) { + E.showMessage(/*LANG*/"No cards"); + return; + } + E.showScroller({ + h : 52, + c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) + draw : function(idx, r) {"ram" + var card = CARDS[idx]; + g.setColor(g.theme.fg); + g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + if (!card) return; + var isPast = false; + var x = r.x+2, name = card.name; + var body = card.expiration ? formatDay(getDate(card.expiration)) : ""; + if (card.balance) body += "\n" + card.balance; + if (name) g.setFontAlign(-1,-1).setFont(fontBig) + .setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2); + if (body) { + g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg); + g.drawString(body, x+10,r.y+20); + } + 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 + if(card.color) { + g.setColor("#"+(0x1000000+Number(card.color)).toString(16).padStart(6,"0")); + g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4); + } + }, + select : idx => showCard(CARDS[idx]), + back : () => load() + }); +} +showList(); diff --git a/apps/cards/app.png b/apps/cards/app.png new file mode 100644 index 0000000000000000000000000000000000000000..b2bfa59f442e61c0b74d96f9779f1345f4bd6856 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWNDA?oa z;uumf=j}8{-c|=5m+ZyI^_Q?+2#Ss8R9nR;*v!N+VS?U=IHT`#&uxh`W?DdpSP*vaD3`byh- zj`jSVdaP`F%o(knzb6Yb-?RU?#Oa>>*Obm$8Oii3@>75=xxpHC@T5Rn#A2ZpKpumq LtDnm{r-UW|sxn7| literal 0 HcmV?d00001 diff --git a/apps/cards/codabar.js b/apps/cards/codabar.js new file mode 100644 index 000000000..072a8508a --- /dev/null +++ b/apps/cards/codabar.js @@ -0,0 +1,63 @@ +// Encoding specification: +// http://www.barcodeisland.com/codabar.phtml + +const Barcode = require("cards.Barcode.js"); + +class codabar extends Barcode{ + constructor(data, options){ + if (/^[0-9\-\$\:\.\+\/]+$/.test(data)) { + data = "A" + data + "A"; + } + + super(data.toUpperCase(), options); + + this.text = this.options.text || this.text.replace(/[A-D]/g, ''); + } + + valid(){ + return /^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/.test(this.data) + } + + encode(){ + var result = []; + var encodings = this.getEncodings(); + for(var i = 0; i < this.data.length; i++){ + result.push(encodings[this.data.charAt(i)]); + // for all characters except the last, append a narrow-space ("0") + if (i !== this.data.length - 1) { + result.push("0"); + } + } + return { + text: this.text, + data: result.join('') + }; + } + + getEncodings(){ + return { + "0": "101010011", + "1": "101011001", + "2": "101001011", + "3": "110010101", + "4": "101101001", + "5": "110101001", + "6": "100101011", + "7": "100101101", + "8": "100110101", + "9": "110100101", + "-": "101001101", + "$": "101100101", + ":": "1101011011", + "/": "1101101011", + ".": "1101101101", + "+": "1011011011", + "A": "1011001001", + "B": "1001001011", + "C": "1010010011", + "D": "1010011001" + }; + } +} + +module.exports = codabar diff --git a/apps/cards/code39.js b/apps/cards/code39.js new file mode 100644 index 000000000..5eced539b --- /dev/null +++ b/apps/cards/code39.js @@ -0,0 +1,105 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/Code_39#Encoding + +const Barcode = require("cards.Barcode.js"); + +class CODE39 extends Barcode { + constructor(data, options){ + data = data.toUpperCase(); + + // Calculate mod43 checksum if enabled + if(options.mod43){ + data += getCharacter(mod43checksum(data)); + } + + super(data, options); + } + + encode(){ + // First character is always a * + var result = getEncoding("*"); + + // Take every character and add the binary representation to the result + for(let i = 0; i < this.data.length; i++){ + result += getEncoding(this.data[i]) + "0"; + } + + // Last character is always a * + result += getEncoding("*"); + + return { + data: result, + text: this.text + }; + } + + valid(){ + return /^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(this.data); + } +} + + + + + + +// All characters. The position in the array is the (checksum) value +var characters = [ + "0", "1", "2", "3", + "4", "5", "6", "7", + "8", "9", "A", "B", + "C", "D", "E", "F", + "G", "H", "I", "J", + "K", "L", "M", "N", + "O", "P", "Q", "R", + "S", "T", "U", "V", + "W", "X", "Y", "Z", + "-", ".", " ", "$", + "/", "+", "%", "*" +]; + +// The decimal representation of the characters, is converted to the +// corresponding binary with the getEncoding function +var encodings = [ + 20957, 29783, 23639, 30485, + 20951, 29813, 23669, 20855, + 29789, 23645, 29975, 23831, + 30533, 22295, 30149, 24005, + 21623, 29981, 23837, 22301, + 30023, 23879, 30545, 22343, + 30161, 24017, 21959, 30065, + 23921, 22385, 29015, 18263, + 29141, 17879, 29045, 18293, + 17783, 29021, 18269, 17477, + 17489, 17681, 20753, 35770 +]; + +// Get the binary representation of a character by converting the encodings +// from decimal to binary +function getEncoding(character){ + return getBinary(characterValue(character)); +} + +function getBinary(characterValue){ + return encodings[characterValue].toString(2); +} + +function getCharacter(characterValue){ + return characters[characterValue]; +} + +function characterValue(character){ + return characters.indexOf(character); +} + +function mod43checksum(data){ + var checksum = 0; + for(let i = 0; i < data.length; i++){ + checksum += characterValue(data[i]); + } + + checksum = checksum % 43; + return checksum; +} + +module.exports = CODE39; diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json new file mode 100644 index 000000000..538a8b56e --- /dev/null +++ b/apps/cards/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "cards", + "name": "Cards", + "version": "0.1", + "description": "Display loyalty cards", + "icon": "app.png", + "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_event1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], + "tags": "cards", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"cards.app.js","url":"app.js"}, + {"name":"cards.settings.js","url":"settings.js"}, + {"name":"cards.Barcode.js","url":"Barcode.js"}, + {"name":"cards.qrcode.js","url":"qrcode.js"}, + {"name":"cards.codabar.js","url":"codabar.js"}, + {"name":"cards.code39.js","url":"code39.js"}, + {"name":"cards.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"cards.settings.json"}] +} diff --git a/apps/cards/qrcode.js b/apps/cards/qrcode.js new file mode 100644 index 000000000..d27c55d7b --- /dev/null +++ b/apps/cards/qrcode.js @@ -0,0 +1,675 @@ +var c = E.compiledC(` +// int get_qr(int, int) + +typedef signed char __int8_t; +typedef unsigned char __uint8_t; +typedef signed short int __int16_t; +typedef unsigned short int __uint16_t; +typedef signed int __int32_t; +typedef unsigned int __uint32_t; + +typedef __int8_t int8_t; +typedef __int16_t int16_t; +typedef __int32_t int32_t; +typedef __uint8_t uint8_t; +typedef __uint16_t uint16_t; +typedef __uint32_t uint32_t; + +typedef struct QRCode { + uint8_t version; + uint8_t size; + uint8_t ecc; + uint8_t mode; + uint8_t mask; + uint8_t *modules; +} QRCode; +uint16_t qrcode_getBufferSize(uint8_t version); + +int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); +int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length); + +bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); + +static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { + { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, + { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, + { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, + { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, +}; + +static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { + { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, + { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, + { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, + { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, +}; + +static const uint16_t NUM_RAW_DATA_MODULES[40] = { + 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, + 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, + 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 +}; +static int max(int a, int b) { + if (a > b) { return a; } + return b; +} + +static int abs(int value) { + if (value < 0) { return -value; } + return value; +} + +static void *memset(void *s, int c, int n) { + char *arr = (char *)s; + for (int i = 0; i= '0' && c <= '9') { return (c - '0'); } + if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); } + switch (c) { + case ' ': return 36; + case '$': return 37; + case '%': return 38; + case '*': return 39; + case '+': return 40; + case '-': return 41; + case '.': return 42; + case '/': return 43; + case ':': return 44; + } + + return -1; +} + +static bool isAlphanumeric(const char *text, uint16_t length) { + while (length != 0) { + if (getAlphanumeric(text[--length]) == -1) { return false; } + } + return true; +} +static bool isNumeric(const char *text, uint16_t length) { + while (length != 0) { + char c = text[--length]; + if (c < '0' || c > '9') { return false; } + } + return true; +} +static char getModeBits(uint8_t version, uint8_t mode) { + unsigned int modeInfo = 0x7bbb80a; + if (version > 9) { modeInfo >>= 9; } + + if (version > 26) { modeInfo >>= 9; } + char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); + if (result == 15) { result = 16; } + + return result; +} + +typedef struct BitBucket { + uint32_t bitOffsetOrWidth; + uint16_t capacityBytes; + uint8_t *data; +} BitBucket; +static uint16_t bb_getGridSizeBytes(uint8_t size) { + return (((size * size) + 7) / 8); +} + +static uint16_t bb_getBufferSizeBytes(uint32_t bits) { + return ((bits + 7) / 8); +} + +static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) { + bitBuffer->bitOffsetOrWidth = 0; + bitBuffer->capacityBytes = capacityBytes; + bitBuffer->data = data; + + memset(data, 0, bitBuffer->capacityBytes); +} + +static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) { + bitGrid->bitOffsetOrWidth = size; + bitGrid->capacityBytes = bb_getGridSizeBytes(size); + bitGrid->data = data; + + memset(data, 0, bitGrid->capacityBytes); +} + +static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) { + uint32_t offset = bitBuffer->bitOffsetOrWidth; + for (int8_t i = length - 1; i >= 0; i--, offset++) { + bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); + } + bitBuffer->bitOffsetOrWidth = offset; +} + +static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) { + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + if (on) { + bitGrid->data[offset >> 3] |= mask; + } else { + bitGrid->data[offset >> 3] &= ~mask; + } +} + +static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) { + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); + if (on ^ invert) { + bitGrid->data[offset >> 3] |= mask; + } else { + bitGrid->data[offset >> 3] &= ~mask; + } +} + +static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) { + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; +} + +static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) { + uint8_t size = modules->bitOffsetOrWidth; + for (uint8_t y = 0; y < size; y++) { + for (uint8_t x = 0; x < size; x++) { + if (bb_getBit(isFunction, x, y)) { continue; } + + bool invert = 0; + switch (mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + } + bb_invertBit(modules, x, y, invert); + } + } +} + +static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) { + bb_setBit(modules, x, y, on); + bb_setBit(isFunction, x, y, true); +} +static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { + uint8_t size = modules->bitOffsetOrWidth; + + for (int8_t i = -4; i <= 4; i++) { + for (int8_t j = -4; j <= 4; j++) { + uint8_t dist = max(abs(i), abs(j)); + int16_t xx = x + j, yy = y + i; + if (0 <= xx && xx < size && 0 <= yy && yy < size) { + setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); + } + } + } +} +static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { + for (int8_t i = -2; i <= 2; i++) { + for (int8_t j = -2; j <= 2; j++) { + setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1); + } + } +} + +static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) { + + uint8_t size = modules->bitOffsetOrWidth; + uint32_t data = ecc << 3 | mask; + uint32_t rem = data; + for (int i = 0; i < 10; i++) { + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + } + + data = data << 10 | rem; + data ^= 0x5412; + for (uint8_t i = 0; i <= 5; i++) { + setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); + } + + setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); + setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); + setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); + + for (int8_t i = 9; i < 15; i++) { + setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); + } + for (int8_t i = 0; i <= 7; i++) { + setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); + } + + for (int8_t i = 8; i < 15; i++) { + setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); + } + + setFunctionModule(modules, isFunction, 8, size - 8, true); +} +static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) { + + int8_t size = modules->bitOffsetOrWidth; + if (version < 7) { return; } + uint32_t rem = version; + for (uint8_t i = 0; i < 12; i++) { + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + } + + uint32_t data = version << 12 | rem; + for (uint8_t i = 0; i < 18; i++) { + bool bit = ((data >> i) & 1) != 0; + uint8_t a = size - 11 + i % 3, b = i / 3; + setFunctionModule(modules, isFunction, a, b, bit); + setFunctionModule(modules, isFunction, b, a, bit); + } +} + +static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) { + + uint8_t size = modules->bitOffsetOrWidth; + for (uint8_t i = 0; i < size; i++) { + setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); + setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); + } + drawFinderPattern(modules, isFunction, 3, 3); + drawFinderPattern(modules, isFunction, size - 4, 3); + drawFinderPattern(modules, isFunction, 3, size - 4); + + if (version > 1) { + + uint8_t alignCount = version / 7 + 2; + uint8_t step; + if (version != 32) { + step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; + } else { + step = 26; + } + + uint8_t alignPositionIndex = alignCount - 1; + uint8_t alignPosition[alignCount]; + + alignPosition[0] = 6; + + uint8_t l_size = version * 4 + 17; + for (uint8_t i = 0, pos = l_size - 7; i < alignCount - 1; i++, pos -= step) { + alignPosition[alignPositionIndex--] = pos; + } + + for (uint8_t i = 0; i < alignCount; i++) { + for (uint8_t j = 0; j < alignCount; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) { + continue; + } else { + drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); + } + } + } + } + drawFormatBits(modules, isFunction, ecc, 0); + drawVersion(modules, isFunction, version); +} +static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) { + + uint32_t bitLength = codewords->bitOffsetOrWidth; + uint8_t *data = codewords->data; + + uint8_t size = modules->bitOffsetOrWidth; + uint32_t i = 0; + for (int16_t right = size - 1; right >= 1; right -= 2) { + if (right == 6) { right = 5; } + + for (uint8_t vert = 0; vert < size; vert++) { + for (int j = 0; j < 2; j++) { + uint8_t x = right - j; + bool upwards = ((right & 2) == 0) ^ (x < 6); + uint8_t y = upwards ? size - 1 - vert : vert; + if (!bb_getBit(isFunction, x, y) && i < bitLength) { + bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); + i++; + } + } + } + } +} +static uint32_t getPenaltyScore(BitBucket *modules) { + uint32_t result = 0; + + uint8_t size = modules->bitOffsetOrWidth; + for (uint8_t y = 0; y < size; y++) { + + bool colorX = bb_getBit(modules, 0, y); + for (uint8_t x = 1, runX = 1; x < size; x++) { + bool cx = bb_getBit(modules, x, y); + if (cx != colorX) { + colorX = cx; + runX = 1; + + } else { + runX++; + if (runX == 5) { + result += 3; + } else if (runX > 5) { + result++; + } + } + } + } + for (uint8_t x = 0; x < size; x++) { + bool colorY = bb_getBit(modules, x, 0); + for (uint8_t y = 1, runY = 1; y < size; y++) { + bool cy = bb_getBit(modules, x, y); + if (cy != colorY) { + colorY = cy; + runY = 1; + } else { + runY++; + if (runY == 5) { + result += 3; + } else if (runY > 5) { + result++; + } + } + } + } + + uint16_t black = 0; + for (uint8_t y = 0; y < size; y++) { + uint16_t bitsRow = 0, bitsCol = 0; + for (uint8_t x = 0; x < size; x++) { + bool color = bb_getBit(modules, x, y); + if (x > 0 && y > 0) { + bool colorUL = bb_getBit(modules, x - 1, y - 1); + bool colorUR = bb_getBit(modules, x, y - 1); + bool colorL = bb_getBit(modules, x - 1, y); + if (color == colorUL && color == colorUR && color == colorL) { + result += 3; + } + } + bitsRow = ((bitsRow << 1) & 0x7FF) | color; + bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x); + if (x >= 10) { + if (bitsRow == 0x05D || bitsRow == 0x5D0) { + result += 40; + } + if (bitsCol == 0x05D || bitsCol == 0x5D0) { + result += 40; + } + } + if (color) { black++; } + } + } + uint16_t total = size * size; + for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { + result += 10; + } + + return result; +} + +static uint8_t rs_multiply(uint8_t x, uint8_t y) { + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; +} + +static void rs_init(uint8_t degree, uint8_t *coeff) { + memset(coeff, 0, degree); + coeff[degree - 1] = 1; + uint16_t root = 1; + for (uint8_t i = 0; i < degree; i++) { + + for (uint8_t j = 0; j < degree; j++) { + coeff[j] = rs_multiply(coeff[j], root); + if (j + 1 < degree) { + coeff[j] ^= coeff[j + 1]; + } + } + root = (root << 1) ^ ((root >> 7) * 0x11D); + } +} + +static void rs_getRemainder(uint8_t degree, uint8_t *coeff, const uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) { + for (uint8_t i = 0; i < length; i++) { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j < degree; j++) { + result[(j - 1) * stride] = result[j * stride]; + } + result[(degree - 1) * stride] = 0; + + for (uint8_t j = 0; j < degree; j++) { + result[j * stride] ^= rs_multiply(coeff[j], factor); + } + } +} +static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) { + int8_t mode = 2; + + if (isNumeric((char*)text, length)) { + mode = 0; + bb_appendBits(dataCodewords, 1 << 0, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, 0)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (uint16_t i = 0; i < length; i++) { + accumData = accumData * 10 + ((char)(text[i]) - '0'); + accumCount++; + if (accumCount == 3) { + bb_appendBits(dataCodewords, accumData, 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) { + bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); + } + + } else if (isAlphanumeric((char*)text, length)) { + mode = 1; + bb_appendBits(dataCodewords, 1 << 1, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, 1)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (uint16_t i = 0; i < length; i++) { + accumData = accumData * 45 + getAlphanumeric((char)(text[i])); + accumCount++; + if (accumCount == 2) { + bb_appendBits(dataCodewords, accumData, 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) { + bb_appendBits(dataCodewords, accumData, 6); + } + + } else { + bb_appendBits(dataCodewords, 1 << 2, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, 2)); + for (uint16_t i = 0; i < length; i++) { + bb_appendBits(dataCodewords, (char)(text[i]), 8); + } + } + + return mode; +} + +static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) { + uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; + uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + uint8_t blockEccLen = totalEcc / numBlocks; + uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; + uint8_t shortBlockLen = moduleCount / 8 / numBlocks; + + uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; + + uint8_t result[data->capacityBytes]; + memset(result, 0, sizeof(result)); + + uint8_t coeff[blockEccLen]; + rs_init(blockEccLen, coeff); + + uint16_t offset = 0; + uint8_t *dataBytes = data->data; + + for (uint8_t i = 0; i < shortDataBlockLen; i++) { + uint16_t index = i; + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { + result[offset++] = dataBytes[index]; + if (blockNum == numShortBlocks) { stride++; } + + index += stride; + } + } + { + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { + result[offset++] = dataBytes[index]; + + if (blockNum == 0) { stride++; } + index += stride; + } + } + + uint8_t blockSize = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { + if (blockNum == numShortBlocks) { blockSize++; } + + rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); + dataBytes += blockSize; + } + + memcpy(data->data, result, data->capacityBytes); + data->bitOffsetOrWidth = moduleCount; +} + +static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); + +uint16_t qrcode_getBufferSize(uint8_t version) { + return bb_getGridSizeBytes(4 * version + 17); +} +int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) { + uint8_t size = version * 4 + 17; + qrcode->version = version; + qrcode->size = size; + qrcode->ecc = ecc; + qrcode->modules = modules; + + uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; + struct BitBucket codewords; + uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; + bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); + int8_t mode = encodeDataCodewords(&codewords, data, length, version); + + if (mode < 0) { return -1; } + qrcode->mode = mode; + uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth; + if (padding > 4) { padding = 4; } + bb_appendBits(&codewords, 0, padding); + bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); + for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) { + bb_appendBits(&codewords, padByte, 8); + } + + BitBucket modulesGrid; + bb_initGrid(&modulesGrid, modules, size); + + BitBucket isFunctionGrid; + uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)]; + bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); + drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); + performErrorCorrection(version, eccFormatBits, &codewords); + drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); + uint8_t mask = 0; + int32_t minPenalty = (2147483647); + for (uint8_t i = 0; i < 8; i++) { + drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); + applyMask(&modulesGrid, &isFunctionGrid, i); + int penalty = getPenaltyScore(&modulesGrid); + if (penalty < minPenalty) { + mask = i; + minPenalty = penalty; + } + applyMask(&modulesGrid, &isFunctionGrid, i); + } + + qrcode->mask = mask; + drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); + applyMask(&modulesGrid, &isFunctionGrid, mask); + + return 0; +} + +int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) { + return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); +} + +bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { + if (x >= qrcode->size || y >= qrcode->size) { + return false; + } + + uint32_t offset = y * qrcode->size + x; + return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; +} + +int get_qr (char *string, uint8_t *qrcodeBitmap) { + // The structure to manage the QR code + QRCode qrcode; + + // Allocate a chunk of memory to store the QR code + uint8_t qrcodeBytes[qrcode_getBufferSize(3)]; + + qrcode_initText(&qrcode, qrcodeBytes, 3, 0, string); + for (uint8_t y = 0; y < qrcode.size; y++) { + for (uint8_t x = 0; x < qrcode.size; x++) { + qrcodeBitmap[x + y * qrcode.size] = qrcode_getModule(&qrcode, x, y); + } + } + return qrcode.size; +} +`); + +function getBinaryQR (value) { + var qrcodeBitmap = new Uint8Array(850); + var flatValue = Uint8Array(E.toArrayBuffer(E.toFlatString(value ,0))); + var valueAddr = E.getAddressOf(flatValue, true); + var qrAddr = E.getAddressOf(qrcodeBitmap, true); + if (valueAddr == 0 || qrAddr == 0) { + console.log ("Failed to get flat arrays.."); + //return; + } + var qrsize = c.get_qr(valueAddr, qrAddr); + return { data: qrcodeBitmap, size: qrsize }; +} + +module.exports = getBinaryQR; diff --git a/apps/cards/screenshot_cards_barcode.png b/apps/cards/screenshot_cards_barcode.png new file mode 100644 index 0000000000000000000000000000000000000000..1910c173e1e3f1f69c774f2aeeac9904346078f8 GIT binary patch literal 2222 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 literal 0 HcmV?d00001 diff --git a/apps/cards/screenshot_cards_qrcode.png b/apps/cards/screenshot_cards_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..5bace3e6efc390a744bca7c6ff6d35ccc96ef565 GIT binary patch literal 2935 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 ai0#X-nV2|BX3xu&G>G8h?Ofv+e))gT)1V9h literal 0 HcmV?d00001 diff --git a/apps/cards/settings.js b/apps/cards/settings.js new file mode 100644 index 000000000..b11ba785a --- /dev/null +++ b/apps/cards/settings.js @@ -0,0 +1,24 @@ +(function(back) { + function gbSend(message) { + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(message)); + } + var settings = require("Storage").readJSON("cards.settings.json",1)||{}; + function updateSettings() { + require("Storage").writeJSON("cards.settings.json", settings); + } + var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; + var mainmenu = { + "" : { "title" : "Cards" }, + "< Back" : back, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" }, + /*LANG*/"Use 'Today',..." : { + value : !!settings.useToday, + onchange: v => { + settings.useToday = v; + updateSettings(); + } + }, + }; + E.showMenu(mainmenu); +}) From 56f9788034db6c61224bab32610cfd32f1317112 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 15 Aug 2023 15:31:18 +0200 Subject: [PATCH 02/39] cards: added changelog and used libraries --- apps/cards/ChangeLog | 1 + apps/cards/README.md | 8 +++++++- apps/cards/metadata.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 apps/cards/ChangeLog diff --git a/apps/cards/ChangeLog b/apps/cards/ChangeLog new file mode 100644 index 000000000..00945cd13 --- /dev/null +++ b/apps/cards/ChangeLog @@ -0,0 +1 @@ +0.01: Simple app to display loyalty cards diff --git a/apps/cards/README.md b/apps/cards/README.md index 724ef9534..4bd70a0b1 100644 --- a/apps/cards/README.md +++ b/apps/cards/README.md @@ -1,6 +1,6 @@ # Cards -Basic viewer for loyalty cards synced from Catima through GadgetBridge. +Simple app to display loyalty cards synced from Catima through GadgetBridge. 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. Double tapping on the code will come back to the visualization of the card's details. @@ -42,3 +42,9 @@ _android.cards.json_ } ] ``` + +### Credits + +Barcode generation adapted from [lindell/JsBarcode](https://github.com/lindell/JsBarcode) + +QR code generation adapted from [ricmoo/QRCode](https://github.com/ricmoo/QRCode) diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 538a8b56e..5f72a1c5b 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -4,7 +4,7 @@ "version": "0.1", "description": "Display loyalty cards", "icon": "app.png", - "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_event1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], + "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], "tags": "cards", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", From 2bc2d9ff3585c44a12b60548c6dae32e1c9bc602 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Wed, 16 Aug 2023 08:58:32 +0200 Subject: [PATCH 03/39] Added padding to codes and updated screenshots --- apps/cards/app.js | 29 ++++++++++++++++-------- apps/cards/metadata.json | 2 +- apps/cards/screenshot_cards_barcode.png | Bin 2222 -> 2283 bytes apps/cards/screenshot_cards_card1.png | Bin 2643 -> 2693 bytes apps/cards/screenshot_cards_card2.png | Bin 0 -> 2506 bytes 5 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 apps/cards/screenshot_cards_card2.png diff --git a/apps/cards/app.js b/apps/cards/app.js index 52eaa392c..a6d802352 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -47,27 +47,30 @@ function formatDay(date) { } function printSquareCode(binary, size) { - var ratio = g.getWidth()/size; + var padding = 5; + var ratio = (g.getWidth()-(2*padding))/size; + g.setColor(g.theme.fg).fillRect(0, 0, g.getWidth(), g.getHeight()); for (var y = 0; y < size; y++) { for (var x = 0; x < size; x++) { if (binary[x + y * size]) { - g.setColor(g.theme.bg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + g.setColor(g.theme.bg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } else { - g.setColor(g.theme.fg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + g.setColor(g.theme.fg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } } } } function printLinearCode(binary) { - var yFrom = 0; + var yFrom = 15; + var yTo = 28; var width = g.getWidth()/binary.length; for(var b = 0; b < binary.length; b++){ var x = b * width; if(binary[b] === "1"){ - g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } else if(binary[b]){ - g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } } } @@ -80,6 +83,8 @@ function showCode(card) { Bangle.removeListener("tap", listener); }; Bangle.on("tap", listener); + E.showScroller(); + g.clear(true); switch (card.type) { case "QR_CODE": const getBinaryQR = require("cards.qrcode.js"); @@ -87,18 +92,23 @@ function showCode(card) { printSquareCode(code.data, code.size); break; case "CODE_39": + g.setFont("Vector:20"); + g.setFontAlign(0,1); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); const CODE39 = require("cards.code39.js"); code = new CODE39(card.value, {}); printLinearCode(code.encode().data); break; case "CODABAR": + g.setFont("Vector:20"); + g.setFontAlign(0,1); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); const codabar = require("cards.codabar.js"); code = new codabar(card.value, {}); printLinearCode(code.encode().data); break; default: - g.clear(true); - g.setFont("Vector:15"); + g.setFont("Vector:30"); g.setFontAlign(0,0); g.drawString(card.value, g.getWidth()/2, g.getHeight()/2); } @@ -114,8 +124,7 @@ function showCard(card) { var titleCnt = lines.length; var start = getDate(card.expiration); var includeDay = true; - if (titleCnt) lines.push(""); // add blank line after name - lines = lines.concat("", /*LANG*/"Tap here to see the value"); + lines = lines.concat("", /*LANG*/"View code"); var valueLine = lines.length - 1; if (card.expiration) lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10)); diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 5f72a1c5b..0aa3249fb 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -4,7 +4,7 @@ "version": "0.1", "description": "Display loyalty cards", "icon": "app.png", - "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], + "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], "tags": "cards", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", diff --git a/apps/cards/screenshot_cards_barcode.png b/apps/cards/screenshot_cards_barcode.png index 1910c173e1e3f1f69c774f2aeeac9904346078f8..e57e9765a76c717f1fcd8d8b85c85def3bff566f 100644 GIT binary patch literal 2283 zcmZ{mc{r3^AIFawF*J`co{2FA4Ot?p5l!}NV<}5Yj5T9>ZINtIOxf2`#u9m=NM%iU z@{}pNv7}y3Z69fSKVPe zZMID;g?xLq_2gUS@|X3q^cdI8OOd@g^8*spPq@Mb7H;Y@#S>rrBWnFe_xRMx;it1J z?h{R}krUH8-3!dM>{WcSq4-;$G|zuzwYA$}jQ+&BWk$v^r$2bX7A`wHYf5Pe902_e z6uw*ZqnJs7c;cAGp=f zj)02#k!-amsst)w2)cDMM)*c!sLgWV`*P1#pUC+o_C3ela97!vN*{eKF8Fm>w+>Ek zC54dVC4d+@t#)3d2YPUAm!v3dV(@McVWOSNV~PfuV4dXUYz>pY9vQ^(*4q@_>5Onr z>ZnzLTWw8$=u+I%i#yy-3+CADpckHdfi#S=JlH}DcEdMv(jz zx#(bEGI216`=dlyv6Z@?TK;nHBqmsn?@8N3jnTD)GvfAqbeinnRubGiM{crXGp_}@ zENM0cRgXDEag~iezP*|=)--|jlG+y8qT;UGMNYvB1vu&zzA1}qe#uO=e(>xVVF_{^ z`KNij4f=5I1SKKUC!Fsv4ie=v!X5*Q*C&V;rdA|!ZwvQm-<(S(RwMVpnG8LweF_Gh z7)rF7TIN%KTvaC8&G@w>q%pEGSx{k2{e+*BR6Avhc2s%Qd98D2aQe%8_LmJvoDCW+ zT>Iv@&$thC##V%2ady%T?Q#d3-R8Qj>Gkx>e%i!GUyd;MMiTHUkMG|pp%(-!uXcFt z5mMw8q#0nC4qPlq+>~2krF%$WF5lKEj5N`dyFoI?%Vmg=lS^7{CQa=6>^?9nJ+^HQ z@elEPl@%Ni-cjtS)HNSu-MY;&g=AbHV+j$nNJ8Ch0FX;-#80z7yNKXKV~t_9MscV5 z(9CxaZc&)rST;Ve0_K*mGRU;A_mT=c1gS0qHI#wWBF=T*J8w&I3Y|5_1kXOa+}&fF z8}>dn;`F(|a(<2-D6sP{LNm!=Jj>r%_QpE{`5xO(Sdaswa!b z@3DYCln{#Tl-^gR(Li1z@-!t1u%A=&Jw3zYb?ju)wZT7osD0R0zxVyE2( zwPZ6J#V3>oZw|GD1D0!|qi*hi`MbCM}mgG zW`D-3LIUG1Xxd->bVlJqoBjyTR!weTfC7EV*(FiL+hU+(IC-Pb(*{Cs9>yfK&25?BJ6$>#()QM{PE$Y{(R|GEN96v6 zNTT>o%O;Chm8xQEgh^t>oojvZ5!^)9mduy_2twtxAdUP-1EyV2@`!u$ZX7e0m%E!7FEJ{-?Av*{({hkB(c@8SWL@m+}-IOtb4(}O*&1Oo+v*HMz{FcAGF z?_lLg-e)3=ikzG}nfk5h-V2oP6I!fhXXTL0^;;!Wi`$b%(i=*E_2${Mlcx}Sp62&; zqogQi_2?0_>TAOt9OHX*jz4W@?CaqT1?epuSf!7SE_4?yXY@4pzG-5)_Jt*gdZ0Ep z6%;qxZbnPS%I3cu-N5iP9!?b3&j1n3nZ;D>=En*iQs*ttnnNet0#xcuYS%xuEZH3T z_M(kvtp)VK_E`wnX^Dtl8dpKF_PYhL9AS(6Jld};nSj(X7;fAlpvuRPhAko@m@+}_ z#~&zXt*;AIH!y{HBD_H+tU)8yrj*;e)i8hnY&0V(wqY!xX1VGNK zX3?3VmsF~y@gYq@3$q|Pl`SeHzDy(Qv*2tO*2pe7{%6B1(0EEw<%7JeTu#V{mR_aC zg6wKiZ6h-Jlzwx7t+!npZn~hPsOWj2q?NKjhSwZi;q`IDO-*n%B}-4!A1WEDZK-Z~ q2oNN1t{XB8RT*c>+8IRj8mbFnU^;R!_dQg^05fAtqe=s}%l`#-lng%r literal 2222 zcmYLLdpwhEAIFBh6V2nez1HK9MV^FA%dzG#!b;>=TCoX}!-H}*C8NnR$;(1?uoZ?J zE3}@LLy6?{B**dOkdWBq_}*LZ;q(36_x-uqt+;cqOiE1Lj!S6THci^x-fnrZB*<4*<&Uj}ZvsS2mO!xL+t4u3EVxnW(}LS%)LfGj~b|7A;&E!g@G#*!2WQ(iHbAf32Nw*_#ls<8|Mxm1SAbe6BTa zmUCXRX4ASOi3M|`I)x;2!~-1-oBOQdG9|&5Qx$>$?Vtcj)wrx~LFIyt4j39v?+c3D zt640e|0fC<(lszp3O4ecjB^2M;kkrXeCX|s{`!^1Tj{O~Hd_l92C2`C8OGipkB(Iw z2&_#|%2dpR%P|_EjE4FlIF96g^SpLV_KDTk$4S~*Tr1pq3Va-^agzF2-tdWDm#S(0 zGYaU02?HoZ!74JAM}9}R)6;0t=G3Hw_g}h$osncs8J9}&{a__tBpw7#wA-!nSsp;R z$q<_6>KK`M?eo~!VNJ&g4rb|`Y#0v{j%Sj=`LPoSHC5@Be#IWjEGLp+x|Etoq^vNt zz1@xU@^2SJbuM$-Nd0CcKAxHiJ(gOGbz}Wu&@Ct1m5Q0%4^A{GO$JSJFa~nNFG|wo z;>&j{-3x#1QQvF5xTFKX~HWscLRNN0;CAq&RvVS7NbU9eoXl|S*8lV zRJJp~c|(kZsY+NAAe#x$CxxtkH52^j?QBBhEFirna_E$V@i42g@3dqb=wl-M{r23x`pPc(@tMx(_ z4722`07n_QpYiouHG6QB0GYqi55?U$PQB`^M*bz3H$cm;j`dq*V&6!hET&38zwYC< zp>)mIwF7Rp-%A28dB;aR9L1jpMNi?g=!RuQ;{CB4*(k)EabF{@H;glaJ>v3Q$8YJb&E@Gs{} z{Ezmo@^f8Wf3W0_sw)pwy_X`6LzdPzaxlmLU_cTv$FDRHruINmc@1=ZGq~ zc9doZMB^!Zf@i!k*X_xpDc#iKX!kH0lJ5pqTN{PkGG8iUc^}Z1+;ozw=d0t?4f?#& z#rKbECEuFuZBFr)t=1t3xMxjgy<^$7UPfDoEN7NE%baSIlmEP)5h7>y-AiKb;)wK- zxRL%s`+`#naRu1e#)%iAw=kmte5sZE#6OH zLWrB%y(it?aM1v43jySRuWg5@F*JCGSf4=Z2eesLy*{t|$bu*$26N4mWaqjIpxk_Q z*b6oA?eV9Gbk__3DCwpZMytKxjNwW0WB~@RwojiW;ljEbV;`HlS5#ik^e5=S|7jdLPPwI?e72_Xt>0AIsfmN7r(F6-0>~TRJzwgWJU;OkngDXkV zFc30~;p z>c^B*obHwX?(J`xC>ad^@`yCL6GS9vQ7nj*PH$6i_Rni?{44MO|0vj%Y+qguFDGf$ zMap5Ji&dTN)v=H+Q~e=ATKVEZ%AhHnC2cooi&R zQBPiMH`O6Xnm@pE)09(>YUp21g4BlE8rH}O^)i0b5@-oP>v2nKZhP4LS>iN%Ve{j9 zaHdCP$ALR_-J$8&d1QG_PsFsrV|8d_qT!_3J^yk`bI;Q3hPD7A(m0%f0b-M#Ffsaf zGR?zvkjFeUe6W`oPVj4t6!YY>x@^Wz#7LK%irM8Hl495lJiB4FA(r_c@mYCT$mXh?5KQE<)Rhk#-piN zZ>U7pqkjUH+b3jTk{b@36fZsV=vky;tf)EJD7iAj{1zI>W7jYEGiUGJ1NBnU=xM;= kur|Occax^m5%3v2p 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 B&yWBB literal 0 HcmV?d00001 From 26da9b2bb36b6842ec1583f815f27d2a4a25ce66 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 17 Aug 2023 14:19:40 +0200 Subject: [PATCH 04/39] Made code colours independent on the theme --- apps/cards/app.js | 41 +++++++++++++++++++++++----------------- apps/cards/metadata.json | 2 +- apps/cards/settings.js | 5 ----- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/cards/app.js b/apps/cards/app.js index a6d802352..3a2635e69 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -14,6 +14,10 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +//may make it configurable in the future +const WHITE=-1 +const BLACK=0 + var FILE = "android.cards.json"; var Locale = require("locale"); @@ -49,13 +53,12 @@ function formatDay(date) { function printSquareCode(binary, size) { var padding = 5; var ratio = (g.getWidth()-(2*padding))/size; - g.setColor(g.theme.fg).fillRect(0, 0, g.getWidth(), g.getHeight()); for (var y = 0; y < size; y++) { for (var x = 0; x < size; x++) { if (binary[x + y * size]) { - g.setColor(g.theme.bg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); + g.setColor(BLACK).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } else { - g.setColor(g.theme.fg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); + g.setColor(WHITE).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } } } @@ -67,47 +70,51 @@ function printLinearCode(binary) { for(var b = 0; b < binary.length; b++){ var x = b * width; if(binary[b] === "1"){ - g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } else if(binary[b]){ - g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } } } function showCode(card) { - var code; - //FIXME doesn't work.. + //FIXME tap doesn't work.. var listener = (data) => { if(data.double) showCard(card); Bangle.removeListener("tap", listener); }; Bangle.on("tap", listener); E.showScroller(); - g.clear(true); + // theme independent + g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight()); switch (card.type) { - case "QR_CODE": + case "QR_CODE": { const getBinaryQR = require("cards.qrcode.js"); - code = getBinaryQR(card.value); + let code = getBinaryQR(card.value); printSquareCode(code.data, code.size); break; - case "CODE_39": + } + case "CODE_39": { g.setFont("Vector:20"); - g.setFontAlign(0,1); + g.setFontAlign(0,1).setColor(BLACK); g.drawString(card.value, g.getWidth()/2, g.getHeight()); const CODE39 = require("cards.code39.js"); - code = new CODE39(card.value, {}); + let code = new CODE39(card.value, {}); printLinearCode(code.encode().data); break; - case "CODABAR": + } + case "CODABAR": { g.setFont("Vector:20"); - g.setFontAlign(0,1); + g.setFontAlign(0,1).setColor(BLACK); g.drawString(card.value, g.getWidth()/2, g.getHeight()); const codabar = require("cards.codabar.js"); - code = new codabar(card.value, {}); + let code = new codabar(card.value, {}); printLinearCode(code.encode().data); break; + } default: + g.clear(true); g.setFont("Vector:30"); g.setFontAlign(0,0); g.drawString(card.value, g.getWidth()/2, g.getHeight()/2); @@ -146,7 +153,7 @@ function showCard(card) { }, select : function(idx) { if (idx>=lines.length-2) showList(); - if (idx>=valueLine) + else if (idx==valueLine) showCode(card); }, back : () => showList() diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 0aa3249fb..63b7da847 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -1,7 +1,7 @@ { "id": "cards", "name": "Cards", - "version": "0.1", + "version": "0.01", "description": "Display loyalty cards", "icon": "app.png", "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], diff --git a/apps/cards/settings.js b/apps/cards/settings.js index b11ba785a..db0ab56de 100644 --- a/apps/cards/settings.js +++ b/apps/cards/settings.js @@ -1,13 +1,8 @@ (function(back) { - function gbSend(message) { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify(message)); - } var settings = require("Storage").readJSON("cards.settings.json",1)||{}; function updateSettings() { require("Storage").writeJSON("cards.settings.json", settings); } - var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; var mainmenu = { "" : { "title" : "Cards" }, "< Back" : back, From b8a123cfe8fbfc93e27f319942977247615ab408 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Sat, 19 Aug 2023 11:56:29 +0200 Subject: [PATCH 05/39] Added support for loyalty cards from gadgetbridge --- apps/android/ChangeLog | 1 + apps/android/boot.js | 5 +++++ apps/android/metadata.json | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index f2a0c5b3f..d531e43a9 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -31,3 +31,4 @@ 0.30: Send firmware and hardware versions on connection Allow alarm enable/disable 0.31: Implement API for activity fetching +0.32: Added support for loyalty cards from gadgetbridge diff --git a/apps/android/boot.js b/apps/android/boot.js index a8027a67c..846fc40a8 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -236,6 +236,11 @@ event.t="remove"; } require("messages").pushMessage(event); + }, + "cards" : function() { + // we receive all, just override what we have + if (Array.isArray(event.d)) + require("Storage").writeJSON("android.cards.json", event.d); } }; var h = HANDLERS[event.t]; diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 8d65d32e3..68bd946c5 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.31", + "version": "0.32", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", @@ -15,6 +15,6 @@ {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], - "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}], + "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}], "sortorder": -8 } From 7b8cfeb6edb16384f66ef5148fca6cbb006f7c6b Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sat, 19 Aug 2023 18:48:03 +0100 Subject: [PATCH 06/39] Asteroids - increased ship, asteroids and font size --- apps/astroid/ChangeLog | 1 + apps/astroid/asteroids.js | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/astroid/ChangeLog b/apps/astroid/ChangeLog index faa0ca5f8..d6a9951bc 100644 --- a/apps/astroid/ChangeLog +++ b/apps/astroid/ChangeLog @@ -1,2 +1,3 @@ 0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast 0.03: Bangle 2 support +0.04: Increase size if ship, asteroids and fonts for better readability diff --git a/apps/astroid/asteroids.js b/apps/astroid/asteroids.js index 6cfa70b47..44593fb95 100644 --- a/apps/astroid/asteroids.js +++ b/apps/astroid/asteroids.js @@ -18,6 +18,10 @@ if (process.env.HWVERSION==2) { } var W = g.getWidth(); var H = g.getHeight(); +var SS = W/11; // ship back length +var SL = W/15; // ship side length +var AS = W/18; // asteroid radius + g.clear().setFontAlign(0,-1); function newAst(x,y) { @@ -25,7 +29,7 @@ function newAst(x,y) { x:x,y:y, vx:Math.random()-0.5, vy:Math.random()-0.5, - rad:3+Math.random()*5 + rad:3+Math.random()*AS }; return a; } @@ -42,7 +46,9 @@ var lastFrame; function gameStop() { running = false; g.clear(); - g.drawString("Game Over!",120,(H-6)/2); + g.setFont('Vector', W/7); + g.setFontAlign(0,0); + g.drawString("Game Over!", W/2, H/2); g.flip(); } @@ -104,12 +110,13 @@ function onFrame() { } g.clear(); - g.drawString(score,W-20,0); + g.setFont('Vector', 16); + g.drawString(score,W-20,16); var rs = Math.PI*0.8; g.drawPoly([ - ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4, - ship.x+Math.cos(ship.r+rs)*3, ship.y+Math.sin(ship.r+rs)*3, - ship.x+Math.cos(ship.r-rs)*3, ship.y+Math.sin(ship.r-rs)*3, + ship.x+Math.cos(ship.r)*SS, ship.y+Math.sin(ship.r)*SS, + ship.x+Math.cos(ship.r+rs)*SL, ship.y+Math.sin(ship.r+rs)*SL, + ship.x+Math.cos(ship.r-rs)*SL, ship.y+Math.sin(ship.r-rs)*SL, ],true); var na = []; ammo.forEach(function(a) { From bc92e0c8c6e301fbe22b061fff3ca901547a67e3 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 19 Aug 2023 20:10:13 +0200 Subject: [PATCH 07/39] widbat: Use flash, not fork to indicate charging Green fork is not easily visible, which can be confusing. --- apps/widbat/ChangeLog | 1 + apps/widbat/metadata.json | 2 +- apps/widbat/widget.js | 11 +++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog index cb11b1be9..9577d952b 100644 --- a/apps/widbat/ChangeLog +++ b/apps/widbat/ChangeLog @@ -8,3 +8,4 @@ 0.09: Misc speed/memory tweaks 0.10: Color changes due to the battery level 0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible +0.12: Use black flash instead of green fork to indicate charging diff --git a/apps/widbat/metadata.json b/apps/widbat/metadata.json index 0151fcbd7..7f0d99f09 100644 --- a/apps/widbat/metadata.json +++ b/apps/widbat/metadata.json @@ -1,7 +1,7 @@ { "id": "widbat", "name": "Battery Level Widget", - "version": "0.11", + "version": "0.12", "description": "Show the current battery level and charging status in the top right of the clock", "icon": "widget.png", "type": "widget", diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index 98eb09227..f87452893 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -26,16 +26,19 @@ var s = 39; var x = this.x, y = this.y; g.reset(); - if (Bangle.isCharging()) { - g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); - x+=16; - } g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); var battery = E.getBattery(); if(battery < 20) {g.setColor("#f00");} else if (battery < 40) {g.setColor(g.theme.dark ? "#ff0" : "#f80");} else {g.setColor("#0f0");} g.fillRect(x+4,y+6,x+4+battery*(s-12)/100,y+17); + if (Bangle.isCharging()) { + let hy = y+11; + let hx = x+s/2; + let flash = [x+5,hy, hx-4,hy-1, hx,y+6, x+s-7,y+hy, + hx+4,y+hy+1, hx,y+17]; + g.setColor(g.theme.fg).fillPoly(flash); + } }}; setWidth(); })() From 2e3ee8cbc360a36eb47691b975b0178cb11b42c0 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 20 Aug 2023 15:10:06 +0100 Subject: [PATCH 08/39] Asteroids - improved collision detected for larger ship v astroid --- apps/astroid/ChangeLog | 1 + apps/astroid/asteroids.js | 7 +++++-- apps/astroid/metadata.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/astroid/ChangeLog b/apps/astroid/ChangeLog index d6a9951bc..8f1d5e355 100644 --- a/apps/astroid/ChangeLog +++ b/apps/astroid/ChangeLog @@ -1,3 +1,4 @@ 0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast 0.03: Bangle 2 support 0.04: Increase size if ship, asteroids and fonts for better readability +0.05: improve collision detect for larger ship v astroid diff --git a/apps/astroid/asteroids.js b/apps/astroid/asteroids.js index 44593fb95..862b6f368 100644 --- a/apps/astroid/asteroids.js +++ b/apps/astroid/asteroids.js @@ -21,6 +21,9 @@ var H = g.getHeight(); var SS = W/11; // ship back length var SL = W/15; // ship side length var AS = W/18; // asteroid radius +// radius of ship, assumed a circle inside equilateral traingle of side SS +// r = a / root 3 where a is length of equilateral triangle +var SR = SS / Math.sqrt(3); g.clear().setFontAlign(0,-1); @@ -45,7 +48,7 @@ var lastFrame; function gameStop() { running = false; - g.clear(); + //g.clear(); g.setFont('Vector', W/7); g.setFontAlign(0,0); g.drawString("Game Over!", W/2, H/2); @@ -172,7 +175,7 @@ function onFrame() { var dx = a.x-ship.x; var dy = a.y-ship.y; var d = Math.sqrt(dx*dx+dy*dy); - if (d < a.rad) crashed = true; + if (d < a.rad + SR) crashed = true; }); ast=na; if (!ast.length) { diff --git a/apps/astroid/metadata.json b/apps/astroid/metadata.json index abb3681ff..e0f4425d8 100644 --- a/apps/astroid/metadata.json +++ b/apps/astroid/metadata.json @@ -1,7 +1,7 @@ { "id": "astroid", "name": "Asteroids!", - "version": "0.03", + "version": "0.05", "description": "Retro asteroids game", "icon": "asteroids.png", "screenshots": [{"url":"screenshot_asteroids.png"}], From 648cc11fdc8ba8ef678295ed8d72e0956c97e079 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 20 Aug 2023 16:15:34 +0100 Subject: [PATCH 09/39] Asteroids, switched to 7 point astroid polygon --- apps/astroid/ChangeLog | 3 ++- apps/astroid/asteroids.js | 18 ++++++++++++++---- apps/astroid/metadata.json | 4 ++-- apps/astroid/screenshot.png | Bin 0 -> 2140 bytes apps/astroid/screenshot_asteroids.png | Bin 1498 -> 0 bytes 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 apps/astroid/screenshot.png delete mode 100644 apps/astroid/screenshot_asteroids.png diff --git a/apps/astroid/ChangeLog b/apps/astroid/ChangeLog index 8f1d5e355..0e2f13745 100644 --- a/apps/astroid/ChangeLog +++ b/apps/astroid/ChangeLog @@ -1,4 +1,5 @@ 0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast 0.03: Bangle 2 support -0.04: Increase size if ship, asteroids and fonts for better readability +0.04: Increase size of ship, asteroids and fonts for better readability 0.05: improve collision detect for larger ship v astroid +0.06: added, 7 point asteroid ploygon, made ship solid, rather than outline diff --git a/apps/astroid/asteroids.js b/apps/astroid/asteroids.js index 862b6f368..076e9a4c3 100644 --- a/apps/astroid/asteroids.js +++ b/apps/astroid/asteroids.js @@ -48,10 +48,9 @@ var lastFrame; function gameStop() { running = false; - //g.clear(); g.setFont('Vector', W/7); g.setFontAlign(0,0); - g.drawString("Game Over!", W/2, H/2); + g.drawString("Game Over", W/2, H/2); g.flip(); } @@ -116,7 +115,7 @@ function onFrame() { g.setFont('Vector', 16); g.drawString(score,W-20,16); var rs = Math.PI*0.8; - g.drawPoly([ + g.fillPoly([ ship.x+Math.cos(ship.r)*SS, ship.y+Math.sin(ship.r)*SS, ship.x+Math.cos(ship.r+rs)*SL, ship.y+Math.sin(ship.r+rs)*SL, ship.x+Math.cos(ship.r-rs)*SL, ship.y+Math.sin(ship.r-rs)*SL, @@ -147,7 +146,18 @@ function onFrame() { ast.forEach(function(a) { a.x += a.vx*d; a.y += a.vy*d; - g.drawCircle(a.x, a.y, a.rad); + //g.drawCircle(a.x, a.y, a.rad); + // a 7 point asteroid with rough circle radius of scale 2 + g.drawPoly([ + a.x , a.y - 1.5 * a.rad, + a.x + a.rad , a.y , + a.x + a.rad/2 , a.y , + a.x + a.rad/2 , a.y + a.rad/2 , + a.x , a.y + a.rad , + a.x - a.rad , a.y , + a.x - a.rad , a.y - a.rad + ],true); + if (a.x<0) a.x+=W; if (a.y<0) a.y+=H; if (a.x>=W) a.x-=W; diff --git a/apps/astroid/metadata.json b/apps/astroid/metadata.json index e0f4425d8..f73feec43 100644 --- a/apps/astroid/metadata.json +++ b/apps/astroid/metadata.json @@ -1,10 +1,10 @@ { "id": "astroid", "name": "Asteroids!", - "version": "0.05", + "version": "0.06", "description": "Retro asteroids game", "icon": "asteroids.png", - "screenshots": [{"url":"screenshot_asteroids.png"}], + "screenshots": [{"url":"screenshot.png"}], "tags": "game", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, diff --git a/apps/astroid/screenshot.png b/apps/astroid/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..81120267dba73cd07b48925bc5c5b04129c75ca4 GIT binary patch literal 2140 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 MUHx3vIVCg!06kBM&j0`b From 536da24a10e9d2e961fc2d2b73c372997452e752 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 22 Aug 2023 17:56:02 +0200 Subject: [PATCH 10/39] Added card's colour in the card's page --- apps/cards/app.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/cards/app.js b/apps/cards/app.js index 3a2635e69..691baf987 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -50,6 +50,17 @@ function formatDay(date) { } } +function getColor(intColor) { + return "#"+(0x1000000+Number(intColor)).toString(16).padStart(6,"0"); +} +function isLight(color) { + var r = +("0x"+color.slice(1,3)); + var g = +("0x"+color.slice(3,5)); + var b = +("0x"+color.slice(5,7)); + var threshold = 0x88 * 3; + return (r+g+b) > threshold; +} + function printSquareCode(binary, size) { var padding = 5; var ratio = (g.getWidth()-(2*padding))/size; @@ -79,7 +90,6 @@ function printLinearCode(binary) { } function showCode(card) { - //FIXME tap doesn't work.. var listener = (data) => { if(data.double) showCard(card); Bangle.removeListener("tap", listener); @@ -140,14 +150,18 @@ function showCard(card) { if(card.note && card.note.trim()) lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10)); lines = lines.concat("",/*LANG*/"< Back"); + var titleBgColor = card.color ? getColor(card.color) : g.theme.bg2; + var titleColor = g.theme.fg2; + if (card.color) + titleColor = isLight(titleBgColor) ? BLACK : WHITE; E.showScroller({ h : g.getFontHeight(), // height of each menu item in pixels c : lines.length, // number of menu items // a function to draw a menu item draw : function(idx, r) { // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 - g.setBgColor(idx Date: Thu, 24 Aug 2023 18:27:22 +0200 Subject: [PATCH 11/39] Going back from the code view with button --- apps/cards/README.md | 2 +- apps/cards/app.js | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/cards/README.md b/apps/cards/README.md index 4bd70a0b1..bd9157d62 100644 --- a/apps/cards/README.md +++ b/apps/cards/README.md @@ -3,7 +3,7 @@ Simple app to display loyalty cards synced from Catima through GadgetBridge. 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. -Double tapping on the code will come back to the visualization of the card's details. +To come back to the visualization of the card's details from the code view, simply press the button. 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). diff --git a/apps/cards/app.js b/apps/cards/app.js index 691baf987..dcef7da76 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -90,12 +90,9 @@ function printLinearCode(binary) { } function showCode(card) { - var listener = (data) => { - if(data.double) showCard(card); - Bangle.removeListener("tap", listener); - }; - Bangle.on("tap", listener); E.showScroller(); + // keeping it on rising edge would come back twice.. + setWatch(()=>showCard(card), BTN, {edge:"falling"}); // theme independent g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight()); switch (card.type) { From 60a99aae53cc28436d6eac7bd086138eb3e5dfb0 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 2 Sep 2023 23:25:54 +0200 Subject: [PATCH 12/39] Widget size no longer changes, so remove associated code. --- apps/widbat/widget.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index f87452893..8262537e8 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -1,10 +1,6 @@ (function(){ - function setWidth() { - WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0); - } Bangle.on('charging',function(charging) { if(charging) Bangle.buzz(); - setWidth(); Bangle.drawWidgets(); // re-layout widgets g.flip(); }); @@ -40,5 +36,4 @@ g.setColor(g.theme.fg).fillPoly(flash); } }}; - setWidth(); })() From 9f2fe22838ba84d3863869d4f6780fed06b2f3cc Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 7 Sep 2023 10:27:19 +0100 Subject: [PATCH 13/39] fix #2996 with new 'core' --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 431a3fb74..37a22e0c4 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 431a3fb743da5c370729ab748cb2c177e70a345b +Subproject commit 37a22e0c49666ec3947ad0daaf5f5675101ca485 From 0bb063f19d68f9a72c544632367143f93f020f78 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 7 Sep 2023 10:49:03 +0100 Subject: [PATCH 14/39] recommend GPS --- apps/assistedgps/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html index 30cdb3eed..994f6d053 100644 --- a/apps/assistedgps/custom.html +++ b/apps/assistedgps/custom.html @@ -31,7 +31,7 @@